Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use baselayerchange/overlayadd/overlayremove instead of layeradd/layerremove #5474

Merged

Conversation

deevroman
Copy link
Contributor

@deevroman deevroman commented Jan 6, 2025

Description

This is fix #5466 After merging openstreetmap/leaflet-osm#43 flame graph looks like this:

https://share.firefox.dev/4a2Wc9Z

Most of the time is spent on two handlers that update information about the current location and elements in the right sidebar.

Unfortunately, replacing layeradd layerremove with baselayerchange overlaylayerchange (#5466 (comment)) is difficult, since in addition to the Map Data layer, other elements may be displayed on the map. For example, the currently selected object on the map. It is not clear how to separate changes to Map Data objects and changes to the selected element. It is necessary to track changes in the active element, otherwise at least the Edit button will break.

Therefore, I implemented a little ugly, but safer in terms of breaking business logic. When adding the Map Data layer, I set the pause flag in the slowdown handlers. While the Map Data is rendering, the user cannot interact with the interface, so it seems safe to pause the event handlers.

Total 15s vs 0.5s when rendering 5000 elements.

https://share.firefox.dev/4h4IK7C

@deevroman deevroman marked this pull request as ready for review January 6, 2025 14:00
@AntonKhorev
Copy link
Collaborator

For example, the currently selected object on the map. It is not clear how to separate changes to Map Data objects and changes to the selected element. It is necessary to track changes in the active element, otherwise at least the Edit button will break.

addObject/removeObject in leaflet.map.js could fire some event for active object change. The listener that calls updateLinks could listen to that event.

@deevroman
Copy link
Contributor Author

I also saw a problem in PR: when you click the Map Data checkbox, the cookie with the current location is no longer updated

@deevroman deevroman marked this pull request as draft January 6, 2025 19:57
@deevroman deevroman force-pushed the speedup-datalayer-render branch 5 times, most recently from a359de9 to a32249e Compare January 10, 2025 23:27
@deevroman deevroman changed the title Suspend the layer event handlers when rendering Map Data Use baselayerchange/overlaylayerchange instead of layeradd/layerremove Jan 10, 2025
@deevroman deevroman marked this pull request as ready for review January 10, 2025 23:51
@deevroman
Copy link
Contributor Author

deevroman commented Jan 10, 2025

Okay, firing events from addObject / removeObject turned out to be the best idea. And I changed this PR to use baselayerchange/overlaylayerchange

I also replaced map.on("layeradd",...), which tracks the layer's on/off state with dataLayer.on("add", ...). Using overlaylayerchange in this case doesn't work, because this event isn't thrown when opening a new page with ...&layers=D Plus add / remove looks cleaner in my opinion.

@deevroman deevroman marked this pull request as draft January 10, 2025 23:59
@deevroman deevroman force-pushed the speedup-datalayer-render branch from a32249e to 5b37b54 Compare January 11, 2025 00:11
@deevroman deevroman marked this pull request as ready for review January 11, 2025 00:15
@mmd-osm
Copy link
Contributor

mmd-osm commented Jan 11, 2025

With the latest changes I was able to render Lake Huron locally in a reasonable amount of time (<20s, including 4.5s API response time; 455k nodes). Also map data display is extremely fast now (e.g. 100ms instead of 3s). That is definitely a significant speed-up.

image

Ideas for follow up PRs:

Furthermore, switching from XML to JSON has improved the response time from 20s to 8s (both values include 4.5s API time). For reference, I've uploaded the quick hack here: https://gist.github.com/mmd-osm/d45ff3b4f41908b1c075d70d1b6fdebe (edit: added fetch based + error handling alternative).

image

We should think about adding some error handling:

image
image

@HolgerJeromin
Copy link
Contributor

HolgerJeromin commented Jan 12, 2025

Jquery uses JSON.parse for parsing of ajax() result.

In a next step switching to fetch would allow using
https://developer.mozilla.org/en-US/docs/Web/API/Response/json
Where the parsing is done without blocking the main thread.

@deevroman deevroman force-pushed the speedup-datalayer-render branch from 5b37b54 to 30403cb Compare January 13, 2025 14:50
@AntonKhorev
Copy link
Collaborator

AntonKhorev commented Jan 14, 2025

We probably need to fire baselayerchange either from map.setState or from map.updateLayers. Otherwise if you edit &layers=X in the location bar, share urls are updated to its previous value.

If we follow the same principles as leaflet.layers.js, baselayerchange should be fired by the same function that does map.addLayer, so I guess map.updateLayers should fire it.

@deevroman
Copy link
Contributor Author

deevroman commented Jan 14, 2025

Otherwise if you edit &layers=X in the location bar, share urls are updated to its previous value.

Do I understand correctly that you are talking about the current behavior of the website, and not after PR? At least in Firefox, changing &layers=X does not change the layers on the map, only if you open in a new tab

@mmd-osm
Copy link
Contributor

mmd-osm commented Jan 14, 2025

@AntonKhorev 's comment wasn't exactly clear to me either. I think it's the following. Note that once you change the layer in the URL (in the example below from "C" to "H"), the share link "layer" attribute is updated accordingly:

Peek 2025-01-14 22-58

This doesn't seem to work anymore with the changes in this PR.

Besides, there may be some timing issue in the current code, as the behavior is not 100% reproducible for me, even today. Sometimes the map layer changes along with the share link, in other cases, the old layer is still shown.

@deevroman
Copy link
Contributor Author

deevroman commented Jan 14, 2025

Ah, I understand. I only tested changing the letters that represent the overlay (D/N/G). Changing the letters that respond to tile layers works now. PR broke it. I'll try to fix it

@AntonKhorev
Copy link
Collaborator

AntonKhorev commented Jan 14, 2025

Leaflet ships with a layers control which lets you switch between layers. That thing is responsible for firing baselayerchange events. We don't use this control because we have our own layers menu. Therefore we're responsible for firing that event, which we do:

map.fire("baselayerchange", { layer: layer });

The problem is that our layers menu is not the only thing that can switch layers. All other places where layer change happens need also to fire baselayerchange. I think that map.updateLayers is the only such place. Lack of baselayerchange there becomes more noticeable with this PR because it uses this event.

Similar story with overlaylayerchange, although it's not a standard Leaflet event. Leaflet layers controluses overlayadd and overlayremove.

UPD: we're doing this wrong, see the following comments...

@deevroman deevroman force-pushed the speedup-datalayer-render branch from 30403cb to ad9dcfe Compare January 14, 2025 23:50
@deevroman
Copy link
Contributor Author

Okay, I added the baselayerchange firing. So, when changing D/N/G, the update does not work now, so firing overlaylayerchange is better done in a separate PR.

@deevroman deevroman force-pushed the speedup-datalayer-render branch 2 times, most recently from f10083a to 12fee32 Compare January 16, 2025 01:42
@AntonKhorev
Copy link
Collaborator

Do we fix baselayerchange everywhere before doing this PR that changes listeners for this event?

@deevroman
Copy link
Contributor Author

deevroman commented Jan 27, 2025

@mmd-osm

Besides, there may be some timing issue in the current code, as the behavior is not 100% reproducible for me, even today. Sometimes the map layer changes along with the share link, in other cases, the old layer is still shown.

I think I found a way to reproduce this (If you meant it):

  1. Open https://www.openstreetmap.org/#map=19/60.008946/30.217791&layers=Y
  2. Change Y -> H
  3. Change H -> Y (the layer has not changed)
2025-01-27.13.34.25.mov

p.s. I'll try to fix it
p.p.s I will request a review as soon as I test and describe the meaning of my changes.

@deevroman deevroman force-pushed the speedup-datalayer-render branch 2 times, most recently from 0707400 to e3b440d Compare January 27, 2025 11:24
@deevroman deevroman changed the title Use baselayerchange/overlaylayerchange instead of layeradd/layerremove Use baselayerchange/overlayadd/overlayremove instead of layeradd/layerremove Jan 27, 2025
@deevroman deevroman force-pushed the speedup-datalayer-render branch from e3b440d to 9a43e99 Compare January 27, 2025 12:41
@deevroman deevroman force-pushed the speedup-datalayer-render branch from 9a43e99 to dae7a00 Compare January 27, 2025 12:44
@deevroman
Copy link
Contributor Author

deevroman commented Jan 27, 2025

I rewrote PR. I abandoned overlaylayerchange because this event is not in Leaflet and it is not suitable in our situation.

In the case of baselayerchange, we can expect that our last base layer is always removed and a new base layer is added. Therefore, the removal event is not necessary to track the layer, baselayerchange is enough. But in the case of overlays, this is not so, overlays can just be removed from the map, without adding a new one.

However, after rejecting layerremove, it becomes important to maintain the invariant that there can't be multiple base layers on the map. I.e. first remove all previous layers and only then add a new one (this is important for example for a field with an iframe, sometimes you can see two base layers at the same time)

So I use overlayadd/overlayremove which are in Leaflet https://leafletjs.com/reference.html#map-event
I fire these events when the add/ remove is triggered, as well as for active objects that should be shown on the map (current node, paths, note, ...)


What was tested:

1: Basic layer switching functionality

1.mp4
  1. Switching layers in the URL
2.mp4
  1. Update links to the active object in the Edit button
3.mp4

@deevroman deevroman requested a review from AntonKhorev January 27, 2025 13:05
@AntonKhorev AntonKhorev merged commit c7adb87 into openstreetmap:master Jan 27, 2025
22 checks passed
@AntonKhorev
Copy link
Collaborator

Merged, thanks.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

"layeradd layerremove" event handlers dramatically slow down Map data layer rendering
4 participants