|
6 | 6 | from typing import IO, TYPE_CHECKING, Any, TextIO, overload |
7 | 7 |
|
8 | 8 | import ipywidgets |
9 | | -import traitlets |
10 | 9 | import traitlets as t |
11 | 10 | from ipywidgets import CallbackDispatcher |
12 | 11 |
|
|
31 | 30 |
|
32 | 31 | from IPython.display import HTML # type: ignore |
33 | 32 |
|
| 33 | + from lonboard._validators.types import TraitProposal |
34 | 34 | from lonboard.types.map import MapKwargs |
35 | 35 |
|
| 36 | + if sys.version_info >= (3, 11): |
| 37 | + from typing import Self |
| 38 | + else: |
| 39 | + from typing_extensions import Self |
| 40 | + |
36 | 41 | if sys.version_info >= (3, 12): |
37 | 42 | from typing import Unpack |
38 | 43 | else: |
@@ -221,6 +226,23 @@ def on_click(self, callback: Callable, *, remove: bool = False) -> None: |
221 | 226 | Views represent the "camera(s)" (essentially viewport dimensions and projection matrices) that you look at your data with. deck.gl offers multiple view types for both geospatial and non-geospatial use cases. Read the [Views and Projections](https://deck.gl/docs/developer-guide/views) guide for the concept and examples. |
222 | 227 | """ |
223 | 228 |
|
| 229 | + @t.validate("view") |
| 230 | + def _validate_view( |
| 231 | + self, |
| 232 | + proposal: TraitProposal[t.Instance[BaseView | None], BaseView, Self], |
| 233 | + ) -> BaseView: |
| 234 | + # if proposed view is a globe view, ensure that basemap is interleaved |
| 235 | + if ( |
| 236 | + isinstance(proposal["value"], GlobeView) |
| 237 | + and self.basemap is not None |
| 238 | + and self.basemap.mode != "interleaved" |
| 239 | + ): |
| 240 | + raise t.TraitError( |
| 241 | + "GlobeView requires the basemap mode to be 'interleaved'. Please set `basemap.mode='interleaved'`.", |
| 242 | + ) |
| 243 | + |
| 244 | + return proposal["value"] |
| 245 | + |
224 | 246 | show_tooltip = t.Bool(default_value=False).tag(sync=True) |
225 | 247 | """ |
226 | 248 | Whether to render a tooltip on hover on the map. |
@@ -265,6 +287,27 @@ def on_click(self, callback: Callable, *, remove: bool = False) -> None: |
265 | 287 | Pass `None` to disable rendering a basemap. |
266 | 288 | """ |
267 | 289 |
|
| 290 | + @t.validate("basemap") |
| 291 | + def _validate_basemap( |
| 292 | + self, |
| 293 | + proposal: TraitProposal[ |
| 294 | + t.Instance[MaplibreBasemap | None], |
| 295 | + MaplibreBasemap, |
| 296 | + Self, |
| 297 | + ], |
| 298 | + ) -> MaplibreBasemap | None: |
| 299 | + # If proposed basemap is not interleaved, ensure current view is not globe view |
| 300 | + if ( |
| 301 | + proposal["value"] is not None |
| 302 | + and proposal["value"].mode != "interleaved" |
| 303 | + and isinstance(self.view, GlobeView) |
| 304 | + ): |
| 305 | + raise t.TraitError( |
| 306 | + "GlobeView requires the basemap mode to be 'interleaved'. Please set `basemap.mode='interleaved'`.", |
| 307 | + ) |
| 308 | + |
| 309 | + return proposal["value"] |
| 310 | + |
268 | 311 | @property |
269 | 312 | def basemap_style(self) -> str | None: |
270 | 313 | """The URL of the basemap style in use.""" |
@@ -673,7 +716,7 @@ def as_html(self) -> HTML: |
673 | 716 |
|
674 | 717 | return HTML(self.to_html()) |
675 | 718 |
|
676 | | - @traitlets.default("view_state") |
| 719 | + @t.default("view_state") |
677 | 720 | def _default_initial_view_state(self) -> dict[str, Any]: |
678 | 721 | if isinstance(self.view, (MapView, GlobeView)): |
679 | 722 | return compute_view(self.layers) # type: ignore |
|
0 commit comments