Skip to content
Merged
Show file tree
Hide file tree
Changes from 10 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
2 changes: 1 addition & 1 deletion docs/api/basemap.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
# lonboard.basemap

::: lonboard.basemap.CartoBasemap
::: lonboard.basemap.CartoStyle
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Need to add MaplibreBasemap to the docs here

8 changes: 8 additions & 0 deletions lonboard/_layer.py
Original file line number Diff line number Diff line change
Expand Up @@ -279,6 +279,14 @@ def _add_extension_traits(self, extensions: Sequence[BaseExtension]) -> None:
for an example.
"""

before_id = t.Unicode(None, allow_none=True).tag(sync=True)
"""The ID of a layer in the Maplibre basemap layer stack. This deck.gl layer will be
rendered just before the layer with the given ID.

This only has an effect when the map's `basemap` is a `MaplibreBasemap`, and the map
is rendering in `"interleaved"` mode.
"""


def default_geoarrow_viewport(
table: Table,
Expand Down
64 changes: 53 additions & 11 deletions lonboard/_map.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from __future__ import annotations

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

Expand All @@ -12,10 +13,9 @@
from lonboard._html_export import map_to_html
from lonboard._layer import BaseLayer
from lonboard._viewport import compute_view
from lonboard.basemap import CartoBasemap
from lonboard.basemap import CartoStyle, MaplibreBasemap
from lonboard.traits import (
DEFAULT_INITIAL_VIEW_STATE,
BasemapUrl,
HeightTrait,
VariableLengthTuple,
ViewStateTrait,
Expand Down Expand Up @@ -69,6 +69,8 @@ class Map(BaseAnyWidget):
def __init__(
self,
layers: BaseLayer | Sequence[BaseLayer],
*,
basemap_style: str | CartoStyle | None = None,
**kwargs: Unpack[MapKwargs],
) -> None:
"""Create a new Map.
Expand All @@ -80,12 +82,28 @@ def __init__(
layers: One or more layers to render on this map.

Keyword Args:
basemap_style: DEPRECATED. Use `basemap` instead. A URL to a MapLibre-compatible basemap style.

Various styles are provided in [`lonboard.basemap`](https://developmentseed.org/lonboard/latest/api/basemap/).

kwargs: Passed on to class variables.

Returns:
A Map object.

"""
if basemap_style is not None:
warnings.warn(
"`basemap_style` is deprecated and will be removed in 0.14. Use `basemap` instead.",
DeprecationWarning,
stacklevel=2,
)
if "basemap" in kwargs:
raise ValueError(
"Cannot pass both `basemap_style` and `basemap`. Use only `basemap`.",
)
kwargs["basemap"] = MaplibreBasemap(style=basemap_style)

if isinstance(layers, BaseLayer):
layers = [layers]

Expand Down Expand Up @@ -161,8 +179,6 @@ def on_click(self, callback: Callable, *, remove: bool = False) -> None:
Indicates if a click handler has been registered.
"""

render_mode = t.Unicode(default_value="deck-first").tag(sync=True)

height = HeightTrait().tag(sync=True)
"""Height of the map in pixels, or valid CSS height property.

Expand Down Expand Up @@ -203,17 +219,43 @@ def on_click(self, callback: Callable, *, remove: bool = False) -> None:
- Default: `5`
"""

basemap_style = BasemapUrl(CartoBasemap.PositronNoLabels)
"""
A URL to a MapLibre-compatible basemap style.
basemap: t.Instance[MaplibreBasemap | None] = t.Instance(
MaplibreBasemap,
allow_none=True,
).tag(
sync=True,
**ipywidgets.widget_serialization,
)
"""A basemap instance.

Various styles are provided in [`lonboard.basemap`](https://developmentseed.org/lonboard/latest/api/basemap/).
See [`lonboard.basemap.MaplibreBasemap`] for more information.

- Type: `str`, holding a URL hosting a basemap style.
- Default
[`lonboard.basemap.CartoBasemap.PositronNoLabels`][lonboard.basemap.CartoBasemap.PositronNoLabels]
Pass `None` to disable rendering a basemap.
"""

@property
def basemap_style(self) -> str | None:
"""The URL of the basemap style in use."""
warnings.warn(
"`basemap_style` is deprecated and will be removed in 0.14. Use `basemap` instead.",
DeprecationWarning,
stacklevel=2,
)

if self.basemap is not None:
return self.basemap.style

return None

@basemap_style.setter
def basemap_style(self, value: str | CartoStyle) -> None:
warnings.warn(
"`basemap_style` is deprecated and will be removed in 0.14. Use `basemap` instead.",
DeprecationWarning,
stacklevel=2,
)
self.basemap = MaplibreBasemap(style=value)

custom_attribution = t.Union(
[
t.Unicode(allow_none=True),
Expand Down
6 changes: 3 additions & 3 deletions lonboard/_viz.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
split_mixed_gdf,
split_mixed_shapely_array,
)
from lonboard.basemap import CartoBasemap
from lonboard.basemap import CartoStyle, MaplibreBasemap

if TYPE_CHECKING:
import duckdb
Expand Down Expand Up @@ -212,8 +212,8 @@ def viz(

map_kwargs = map_kwargs if map_kwargs else {}

if "basemap_style" not in map_kwargs:
map_kwargs["basemap_style"] = CartoBasemap.DarkMatter
if "basemap_style" not in map_kwargs and "basemap" not in map_kwargs:
map_kwargs["basemap"] = MaplibreBasemap(style=CartoStyle.DarkMatter)

return Map(layers=layers, **map_kwargs)

Expand Down
140 changes: 136 additions & 4 deletions lonboard/basemap.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,78 @@
"""Basemap helpers."""

from __future__ import annotations

from enum import Enum
from typing import TYPE_CHECKING

import traitlets as t
from typing_extensions import deprecated

from lonboard._base import BaseWidget
from lonboard.traits import BasemapUrl

if TYPE_CHECKING:
from typing import Literal


class CartoStyle(str, Enum):
"""Maplibre-supported basemap styles provided by Carto.

Refer to [Carto
documentation](https://docs.carto.com/carto-for-developers/carto-for-react/guides/basemaps)
for information on styles.
"""

DarkMatter = "https://basemaps.cartocdn.com/gl/dark-matter-gl-style/style.json"
"""A dark map style with labels.

![](https://carto.com/help/images/building-maps/basemaps/dark_labels.png)
"""

DarkMatterNoLabels = (
"https://basemaps.cartocdn.com/gl/dark-matter-nolabels-gl-style/style.json"
)
"""A dark map style without labels.

![](https://carto.com/help/images/building-maps/basemaps/dark_no_labels.png)
"""

Positron = "https://basemaps.cartocdn.com/gl/positron-gl-style/style.json"
"""A light map style with labels.

![](https://carto.com/help/images/building-maps/basemaps/positron_labels.png)
"""

PositronNoLabels = (
"https://basemaps.cartocdn.com/gl/positron-nolabels-gl-style/style.json"
)
"""A light map style without labels.

![](https://carto.com/help/images/building-maps/basemaps/positron_no_labels.png)
"""

Voyager = "https://basemaps.cartocdn.com/gl/voyager-gl-style/style.json"
"""A light, colored map style with labels.

![](https://carto.com/help/images/building-maps/basemaps/voyager_labels.png)
"""

VoyagerNoLabels = (
"https://basemaps.cartocdn.com/gl/voyager-nolabels-gl-style/style.json"
)
"""A light, colored map style without labels.

![](https://carto.com/help/images/building-maps/basemaps/voyager_no_labels.png)
"""


# NOTE: this is fully duplicated because you can't subclass enums, and I don't see any
# other way to provide a deprecation warning
@deprecated(
"CartoBasemap is deprecated, use CartoStyle instead. Will be removed in v0.14",
)
class CartoBasemap(str, Enum):
"""Basemap styles provided by Carto.
"""Maplibre-supported basemap styles provided by Carto.

Refer to [Carto
documentation](https://docs.carto.com/carto-for-developers/carto-for-react/guides/basemaps)
Expand All @@ -20,7 +88,10 @@ class CartoBasemap(str, Enum):
DarkMatterNoLabels = (
"https://basemaps.cartocdn.com/gl/dark-matter-nolabels-gl-style/style.json"
)
"""A dark map style without labels."""
"""A dark map style without labels.

![](https://carto.com/help/images/building-maps/basemaps/dark_no_labels.png)
"""

Positron = "https://basemaps.cartocdn.com/gl/positron-gl-style/style.json"
"""A light map style with labels.
Expand All @@ -31,7 +102,10 @@ class CartoBasemap(str, Enum):
PositronNoLabels = (
"https://basemaps.cartocdn.com/gl/positron-nolabels-gl-style/style.json"
)
"""A light map style without labels."""
"""A light map style without labels.

![](https://carto.com/help/images/building-maps/basemaps/positron_no_labels.png)
"""

Voyager = "https://basemaps.cartocdn.com/gl/voyager-gl-style/style.json"
"""A light, colored map style with labels.
Expand All @@ -42,4 +116,62 @@ class CartoBasemap(str, Enum):
VoyagerNoLabels = (
"https://basemaps.cartocdn.com/gl/voyager-nolabels-gl-style/style.json"
)
"""A light, colored map style without labels."""
"""A light, colored map style without labels.

![](https://carto.com/help/images/building-maps/basemaps/voyager_no_labels.png)
"""


class MaplibreBasemap(BaseWidget):
"""A MapLibre GL JS basemap."""
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Improve this docstring


def __init__(
self,
*,
mode: Literal[
"interleaved",
"overlaid",
"reverse-controlled",
] = "reverse-controlled",
style: str | CartoStyle = CartoStyle.PositronNoLabels,
) -> None:
"""Create a MapLibre GL JS basemap."""
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

todo: improve this docstring

super().__init__(mode=mode, style=style)

mode = t.Enum(
[
"interleaved",
"overlaid",
"reverse-controlled",
],
default_value="reverse-controlled",
).tag(sync=True)
"""The basemap integration mode.

- **`"interleaved"`**:

The interleaved mode renders deck.gl layers into the same context created by MapLibre. If you need to mix deck.gl layers with MapLibre layers, e.g. having deck.gl surfaces below text labels, or objects occluding each other correctly in 3D, then you have to use this option.

- **`"overlaid"`**:

The overlaid mode renders deck.gl in a separate canvas inside the MapLibre's controls container. If your use case does not require interleaving, but you still want to use certain features of maplibre-gl, such as globe view, then you should use this option.

- **`"reverse-controlled"`**:

The reverse-controlled mode renders deck.gl above the MapLibre container and blocks any interaction to the base map.

If you need to have multiple views, you should use this option.

**Default**: `"reverse-controlled"`
"""

style = BasemapUrl(CartoStyle.PositronNoLabels).tag(sync=True)
"""
A URL to a MapLibre-compatible basemap style.

Various styles are provided in [`lonboard.basemap`](https://developmentseed.org/lonboard/latest/api/basemap/).

- Type: `str`, holding a URL hosting a basemap style.
- Default
[`lonboard.basemap.CartoStyle.PositronNoLabels`][lonboard.basemap.CartoStyle.PositronNoLabels]
"""
1 change: 0 additions & 1 deletion lonboard/traits.py
Original file line number Diff line number Diff line change
Expand Up @@ -1078,7 +1078,6 @@ def __init__(
**kwargs: Any,
) -> None:
super().__init__(*args, **kwargs)
self.tag(sync=True)

def validate(self, obj: HasTraits | None, value: Any) -> Any:
value = super().validate(obj, value)
Expand Down
4 changes: 2 additions & 2 deletions lonboard/types/map.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,14 @@
from typing_extensions import TypedDict

if TYPE_CHECKING:
from lonboard.basemap import CartoBasemap
from lonboard.basemap import MaplibreBasemap


class MapKwargs(TypedDict, total=False):
"""Kwargs to pass into map constructor."""

height: int | str
basemap_style: str | CartoBasemap
basemap: MaplibreBasemap
parameters: dict[str, Any]
picking_radius: int
show_tooltip: bool
Expand Down
Loading