Skip to content

Commit 57b0776

Browse files
authored
Add fly-to map action (#408)
### Change list - Add `Map.fly_to` which "flies" map to desired view state location
1 parent 98c7d9a commit 57b0776

File tree

5 files changed

+234
-4
lines changed

5 files changed

+234
-4
lines changed

lonboard/_map.py

Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,153 @@ def __init__(
167167
[`lonboard.basemap.CartoBasemap.PositronNoLabels`][lonboard.basemap.CartoBasemap.PositronNoLabels]
168168
"""
169169

170+
# TODO: We'd prefer a "Strict union of bool and float" but that doesn't
171+
# work here because `Union[bool, float]` would coerce `1` to `True`, which we don't
172+
# want, and `Union[float, bool]` would coerce `True` to `1`, which we also don't
173+
# want.
174+
# In the future we could create a custom trait for this if asked for.
175+
use_device_pixels = traitlets.Any(allow_none=True, default_value=None).tag(
176+
sync=True
177+
)
178+
"""Controls the resolution of the drawing buffer used for rendering.
179+
180+
Setting this to `false` or a number <= 1 will improve performance on high resolution
181+
displays.
182+
183+
**Note**: This parameter must be passed to the `Map()` constructor. It cannot be
184+
changed once the map has been created.
185+
186+
The available options are:
187+
188+
- `true`: Device (physical) pixels resolution is used for rendering, this resolution
189+
is defined by `window.devicePixelRatio`. On Retina/HD systems this resolution is
190+
usually twice as big as CSS pixels resolution.
191+
- `false`: CSS pixels resolution (equal to the canvas size) is used for rendering.
192+
- `Number` (Experimental): Specified Number is used as a custom ratio (drawing
193+
buffer resolution to CSS pixel resolution) to determine drawing buffer size, a
194+
value less than one uses resolution smaller than CSS pixels, gives better
195+
performance but produces blurry images, a value greater than one uses resolution
196+
bigger than CSS pixels resolution (canvas size), produces sharp images but at a
197+
lower performance.
198+
199+
- Type: `float`, `int` or `bool`
200+
- Default: `true`
201+
"""
202+
203+
parameters = traitlets.Any(allow_none=True, default_value=None).tag(sync=True)
204+
"""GPU parameters to pass to deck.gl.
205+
206+
**This is an advanced API. The vast majority of users should not need to touch this
207+
setting.**
208+
209+
!!! Note
210+
211+
The docstring below is copied from upstream deck.gl documentation. Any usage of
212+
`GL` refers to the constants defined in [`@luma.gl/constants`
213+
here](https://github.com/visgl/luma.gl/blob/master/modules/constants/src/webgl-constants.ts),
214+
which comes from the [MDN docs
215+
here](https://developer.mozilla.org/en-US/docs/Web/API/WebGL_API/Constants).
216+
217+
In place of any `GL` constant, you can use the underlying integer it represents.
218+
For example, instead of the JS
219+
220+
```
221+
depthFunc: GL.LEQUAL
222+
```
223+
224+
referring to the [MDN
225+
docs](https://developer.mozilla.org/en-US/docs/Web/API/WebGL_API/Constants#depth_or_stencil_tests),
226+
you should use
227+
228+
```
229+
depthFunc: 0x0203
230+
```
231+
232+
Note that these parameters do not yet work with integer keys. If you would like
233+
to use integer keys, open an issue.
234+
235+
Expects an object with GPU parameters. Before each frame is rendered, this object
236+
will be passed to luma.gl's `setParameters` function to reset the GPU context
237+
parameters, e.g. to disable depth testing, change blending modes etc. The default
238+
parameters set by `Deck` on initialization are the following:
239+
240+
```js
241+
{
242+
blend: true,
243+
blendFunc: [GL.SRC_ALPHA, GL.ONE_MINUS_SRC_ALPHA, GL.ONE, GL.ONE_MINUS_SRC_ALPHA],
244+
polygonOffsetFill: true,
245+
depthTest: true,
246+
depthFunc: GL.LEQUAL
247+
}
248+
```
249+
250+
Refer to the luma.gl
251+
[setParameters](https://github.com/visgl/luma.gl/blob/8.5-release/modules/gltools/docs/api-reference/parameter-setting.md)
252+
API for documentation on supported parameters and values.
253+
254+
```js
255+
import GL from '@luma.gl/constants';
256+
new Deck({
257+
// ...
258+
parameters: {
259+
blendFunc: [GL.ONE, GL.ONE, GL.ONE, GL.ONE],
260+
depthTest: false
261+
}
262+
});
263+
```
264+
265+
Notes:
266+
267+
- Any GPU `parameters` prop supplied to individual layers will still override the
268+
global `parameters` when that layer is rendered.
269+
"""
270+
271+
def fly_to(
272+
self,
273+
*,
274+
longitude: Union[int, float],
275+
latitude: Union[int, float],
276+
zoom: int,
277+
duration: int = 4000,
278+
pitch: Union[int, float] = 0,
279+
bearing: Union[int, float] = 0,
280+
curve: Optional[Union[int, float]] = None,
281+
speed: Optional[Union[int, float]] = None,
282+
screen_speed: Optional[Union[int, float]] = None,
283+
):
284+
""" "Fly" the map to a new location.
285+
286+
Args:
287+
longitude: The longitude of the new viewport.
288+
latitude: The latitude of the new viewport.
289+
zoom: The zoom of the new viewport.
290+
pitch: The pitch of the new viewport. Defaults to 0.
291+
bearing: The bearing of the new viewport. Defaults to 0.
292+
duration: The number of milliseconds for the viewport transition to take.
293+
Defaults to 4000.
294+
curve: The zooming "curve" that will occur along the flight path. Default
295+
`1.414`.
296+
speed: The average speed of the animation defined in relation to
297+
`curve`, it linearly affects the duration, higher speed returns smaller
298+
durations and vice versa. Default `1.2`.
299+
screen_speed: The average speed of the animation measured in screenfuls per
300+
second. Similar to speed it linearly affects the duration, when
301+
specified speed is ignored.
302+
"""
303+
data = {
304+
"type": "fly-to",
305+
"longitude": longitude,
306+
"latitude": latitude,
307+
"zoom": zoom,
308+
"pitch": pitch,
309+
"bearing": bearing,
310+
"transitionDuration": duration,
311+
"curve": curve,
312+
"speed": speed,
313+
"screenSpeed": screen_speed,
314+
}
315+
self.send(data)
316+
170317
def to_html(
171318
self, filename: Union[str, Path, TextIO, IO[str]], title: Optional[str] = None
172319
) -> None:

lonboard/types/map.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,3 +15,5 @@ class MapKwargs(TypedDict, total=False):
1515
show_tooltip: bool
1616
picking_radius: int
1717
basemap_style: Union[str, CartoBasemap]
18+
use_device_pixels: Union[int, float, bool]
19+
parameters: Dict[str, Any]

src/actions/fly-to.ts

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import { SetStateAction } from "react";
2+
import { FlyToMessage } from "../types";
3+
import { FlyToInterpolator, MapViewState } from "@deck.gl/core/typed";
4+
import { isDefined } from "../util";
5+
6+
export function flyTo(
7+
msg: FlyToMessage,
8+
setInitialViewState: (value: SetStateAction<MapViewState>) => void,
9+
) {
10+
const {
11+
longitude,
12+
latitude,
13+
zoom,
14+
pitch,
15+
bearing,
16+
transitionDuration,
17+
curve,
18+
speed,
19+
screenSpeed,
20+
} = msg;
21+
const transitionInterpolator = new FlyToInterpolator({
22+
...(isDefined(curve) && { curve }),
23+
...(isDefined(speed) && { speed }),
24+
...(isDefined(screenSpeed) && { screenSpeed }),
25+
});
26+
setInitialViewState({
27+
longitude,
28+
latitude,
29+
zoom,
30+
pitch,
31+
bearing,
32+
transitionDuration,
33+
transitionInterpolator,
34+
});
35+
}

src/index.tsx

Lines changed: 36 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,15 @@ import { createRender, useModelState, useModel } from "@anywidget/react";
44
import type { Initialize, Render } from "@anywidget/types";
55
import Map from "react-map-gl/maplibre";
66
import DeckGL from "@deck.gl/react/typed";
7-
import type { Layer } from "@deck.gl/core/typed";
7+
import { MapViewState, type Layer } from "@deck.gl/core/typed";
88
import { BaseLayerModel, initializeLayer } from "./model/index.js";
99
import type { WidgetModel } from "@jupyter-widgets/base";
1010
import { initParquetWasm } from "./parquet.js";
1111
import { getTooltip } from "./tooltip/index.js";
12-
import { loadChildModels } from "./util.js";
12+
import { isDefined, loadChildModels } from "./util.js";
1313
import { v4 as uuidv4 } from "uuid";
14+
import { Message } from "./types.js";
15+
import { flyTo } from "./actions/fly-to.js";
1416

1517
await initParquetWasm();
1618

@@ -60,17 +62,40 @@ async function getChildModelState(
6062
}
6163

6264
function App() {
63-
let [initialViewState] = useModelState<DataView>("_initial_view_state");
65+
let model = useModel();
66+
67+
let [pythonInitialViewState] = useModelState<MapViewState>(
68+
"_initial_view_state",
69+
);
6470
let [mapStyle] = useModelState<string>("basemap_style");
6571
let [mapHeight] = useModelState<number>("_height");
6672
let [showTooltip] = useModelState<boolean>("show_tooltip");
6773
let [pickingRadius] = useModelState<number>("picking_radius");
74+
let [useDevicePixels] = useModelState<number | boolean>("use_device_pixels");
75+
let [parameters] = useModelState<object>("parameters");
76+
77+
let [initialViewState, setInitialViewState] = useState(
78+
pythonInitialViewState,
79+
);
80+
81+
// Handle custom messages
82+
model.on("msg:custom", (msg: Message, buffers) => {
83+
switch (msg.type) {
84+
case "fly-to":
85+
flyTo(msg, setInitialViewState);
86+
break;
87+
88+
default:
89+
break;
90+
}
91+
});
92+
6893
const [mapId] = useState(uuidv4());
6994

7095
let [subModelState, setSubModelState] = useState<
7196
Record<string, BaseLayerModel>
7297
>({});
73-
let model = useModel();
98+
7499
let [childLayerIds] = useModelState<string[]>("layers");
75100

76101
// Fake state just to get react to re-render when a model callback is called
@@ -134,6 +159,13 @@ function App() {
134159
// @ts-expect-error
135160
getTooltip={showTooltip && getTooltip}
136161
pickingRadius={pickingRadius}
162+
useDevicePixels={isDefined(useDevicePixels) ? useDevicePixels : true}
163+
// https://deck.gl/docs/api-reference/core/deck#_typedarraymanagerprops
164+
_typedArrayManagerProps={{
165+
overAlloc: 1,
166+
poolSize: 0,
167+
}}
168+
parameters={parameters || {}}
137169
>
138170
<Map mapStyle={mapStyle || DEFAULT_MAP_STYLE} />
139171
</DeckGL>

src/types.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
export type FlyToMessage = {
2+
type: "fly-to";
3+
longitude: number;
4+
latitude: number;
5+
zoom: number;
6+
pitch?: number | undefined;
7+
bearing?: number | undefined;
8+
transitionDuration?: number | "auto" | undefined;
9+
curve?: number | undefined;
10+
speed?: number | undefined;
11+
screenSpeed?: number | undefined;
12+
};
13+
14+
export type Message = FlyToMessage;

0 commit comments

Comments
 (0)