I recently needed to implement a way of showing the uncertainty of a marker in a web map (the final application isn’t ready yet) and while I found a bunch of tutorials and stackoverflow answers that covered parts of this I couldn’t find a single page on how to do it. So I’m going to write it up here so that next time I need to do this a search will find this.

TL;DR; You can see a simple demo here, and here’s a picture to show you what I’m talking about.

A map with uncertain markers

There are 3 markers (in my app anyway) and they are represented using a coloured flag. At its simplest the user can just pick a position and if they are happy with that move on to the next step (in the image above this is the blue flag). If however they are less sure of the location they can double click on the marker which will add a 100m circle (in a matching colour) around the point (the green flag above). Clicking on the circle will display a popup which allows them to adjust the radius of the circle (the red flag above). Also at any time the user can click on a flag and drag it (and it’s circle) to a new position on the map.

function addMarker(n) {
    name = n;
    if (lastClick) {
        L.DomUtil.removeClass(map._container, `${lastClick}-flag-cursor-enabled`);
    }
    lastClick = names[name];
    if (!positions[names[name]]) {
        L.DomUtil.addClass(map._container, `${names[name]}-flag-cursor-enabled`);
        map.on('click', setMarker)
    } else {
        map.panTo(positions[names[name]].getLatLng());
    }
    if (Object.values(positions).length == 3) {
      // proceed to remainder of the app
    }
}

When the user clicks on one of the buttons, addMarker is called, this clears the previous cursor if it was set and then if this colour of marker hasn’t been set changes the cursor to match the flag we are adding or pans to that marker if it has been placed on the map. Then we attach the setMarker method to the map’s click event, so when they click we can place a marker there.

function setMarker(e) {
    map.removeEventListener("click", setMarker, false);
    if (!positions[names[name]]) {
        var icon = L.icon({
            iconUrl: icons[names[name]],
            iconSize: [30, 30],
            iconAnchor: [20, 27],
        });
        lat = e.latlng.lat;
        lon = e.latlng.lng;
        //Add a marker to show where you clicked.
        var latlng = L.latLng(lat, lon);
        var marker = new L.marker(latlng, {
            icon: icon,
            title: labels[names[name]],
            draggable: false,
            clickable: true,
            autoPan: true,
        }).addTo(map);
        marker.on('dblclick', (e) => {
            addCircle(e.target);
        });
        marker.on('click', mapClickListen);
        /* this is all to work around dragend triggering a click
         * see https://gis.stackexchange.com/questions/190049/leaflet-map-draggable-marker-events
         */
        marker.on('dragstart', function(e) {
            console.log('marker dragstart event');
            marker.off('click', mapClickListen);
        });
        marker.on('dragend', (e) => {
            e.target.dragging.disable();
            //pointsValid();
            marker.on('click', mapClickListen);
        });
        positions[names[name]] = marker;

    } else {
        map.panTo(positions[names[name]].getLatLng());
    }
    L.DomUtil.removeClass(map._container, `${names[name]}-flag-cursor-enabled`);
}

Again, we check if the marker has already been placed (I’m paranoid) and if not go ahead and create a flag icon with the correctly coloured image. The IconAnchor makes sure that the base of the flag pole is placed on the click location. Then we create a new Marker using the icon at the latitude and longitude of the click, we set it to be clickable but not draggable. The idea is (was) that the user should need to click on a marker to move it, which works but the end of the drag seems to click on the marker again so it never turns off. So I found a workaround from 2016 to the bug and which was apparently fixed some years ago but I can’t seem to make it work. Any way the key step is the add addCircle to the double click event.

function addCircle(marker) {
    name = names[marker.options.title];
    if (!circles[name]) {
        const popup = document.createElement("div");
        popup.innerHTML = 'adjust radius ';
        const spinner = document.createElement("INPUT");
        spinner.id = name;
        spinner.setAttribute("type", "number");
        spinner.setAttribute("min", "10");
        spinner.setAttribute("max", "200");
        spinner.setAttribute("step", "10");
        spinner.setAttribute("value", "100");
        spinner.addEventListener('input', function(e) {
          circles[e.target.id].setRadius(e.data);
        });
        popup.appendChild(spinner);
        circle = L.circle(marker.getLatLng(), 100, {
            color: colors[name],
            fillcolor: colors[name],
            fillopacity: 0.5,

        }).bindPopup(popup).addTo(map);

        marker.on('drag', (e) => {
            circles[names[e.target.options.title]].setLatLng(e.latlng);
        });
        circles[name] = circle;
    }
}

As is usual we check if there is already a circle with this name before we create one. Then we generate the popup that allows the user to change the radius of the circle. This is a basic HTML div with a spinner (or number as HTML calls them for some reason), we can set the minimum, maximum and step for this in the HTML as per normal. The trick then to add a function to the input event that is fired when ever the user clicks the spinner (or uses their up/down arrows on it). The remaining issue is to make sure that the right circle gets updated when the input changes, to do this I set the id of the spinner to the same name that keys the dictionary of circles (these keys match the markers too). Then we can simply use the setRadius method of the CircleMarker that we are about to create. Then we create the CircleMaker and attach the popup to it and add it to the map. Finally, when we drag the matching marker we want to move we need to change the centre of the circle. This needs a function for the drag event to do this, it’s slightly more complex to look up the required circle from a marker due to makers having nice long titles so there’s a double lookup, if I had millions of markers this might be a problem but with 3 it’s fine.

And that’s all there is too it, that’s not to say I like JavaScript or web development but in the end its not too hard!