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

Leaflet: map flickers between centers after flyTo #3035

Open
backbord opened this issue May 8, 2024 · 8 comments
Open

Leaflet: map flickers between centers after flyTo #3035

backbord opened this issue May 8, 2024 · 8 comments
Labels
bug Something isn't working help wanted Extra attention is needed
Milestone

Comments

@backbord
Copy link

backbord commented May 8, 2024

Description

Hi all,

thanks for the great project! Unfortunately, I came across a possible regression with an undesired effect:

I've updated from NiceGUI 1.4.21 to 1.4.24 and now this small example flickers between the old and the new center after the button was clicked.

from functools import partial
from nicegui import ui

with ui.row().classes('h-96 w-96'):
    m = ui.leaflet(center=(48.1, 11.6), zoom=10)  # Munich
    ui.button('Berlin', on_click=partial(m.run_map_method, "flyTo", [52.5, 13.4], 9, {"duration": 1.0}))

    m.on("map-moveend", lambda e: print(e.args))

ui.run()

There appears to be an endless loop issuing moveend events, alternating between the old and the new center, causing the map to flicker and not draw as desired. A pan or zoom with the mouse breaks the loop.

Sometimes, it doesn't go into this bad loops and just works as desired.

Output:

[...]
{'type': 'moveend', 'center': [52.5, 13.4], 'zoom': 9}
{'type': 'moveend', 'center': [48.1, 11.6], 'zoom': 9}
{'type': 'moveend', 'center': [52.5, 13.4], 'zoom': 9}
{'type': 'moveend', 'center': [48.1, 11.6], 'zoom': 9}
{'type': 'moveend', 'center': [52.5, 13.4], 'zoom': 9}
{'type': 'moveend', 'center': [48.1, 11.6], 'zoom': 9}
{'type': 'moveend', 'center': [52.5, 13.4], 'zoom': 9}
{'type': 'moveend', 'center': [48.1, 11.6], 'zoom': 9}
{'type': 'moveend', 'center': [52.5, 13.4], 'zoom': 9}
{'type': 'moveend', 'center': [48.1, 11.6], 'zoom': 9}
{'type': 'moveend', 'center': [52.5, 13.4], 'zoom': 9}
{'type': 'moveend', 'center': [48.1, 11.6], 'zoom': 9}
{'type': 'moveend', 'center': [52.5, 13.4], 'zoom': 9}
{'type': 'moveend', 'center': [48.1, 11.6], 'zoom': 9}
{'type': 'moveend', 'center': [52.5, 13.4], 'zoom': 9}
{'type': 'moveend', 'center': [48.1, 11.6], 'zoom': 9}
[...]

I didn't see changes to leaflet in the diff from 1.4.21 to 1.4.24 and haven't looked deeper into the issue.

Do you have an idea what might cause this loop and how to fix ist?

Thanks!

@falkoschindler
Copy link
Contributor

Thanks a lot for reporting this issue, @backbord!

