From a1fe3a0c8f235882c12ad4d8073c69ef36cd9e75 Mon Sep 17 00:00:00 2001 From: jwardbond Date: Wed, 3 Dec 2025 19:55:08 -0500 Subject: [PATCH 1/6] fix: Fix map.set_view_state does not set pitch and bearing Previously, changing pitch and bearing using set_view_state or by clicking and dragging the map did not forward updates to Map.view_state. Added tests to verify. Closes #1052 --- src/util.ts | 9 +++ tests/test_map.py | 10 ++- tests/ui/test_mapview_interaction.py | 92 ++++++++++++++++++++++++++++ 3 files changed, 109 insertions(+), 2 deletions(-) create mode 100644 tests/ui/test_mapview_interaction.py diff --git a/src/util.ts b/src/util.ts index ecc2f21b..6a6990e7 100644 --- a/src/util.ts +++ b/src/util.ts @@ -8,6 +8,7 @@ import { } from "@deck.gl/core"; import { MapRendererProps } from "./renderers"; +import { isMap } from "lodash"; // https://stackoverflow.com/a/52097445 export function isDefined(value: T | undefined | null): value is T { @@ -47,6 +48,14 @@ export function sanitizeViewState( maxZoom: viewState.maxZoom, } : 0), + + // Only include pitch & bearing if defined in ViewState + ...("pitch" in viewState && Number.isFinite(viewState.pitch) + ? { pitch: viewState.pitch } + : {}), + ...("bearing" in viewState && Number.isFinite(viewState.bearing) + ? { bearing: viewState.bearing } + : {}), }; return sanitized; } diff --git a/tests/test_map.py b/tests/test_map.py index ad2186d9..919e099b 100644 --- a/tests/test_map.py +++ b/tests/test_map.py @@ -118,14 +118,20 @@ def test_view_state_orthographic_view_empty(): def test_set_view_state_map_view_kwargs(): m = Map([]) - set_state = {"longitude": -100, "latitude": 40, "zoom": 5} + set_state = { + "longitude": -100, + "latitude": 40, + "zoom": 5, + "pitch": 30, + "bearing": 45, + } m.set_view_state(**set_state) assert m.view_state == MapViewState(**set_state) def test_set_view_state_map_view_instance(): m = Map([]) - set_state = MapViewState(longitude=-100, latitude=40, zoom=5) + set_state = MapViewState(longitude=-100, latitude=40, zoom=5, pitch=30, bearing=45) m.set_view_state(set_state) assert m.view_state == set_state diff --git a/tests/ui/test_mapview_interaction.py b/tests/ui/test_mapview_interaction.py new file mode 100644 index 00000000..3e1ed68e --- /dev/null +++ b/tests/ui/test_mapview_interaction.py @@ -0,0 +1,92 @@ +"""Mapview interaction tests.""" + +import pytest +from IPython.display import display + +from lonboard import Map +from lonboard.view_state import MapViewState + + +@pytest.fixture +def sample_map(): + """Lonboard map for testing.""" + return Map([]) + + +def click_and_drag_canvas( + page_session, + start_pos, + end_pos, + button, +): + """Simulate click and drag on the map. + + Start and end positions are 0-100 percentages of the canvas size. + """ + canvas = page_session.locator("canvas").first + bbox = canvas.bounding_box() + + # Convert relative coords to absolute pixels + start_x = bbox["x"] + (start_pos["x"] / 100) * bbox["width"] + start_y = bbox["y"] + (start_pos["y"] / 100) * bbox["height"] + end_x = bbox["x"] + (end_pos["x"] / 100) * bbox["width"] + end_y = bbox["y"] + (end_pos["y"] / 100) * bbox["height"] + + page_session.mouse.move(start_x, start_y) + page_session.mouse.down(button=button) + page_session.mouse.move(end_x, end_y) + page_session.mouse.up(button=button) + page_session.wait_for_timeout(500) + + +@pytest.mark.usefixtures("solara_test") +def test_jupyter_set_view_state(page_session, sample_map): + """Test setting view state in Jupyter environment.""" + m = sample_map + set_state = { + "longitude": -100, + "latitude": 40, + "zoom": 5, + "pitch": 30, + "bearing": 45, + } + m.set_view_state(**set_state) + + # Simulate a cell break in Jupyter, best I could do + display(m) + canvas = page_session.locator("canvas").first + canvas.wait_for(timeout=5000) + page_session.wait_for_timeout(1000) + + assert m.view_state == MapViewState(**set_state) + + +@pytest.mark.usefixtures("solara_test") +def test_jupyter_manual_view_state_change(page_session, sample_map): + """Test manual view state change in Jupyter environment.""" + m = sample_map + m.set_view_state(zoom=3) + display(m) + initial_view_state = m.view_state + start_pos = {"x": 50, "y": 50} + + # Increase lon from 0 by dragging left + end_pos = {"x": 40, "y": 50} + click_and_drag_canvas(page_session, start_pos, end_pos, button="left") + assert m.view_state.longitude > initial_view_state.longitude + + # Increse lat from 0 by dragging down + end_pos = {"x": 50, "y": 60} + click_and_drag_canvas(page_session, start_pos, end_pos, button="left") + assert m.view_state.latitude > initial_view_state.latitude + + # Increase pitch by dragging up w. RMB + end_pos = {"x": 50, "y": 40} + click_and_drag_canvas(page_session, start_pos, end_pos, button="right") + assert m.view_state.pitch > initial_view_state.pitch + + # Increase bearing by dragging left w. RMB + start_pos = {"x": 40, "y": 40} + end_pos = {"x": 30, "y": 40} + click_and_drag_canvas(page_session, start_pos, end_pos, button="right") + assert m.view_state.bearing > initial_view_state.bearing From 503b1b5872cc5ba463e0f7a35feb3af80820daf1 Mon Sep 17 00:00:00 2001 From: jwardbond Date: Wed, 3 Dec 2025 20:00:52 -0500 Subject: [PATCH 2/6] Removed accidental lodash import --- src/util.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/util.ts b/src/util.ts index 6a6990e7..9e6d894c 100644 --- a/src/util.ts +++ b/src/util.ts @@ -8,7 +8,6 @@ import { } from "@deck.gl/core"; import { MapRendererProps } from "./renderers"; -import { isMap } from "lodash"; // https://stackoverflow.com/a/52097445 export function isDefined(value: T | undefined | null): value is T { From 6424f6f6625d6906b45f317e9805e84100309338 Mon Sep 17 00:00:00 2001 From: jwardbond Date: Wed, 3 Dec 2025 20:22:44 -0500 Subject: [PATCH 3/6] Now waits for canvas to render in click and drag calls --- tests/ui/test_mapview_interaction.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/ui/test_mapview_interaction.py b/tests/ui/test_mapview_interaction.py index 3e1ed68e..d2867b21 100644 --- a/tests/ui/test_mapview_interaction.py +++ b/tests/ui/test_mapview_interaction.py @@ -24,6 +24,7 @@ def click_and_drag_canvas( Start and end positions are 0-100 percentages of the canvas size. """ canvas = page_session.locator("canvas").first + canvas.wait_for(state="visible", timeout=5000) bbox = canvas.bounding_box() # Convert relative coords to absolute pixels From 46f8abf7d587fe616f1b50331b6f0f099679139f Mon Sep 17 00:00:00 2001 From: jwardbond Date: Thu, 4 Dec 2025 07:21:19 -0500 Subject: [PATCH 4/6] Removed redundant check for pitch and bearing in viewState --- src/util.ts | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/src/util.ts b/src/util.ts index 9e6d894c..bc317eb7 100644 --- a/src/util.ts +++ b/src/util.ts @@ -47,14 +47,16 @@ export function sanitizeViewState( maxZoom: viewState.maxZoom, } : 0), - - // Only include pitch & bearing if defined in ViewState - ...("pitch" in viewState && Number.isFinite(viewState.pitch) - ? { pitch: viewState.pitch } - : {}), - ...("bearing" in viewState && Number.isFinite(viewState.bearing) - ? { bearing: viewState.bearing } - : {}), + ...(Number.isFinite(viewState.pitch) + ? { + pitch: viewState.pitch + } + : 0), + ...(Number.isFinite(viewState.bearing) + ? { + bearing: viewState.bearing + } + : 0), }; return sanitized; } From 71eadacfdf69ba3f4b43aa1b04818457a4cf90a2 Mon Sep 17 00:00:00 2001 From: jwardbond Date: Thu, 4 Dec 2025 13:00:39 -0500 Subject: [PATCH 5/6] Removed trailing whitespace --- src/util.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/util.ts b/src/util.ts index bc317eb7..43dff641 100644 --- a/src/util.ts +++ b/src/util.ts @@ -48,13 +48,13 @@ export function sanitizeViewState( } : 0), ...(Number.isFinite(viewState.pitch) - ? { - pitch: viewState.pitch + ? { + pitch: viewState.pitch } : 0), ...(Number.isFinite(viewState.bearing) - ? { - bearing: viewState.bearing + ? { + bearing: viewState.bearing } : 0), }; From c7cd153c7ac179491c2c52cbc75c7ece3ae8169b Mon Sep 17 00:00:00 2001 From: jwardbond Date: Thu, 4 Dec 2025 13:11:17 -0500 Subject: [PATCH 6/6] Added commas to pass linting check --- src/util.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/util.ts b/src/util.ts index 43dff641..f9f3e360 100644 --- a/src/util.ts +++ b/src/util.ts @@ -49,12 +49,12 @@ export function sanitizeViewState( : 0), ...(Number.isFinite(viewState.pitch) ? { - pitch: viewState.pitch + pitch: viewState.pitch, } : 0), ...(Number.isFinite(viewState.bearing) ? { - bearing: viewState.bearing + bearing: viewState.bearing, } : 0), };