Map Drop

I wanted to recreate the Flickr's feature to drop photos on a map to geolocalize them. Like this:

So, hacking on a MapBox canvas, I obtained that:

Here we have

  • a <div id="map"> used to instance the MapBox map
  • many <div class="thumb">, each containing the img to geolocalize
  • a few CSS and JS

The important part of CSS is the "overlay modal" effect generated around the dragged image when over the map, obtained with a little padding, a blank background, and the evergreen trick for triangles with borders.

.thumb.map-placing {
    padding: 10px;
    background-color: #FFF;
    position: relative;
}

.thumb.map-placing::after{
    content: '';
    position: absolute;
    left: 42%;
    top: 100%;
    width: 0;
    height: 0;
    border-left: 20px solid transparent;
    border-right: 20px solid transparent;
    border-top: 20px solid #FFF;
    clear: both;
}

The JS part combines jQuery-UI (for draggable and droppable), some arithmetic, and the important unproject() MapBox's function, which permits to translate coordinates on screen with coordinates on the rendered map.
The (simple) challenge here is to retrieve coordinates of the triangle's bottom vertex on drop, where the user intends to place the dragged image.

$('.thumb').draggable({
    appendTo: 'body',
    helper: 'clone',
    revert: true
});

$('#map').droppable({
    accept: '.thumb',
    over: function(event, ui) {
        // Here we apply the "overlay modal" effect
        ui.helper.addClass('map-placing');
    },
    out: function(event, ui) {
        // Remove the "overlay modal" effect
        ui.helper.removeClass('map-placing');
    },
    drop: function(event, ui) {
        var helper_offset = ui.helper.offset();
        var map_offset = $('#map').offset();

        // To compute exact coordinates we have to take into account
        // - padding 10px (which means: 20px more than the overlay element width and height
        // - 20px of border height
        var x = helper_offset.left - map_offset.left + ((ui.helper.width() + 20) / 2);
        var y = helper_offset.top - map_offset.top + ui.helper.height() + 20 + 20;
        var point = new mapboxgl.Point(x, y);
        var coordinates = map.unproject(point);

        // "points" is a GeoJSON array elsewhere inited
        points.features.push({
            "type": "Feature",
            "geometry": {
                "type": "Point",
                "coordinates": coordinates.toArray()
            },
            "properties": {
                "name": "Foo Bar"
            }
        });

        // "existing-points" has been previously added with map.addSource()
        map.getSource("existing-points").setData(points);

        ui.helper.remove();
    }
});

That's it. Not so hard, after all...