I just bisected it and identified commit a2544c1 (PR #2867), which was a collaboration with @afullerx and part of release 1.4.23. Somehow the way we process the outbox queue has an effect on the Leaflet element.

I'll try to investigate further. But if anyone else has an idea, I'm all ears.

@falkoschindler falkoschindler added bug Something isn't working help wanted Extra attention is needed labels May 12, 2024
@falkoschindler falkoschindler added this to the 1.4.25 milestone May 12, 2024
@afullerx
Copy link
Contributor

I was able to consistently reproduce the bug using 1.4.21. (Windows 10, Python 3.12). While the changes made in #2867 may be causing it to be triggered more consistently for more users, it seems as though it is not the root cause.

@falkoschindler
Copy link
Contributor

Ah, actually that kind of makes sense:

When introducing asyncio events in PR #2867, we implicitly removed a short delay of 0.01s between loop cycles. Now the outbox reacts immediately when new messages are added to the queue, which could have changed the behavior of ui.leaflet. When adding an asyncio.sleep(0.01) after the event has been awaited

await asyncio.wait_for(self._enqueue_event.wait(), timeout=1.0)

the flicker disappears.

Why does the problem already exist on Windows before PR #2867? As we noticed in #2482, Windows tends to ignore delays shorter than 0.016s, so the implicit delay of 0.01s is ineffective.

Now I want to find out the exact order of messages that leads to the endless loop. Maybe it's a Leaflet-specific problem. Otherwise we need to rethink the outbox.

@falkoschindler falkoschindler modified the milestones: 1.4.25, 1.4.26 May 13, 2024
@afullerx
Copy link
Contributor

afullerx commented May 14, 2024

You nailed it there @falkoschindler. Here is the sequence of messages/updates after the button is clicked:

Message: ('78ed9408-c2ec-4c5c-95e8-79f3d503e2e7', 'run_javascript', {'code': 'return runMethod(5, "run_map_method", ["flyTo",[52.5,13.4],9,{"duration":1.0}])'})

After the "flyTo" operation is completed, the following updates are issued (only the _props attribute of the queued Leaflet object was recorded):

{'resource_path': '/_nicegui/1.4.20/resources/763203f93f18a3f1f5d14f74197580e4', 'center': (48.1, 11.6), 'zoom': 9, 'options': {}, 'draw_control': False}
{'resource_path': '/_nicegui/1.4.20/resources/763203f93f18a3f1f5d14f74197580e4', 'center': [52.5, 13.4], 'zoom': 9, 'options': {}, 'draw_control': False}
{'resource_path': '/_nicegui/1.4.20/resources/763203f93f18a3f1f5d14f74197580e4', 'center': [48.1, 11.6], 'zoom': 9, 'options': {}, 'draw_control': False}
{'resource_path': '/_nicegui/1.4.20/resources/763203f93f18a3f1f5d14f74197580e4', 'center': [52.5, 13.4], 'zoom': 9, 'options': {}, 'draw_control': False}
[...repeated endlessly]

An incorrect update is issued with the original Munich coordinates, quickly followed by the one with the correct Berlin coordinates. When a delay is present, the first update is overwritten before it is emitted, and everything functions properly. Without the delay, both updates are issued in quick succession, causing the leaflet to enter an endless loop navigating between them.

@falkoschindler
Copy link
Contributor

Thanks, @afullerx!

By the way, I thought I might be able to reproduce the problem with plain HTML/JavaScript, but this code works perfectly fine:

<html>
  <head>
    <link rel="stylesheet" href="https://unpkg.com/leaflet/dist/leaflet.css" />
  </head>
  <body>
    <div id="map" style="height: 400px"></div>
    <button id="flyToBerlin">Berlin</button>
    <script src="https://unpkg.com/leaflet/dist/leaflet.js"></script>
    <script>
      const map = L.map("map").setView([48.1, 11.6], 10);
      L.tileLayer("https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png").addTo(map);
      document.getElementById("flyToBerlin").onclick = () => map.flyTo([52.5, 13.4], 9, { duration: 1.0 });
      map.on("moveend", console.log);
    </script>
  </body>
</html>

@falkoschindler
Copy link
Contributor

falkoschindler commented May 14, 2024

I think outbox.py is ok and ui.leaflet is doing something fishy. Let's add

print(f'{time.time():.6f}', 'outbox enqueue_update', getattr(element, 'center', None))

and

print(f'{time.time():.6f}', 'outbox enqueue_message', message_type, data)

to outbox.py and run this slightly modified example:

count = {'value': 0}

def tick():
    count['value'] += 1
    if count['value'] == 10:
        sys.exit(0)  # interrupt endless loop

m = ui.leaflet(center=(48.1, 11.6), zoom=10)
m.on('map-moveend', lambda e: (print(f'{time.time():.6f}', 'on map-moveend:', e.args['center']), tick()))
ui.button('Berlin', on_click=lambda: (print('click'), m.run_map_method('flyTo', [52.5, 13.4], 9, {'duration': 1.0})))

Output:

click
1715678352.542828 outbox enqueue_message run_javascript {'code': 'return runMethod(4, "run_map_method", ["flyTo",[52.5,13.4],9,{"duration":1.0}])'}
1715678353.564281 outbox enqueue_update [48.1, 11.6]
1715678353.564758 outbox enqueue_update [52.5, 13.4]
1715678353.565157 on map-moveend: [52.5, 13.4]
1715678353.569320 outbox enqueue_update [48.1, 11.6]
1715678353.572813 on map-moveend: [48.1, 11.6]

And the oscillation begins.
But why is there an update with the old center [48.1, 11.6] one second after "flyTo" has been started? Is this the root cause?

@falkoschindler
Copy link
Contributor

Got it! Here is what happens:

  1. The "flyTo" transition takes about 1 second to move to Berlin. It ends with a "map-zoomend" and a "map-moveend" event being sent to the server.
  2. The "map-zoomend" is handled first. It causes an update which is sent to the client. But because no "map-moveend" event has been processed yet, the update still contains the old center, which causes the client to move back to Munich.
  3. Now the "map-moveend" event is handled. The updated center is sent to the client, which cause it to move to Berlin.

@falkoschindler
Copy link
Contributor

I just noticed that the "Wait for Initialization" demo is flickering as well. It's probably the same problem.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working help wanted Extra attention is needed
Projects
None yet
Development

No branches or pull requests

3 participants