One of the things that note-viewer does is showing notes on a map. They are displayed as markers which we can click to find notes in the table. Map markers show us locations and statuses of notes, the rest of note details can be found in the table. But we can dig deeper into these details. From the last diary entry we know that note-viewer looks for links to osm elements inside note comments. When clicked these links display their linked element on the map. That shows us the element geometry, but now we may want to see more details about the element. The map is displayed using Leaflet library1 which supports adding popups to various items that are shown over the base layer. A popup seems like an appropriate place to show the element version, changeset, last editing user and tags.
What’s the best way for us to implement these popups? If we just bind the popup (layer.bindPopup()
) to the element geometry on the map, the user would have to click the element for the popup to show up. Sometimes that’s not very convenient to do because of note markers covering the geometry. To make things easier we can open the popup right away (layer.openPopup()
) when the element link is clicked. But that’s not the only thing that needs to happen at that moment, and we may find ourselves struggling a bit with Leaflet. Obviously the linked element may be outside the current map view and we need to pan and zoom to it (map.panTo()
, map.flyTo()
or others). Opening the popup could have done part of this job because by default the map pans to the opened popup. That still doesn’t take care of zooming. But you may think so what, just let the user zoom to the tip of the popup to find the element on the map.
Here comes up our next problem: when the popup is opened at a low zoom level, after zooming it may drift away from the intended place. Anyway we probably want to zoom first and then open the popup. It would have been nice if we could queue the popup opening after the zooming/panning is finished. We’ll have to do that by listening to animation end events. There are zoomend
and moveend
events, which ones do we need to listen to? Maybe the map is already at a correct zoom level or at max zoom and the element is small. Then the zooming is not going to happen. Will the zoomend
event also not happen in which case waiting for it is useless? But maybe the panning is also not going to happen and waiting for moveend
is useless too? Apparently the moveend
event happens after every call like map.panTo()
or map.flyTo()
, so we’ll wait for this event and then open the popup.
We’re still not done because for some unclear reason Leaflet sometimes manages to open the popup at a wrong location even after the map finished moving and zooming. Normally we would attach the popup to the geometry with layer.bindPopup()
where we can also set popup options. One of the options is autoPan
which is on by default as mentioned above. When the popup is opened at a wrong place, the map view scrolls away from the element with this option on, not something we want. With this option off, the popup stays outside the view, not something we want either. Here’s what we can do to solve this problem and what note-viewer currently does:
- Add the geometry to the map.
- Tell the map to
.panTo()
/.flyTo()
the element if it’s a node or fit the element’s bounding box (layer.getBounds()
) into the map view (map.fitBounds()
) and wait formoveend
. - In the
moveend
listener create newL.popup()
not bound to any geometry. - Set the popup coordinates (
popup.setLatLng()
) to the current map center (map.getCenter()
). - Open the popup (
popup.openOn(map)
). - Only after the steps above bind the popup to the geometry (
layer.bindPopup(popup)
).
Now the popup is going to be shown at the center of the element’s bounding box. This is not ideal because this center may be outside the element itself, but at least it’s not at some random location. That’s true unless the user manages to interrupt the map movement by grabbing the map before the animation stops, but they’ll have to do that on purpose and be quick.
-
Everything written here is concerned with Leaflet v1.7.1 and may not be exactly true for the recently released v1.8. ↩
Discussion