Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 0 additions & 3 deletions docs/api/map.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,3 @@ https://mkdocstrings.github.io/python/usage/configuration/members/#filters
group_by_category: false
show_bases: false
filters:


::: lonboard.models.ViewState
8 changes: 8 additions & 0 deletions docs/api/view.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# lonboard.view

::: lonboard.view.BaseView
::: lonboard.view.FirstPersonView
::: lonboard.view.GlobeView
::: lonboard.view.MapView
::: lonboard.view.OrbitView
::: lonboard.view.OrthographicView
8 changes: 8 additions & 0 deletions docs/api/view_state.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# lonboard.view_state

::: lonboard.view_state.BaseViewState
::: lonboard.view_state.MapViewState
::: lonboard.view_state.GlobeViewState
::: lonboard.view_state.FirstPersonViewState
::: lonboard.view_state.OrthographicViewState
::: lonboard.view_state.OrbitViewState
8 changes: 4 additions & 4 deletions examples/linked-maps.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@
"\n",
"from lonboard import Map\n",
"from lonboard.basemap import CartoBasemap\n",
"from lonboard.models import ViewState"
"from lonboard.view_state import MapViewState"
]
},
{
Expand Down Expand Up @@ -143,15 +143,15 @@
"outputs": [],
"source": [
"def sync_positron_to_darkmatter(event: traitlets.utils.bunch.Bunch) -> None:\n",
" if isinstance(event.get(\"new\"), ViewState):\n",
" if isinstance(event.get(\"new\"), MapViewState):\n",
" darkmatter_map.view_state = positron_map.view_state\n",
"\n",
"\n",
"positron_map.observe(sync_positron_to_darkmatter)\n",
"\n",
"\n",
"def sync_darkmatter_to_positron(event: traitlets.utils.bunch.Bunch) -> None:\n",
" if isinstance(event.get(\"new\"), ViewState):\n",
" if isinstance(event.get(\"new\"), MapViewState):\n",
" positron_map.view_state = darkmatter_map.view_state\n",
"\n",
"\n",
Expand Down Expand Up @@ -179,7 +179,7 @@
" event: traitlets.utils.bunch.Bunch,\n",
" other_maps: Sequence[Map] = (),\n",
") -> None:\n",
" if isinstance(event.get(\"new\"), ViewState):\n",
" if isinstance(event.get(\"new\"), MapViewState):\n",
" for lonboard_map in other_maps:\n",
" lonboard_map.view_state = event[\"new\"]\n",
"\n",
Expand Down
60 changes: 41 additions & 19 deletions lonboard/_map.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from __future__ import annotations

import warnings
from dataclasses import replace
from pathlib import Path
from typing import IO, TYPE_CHECKING, Any, TextIO, overload

Expand All @@ -21,8 +22,8 @@
)
from lonboard.layer import BaseLayer
from lonboard.traits import HeightTrait, VariableLengthTuple, ViewStateTrait
from lonboard.traits._map import DEFAULT_INITIAL_VIEW_STATE
from lonboard.view import BaseView
from lonboard.view import BaseView, GlobeView, MapView
from lonboard.view_state import BaseViewState, GlobeViewState, MapViewState

if TYPE_CHECKING:
import sys
Expand Down Expand Up @@ -154,13 +155,13 @@ def on_click(self, callback: Callable, *, remove: bool = False) -> None:
_esm = bundler_output_dir / "index.js"
_css = bundler_output_dir / "index.css"

# TODO: change this view state to allow non-map view states if we have non-map views
# Also allow a list/tuple of view states for multiple views
view_state = ViewStateTrait()
view_state: BaseViewState | None = ViewStateTrait() # type: ignore
"""
The view state of the map.

- Type: [`ViewState`][lonboard.models.ViewState]
- Type: A subclass of [`BaseViewState`][lonboard.view_state.BaseViewState], such as
[`MapViewState`][lonboard.view_state.MapViewState] or
[`GlobeViewState`][lonboard.view_state.GlobeViewState].
- Default: Automatically inferred from the data passed to the map.

You can initialize the map to a specific view state using this property:
Expand Down Expand Up @@ -492,8 +493,9 @@ def add_layer(
elif reset_zoom:
self.view_state = compute_view(self.layers) # type: ignore

def set_view_state(
def set_view_state( # noqa: PLR0913
self,
view_state: BaseViewState | None = None,
*,
longitude: float | None = None,
latitude: float | None = None,
Expand All @@ -505,6 +507,9 @@ def set_view_state(

Any parameters that are unset will not be changed.

Args:
view_state: A complete view state object to set on the map.

Keyword Args:
longitude: the new longitude to set on the map. Defaults to None.
latitude: the new latitude to set on the map. Defaults to None.
Expand All @@ -513,24 +518,38 @@ def set_view_state(
bearing: the new bearing to set on the map. Defaults to None.

"""
view_state = (
self.view_state._asdict() # type: ignore
if self.view_state is not None
else DEFAULT_INITIAL_VIEW_STATE
)
if view_state is not None:
self.view_state = view_state
return

current_view_state = self.view_state

changes = {}
if longitude is not None:
view_state["longitude"] = longitude
changes["longitude"] = longitude
if latitude is not None:
view_state["latitude"] = latitude
changes["latitude"] = latitude
if zoom is not None:
view_state["zoom"] = zoom
changes["zoom"] = zoom

# Only params allowed by globe view state
if isinstance(current_view_state, GlobeViewState):
self.view_state = replace(current_view_state, **changes)
return

# Add more params allowed by map view state
if pitch is not None:
view_state["pitch"] = pitch
changes["pitch"] = pitch
if bearing is not None:
view_state["bearing"] = bearing
changes["bearing"] = bearing

if isinstance(current_view_state, MapViewState):
self.view_state = replace(current_view_state, **changes)
return

self.view_state = view_state
raise TypeError(
"Can only set MapViewState or GlobeViewState parameters individually via set_view_state.\nFor other view state types, pass a complete view_state object.",
)

def fly_to( # noqa: PLR0913
self,
Expand Down Expand Up @@ -656,4 +675,7 @@ def as_html(self) -> HTML:

@traitlets.default("view_state")
def _default_initial_view_state(self) -> dict[str, Any]:
return compute_view(self.layers) # type: ignore
if isinstance(self.views, (MapView, GlobeView)):
return compute_view(self.layers) # type: ignore

return {}
24 changes: 16 additions & 8 deletions lonboard/_serialization.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import math
from concurrent.futures import ThreadPoolExecutor
from dataclasses import asdict
from io import BytesIO
from typing import TYPE_CHECKING, Any, overload

Expand All @@ -23,7 +24,7 @@

if TYPE_CHECKING:
from lonboard.layer import BaseArrowLayer, TripsLayer
from lonboard.models import ViewState
from lonboard.view_state import BaseViewState


DEFAULT_PARQUET_COMPRESSION = "ZSTD"
Expand Down Expand Up @@ -158,13 +159,6 @@ def validate_accessor_length_matches_table(
raise TraitError("accessor must have same length as table")


def serialize_view_state(data: ViewState | None, obj: Any) -> None | dict[str, Any]: # noqa: ARG001
if data is None:
return None

return data._asdict()


def serialize_timestamp_accessor(
timestamps: ChunkedArray,
obj: TripsLayer,
Expand Down Expand Up @@ -199,6 +193,20 @@ def serialize_timestamp_accessor(
return serialize_accessor(f32_timestamps_col, obj)


def _to_camel(s: str) -> str:
parts = s.split("_")
return parts[0] + "".join(p.title() for p in parts[1:])


def serialize_view_state(data: BaseViewState | None, _obj: Any) -> Any:
if data is None:
return None

d = asdict(data) # type: ignore
# Convert to camel case and remove None values
return {_to_camel(k): v for k, v in d.items() if v is not None}


ACCESSOR_SERIALIZATION = {"to_json": serialize_accessor}
TIMESTAMP_ACCESSOR_SERIALIZATION = {"to_json": serialize_timestamp_accessor}
TABLE_SERIALIZATION = {"to_json": serialize_table}
4 changes: 0 additions & 4 deletions lonboard/_viewport.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,14 +74,10 @@ def compute_view(layers: Sequence[BaseLayer]) -> dict[str, Any]:
"longitude": center.x or 0,
"latitude": center.y or 0,
"zoom": 0,
"pitch": 0,
"bearing": 0,
}
else:
return {
"longitude": center.x,
"latitude": center.y,
"zoom": zoom,
"pitch": 0,
"bearing": 0,
}
20 changes: 0 additions & 20 deletions lonboard/models.py

This file was deleted.

26 changes: 8 additions & 18 deletions lonboard/traits/_map.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,23 +7,15 @@

from lonboard._environment import DEFAULT_HEIGHT
from lonboard._serialization import serialize_view_state
from lonboard.models import ViewState
from lonboard.traits._base import FixedErrorTraitType
from lonboard.view_state import BaseViewState, MapViewState

if TYPE_CHECKING:
from traitlets import HasTraits
from traitlets.traitlets import TraitType

from lonboard._map import Map

DEFAULT_INITIAL_VIEW_STATE = {
"latitude": 10,
"longitude": 0,
"zoom": 0.5,
"bearing": 0,
"pitch": 0,
}


class BasemapUrl(traitlets.Unicode):
"""Validation for basemap url."""
Expand Down Expand Up @@ -79,7 +71,7 @@ class ViewStateTrait(FixedErrorTraitType):
"""Trait to validate view state input."""

allow_none = True
default_value = DEFAULT_INITIAL_VIEW_STATE
default_value = None

def __init__(
self: TraitType,
Expand All @@ -90,16 +82,14 @@ def __init__(

self.tag(sync=True, to_json=serialize_view_state)

def validate(self, obj: Map, value: Any) -> None | ViewState:
def validate(self, obj: Map, value: Any) -> None | BaseViewState:
view = obj.views
if value is None:
return None

if isinstance(value, ViewState):
if isinstance(value, BaseViewState):
return value

if isinstance(value, dict):
value = {**DEFAULT_INITIAL_VIEW_STATE, **value}
return ViewState(**value)

self.error(obj, value)
assert False
# Otherwise dict input
validator = view._view_state_type if view is not None else MapViewState # noqa: SLF001
return validator(**value) # type: ignore
3 changes: 2 additions & 1 deletion lonboard/types/map.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

if TYPE_CHECKING:
from lonboard.basemap import MaplibreBasemap
from lonboard.models import BaseViewState
from lonboard.view import BaseView


Expand All @@ -24,4 +25,4 @@ class MapKwargs(TypedDict, total=False):
show_side_panel: bool
use_device_pixels: int | float | bool
views: BaseView | list[BaseView] | tuple[BaseView, ...]
view_state: dict[str, Any]
view_state: BaseViewState | dict[str, Any]
Loading