mapbox module¶
Mapbox GL JS map widget implementation.
MapboxMap (MapWidget)
¶
Interactive map widget using Mapbox GL JS.
This class provides a Python interface to Mapbox GL JS maps with full bidirectional communication through anywidget.
Note
Requires a Mapbox access token. Set via MAPBOX_TOKEN environment variable or pass directly to the constructor.
Examples:
>>> from anymap_ts import MapboxMap
>>> m = MapboxMap(center=[-122.4, 37.8], zoom=10)
>>> m.add_basemap("mapbox://styles/mapbox/streets-v12")
>>> m
Source code in anymap_ts/mapbox.py
class MapboxMap(MapWidget):
"""Interactive map widget using Mapbox GL JS.
This class provides a Python interface to Mapbox GL JS maps with
full bidirectional communication through anywidget.
Note:
Requires a Mapbox access token. Set via MAPBOX_TOKEN environment
variable or pass directly to the constructor.
Example:
>>> from anymap_ts import MapboxMap
>>> m = MapboxMap(center=[-122.4, 37.8], zoom=10)
>>> m.add_basemap("mapbox://styles/mapbox/streets-v12")
>>> m
"""
# ESM module for frontend
_esm = STATIC_DIR / "mapbox.js"
_css = STATIC_DIR / "mapbox.css"
# Mapbox-specific traits
access_token = traitlets.Unicode("").tag(sync=True)
bearing = traitlets.Float(0.0).tag(sync=True)
pitch = traitlets.Float(0.0).tag(sync=True)
antialias = traitlets.Bool(True).tag(sync=True)
double_click_zoom = traitlets.Bool(True).tag(sync=True)
# Layer tracking
_layer_dict = traitlets.Dict({}).tag(sync=True)
def __init__(
self,
center: Tuple[float, float] = (0.0, 0.0),
zoom: float = 2.0,
width: str = "100%",
height: str = "600px",
style: str = "mapbox://styles/mapbox/streets-v12",
bearing: float = 0.0,
pitch: float = 0.0,
max_pitch: float = 85.0,
access_token: Optional[str] = None,
controls: Optional[Dict[str, Any]] = None,
**kwargs,
):
"""Initialize a Mapbox map.
Args:
center: Map center as (longitude, latitude).
zoom: Initial zoom level.
width: Map width as CSS string.
height: Map height as CSS string.
style: Mapbox style URL (e.g., "mapbox://styles/mapbox/streets-v12").
bearing: Map bearing in degrees.
pitch: Map pitch in degrees.
max_pitch: Maximum pitch angle in degrees (default: 85).
access_token: Mapbox access token. If None, reads from MAPBOX_TOKEN env var.
controls: Dict of controls to add (e.g., {"navigation": True}).
**kwargs: Additional widget arguments.
"""
# Get access token
token = access_token or get_mapbox_token()
if not token:
print(
"Warning: No Mapbox access token provided. "
"Set MAPBOX_TOKEN environment variable or pass access_token parameter."
)
super().__init__(
center=list(center),
zoom=zoom,
width=width,
height=height,
style=style,
bearing=bearing,
pitch=pitch,
max_pitch=max_pitch,
access_token=token,
**kwargs,
)
# Initialize layer dictionary
self._layer_dict = {"Background": []}
# Add default controls
if controls is None:
controls = {"navigation": True, "fullscreen": True}
for control_name, config in controls.items():
if config:
self.add_control(
control_name, **(config if isinstance(config, dict) else {})
)
def set_access_token(self, token: str) -> None:
"""Set the Mapbox access token.
Args:
token: Mapbox access token.
"""
self.access_token = token
# -------------------------------------------------------------------------
# Layer Dict Helpers
# -------------------------------------------------------------------------
def _add_to_layer_dict(self, layer_id: str, category: str = "Overlays") -> None:
"""Add a layer to the layer dictionary for UI tracking."""
layers = self._layer_dict.get(category, [])
if layer_id not in layers:
self._layer_dict = {
**self._layer_dict,
category: layers + [layer_id],
}
def _remove_from_layer_dict(self, layer_id: str) -> None:
"""Remove a layer from the layer dictionary."""
new_dict = {}
for category, layers in self._layer_dict.items():
if layer_id in layers:
new_layers = [lid for lid in layers if lid != layer_id]
if new_layers:
new_dict[category] = new_layers
else:
new_dict[category] = layers
self._layer_dict = new_dict
def _validate_opacity(self, opacity: float, param_name: str = "opacity") -> float:
"""Validate opacity value is between 0 and 1."""
if not 0 <= opacity <= 1:
raise ValueError(f"{param_name} must be between 0 and 1, got {opacity}")
return opacity
def _validate_position(self, position: str) -> str:
"""Validate control position is valid."""
valid_positions = ["top-left", "top-right", "bottom-left", "bottom-right"]
if position not in valid_positions:
raise ValueError(
f"Position must be one of: {', '.join(valid_positions)}, got '{position}'"
)
return position
def _remove_layer_internal(self, layer_id: str, js_method: str) -> None:
"""Internal helper to remove a layer."""
if layer_id in self._layers:
layers = dict(self._layers)
del layers[layer_id]
self._layers = layers
self._remove_from_layer_dict(layer_id)
self.call_js_method(js_method, layer_id)
# -------------------------------------------------------------------------
# Basemap Methods
# -------------------------------------------------------------------------
def add_basemap(
self,
basemap: str = "mapbox://styles/mapbox/streets-v12",
attribution: Optional[str] = None,
**kwargs,
) -> None:
"""Add a basemap layer.
For Mapbox styles, use the style URL format:
- "mapbox://styles/mapbox/streets-v12"
- "mapbox://styles/mapbox/satellite-v9"
- "mapbox://styles/mapbox/satellite-streets-v12"
- "mapbox://styles/mapbox/light-v11"
- "mapbox://styles/mapbox/dark-v11"
- "mapbox://styles/mapbox/outdoors-v12"
Or use XYZ tile URLs for custom basemaps.
Args:
basemap: Mapbox style URL or XYZ tile URL.
attribution: Custom attribution text.
**kwargs: Additional options.
"""
# If it's a Mapbox style URL, set it as the map style
if basemap.startswith("mapbox://"):
self.style = basemap
return
# Otherwise, treat as XYZ tile URL
try:
url, default_attribution = get_basemap_url(basemap)
except (ValueError, KeyError):
url = basemap
default_attribution = ""
self.call_js_method(
"addBasemap",
url,
attribution=attribution or default_attribution,
name=basemap,
**kwargs,
)
# Track in layer dict
basemaps = self._layer_dict.get("Basemaps", [])
if basemap not in basemaps:
self._layer_dict = {
**self._layer_dict,
"Basemaps": basemaps + [basemap],
}
# -------------------------------------------------------------------------
# Vector Data Methods
# -------------------------------------------------------------------------
def add_vector(
self,
data: Any,
layer_type: Optional[str] = None,
paint: Optional[Dict] = None,
name: Optional[str] = None,
fit_bounds: bool = True,
**kwargs,
) -> None:
"""Add vector data to the map.
Supports GeoJSON, GeoDataFrame, or file paths to vector formats.
Args:
data: GeoJSON dict, GeoDataFrame, or path to vector file.
layer_type: Mapbox layer type ('circle', 'line', 'fill', 'symbol').
paint: Mapbox paint properties.
name: Layer name.
fit_bounds: Whether to fit map to data bounds.
**kwargs: Additional layer options.
"""
geojson = to_geojson(data)
layer_id = name or f"vector-{len(self._layers)}"
# Handle URL data - fetch GeoJSON to get bounds and infer layer type
if geojson.get("type") == "url":
url = geojson["url"]
geojson = fetch_geojson(url)
# Infer layer type if not specified
if layer_type is None:
layer_type = infer_layer_type(geojson)
# Get default paint if not provided
if paint is None:
paint = get_default_paint(layer_type)
# Get bounds (use geojson dict, not original data which may be a URL)
bounds = get_bounds(geojson) if fit_bounds else None
# Call JavaScript
self.call_js_method(
"addGeoJSON",
data=geojson,
name=layer_id,
layerType=layer_type,
paint=paint,
fitBounds=fit_bounds,
bounds=bounds,
**kwargs,
)
# Track layer
self._layers = {
**self._layers,
layer_id: {
"id": layer_id,
"type": layer_type,
"source": f"{layer_id}-source",
"paint": paint,
},
}
self._add_to_layer_dict(layer_id, "Vector")
def add_geojson(
self,
data: Union[str, Dict],
layer_type: Optional[str] = None,
paint: Optional[Dict] = None,
name: Optional[str] = None,
fit_bounds: bool = True,
**kwargs,
) -> None:
"""Add GeoJSON data to the map.
Args:
data: GeoJSON dict or URL to GeoJSON file.
layer_type: Mapbox layer type.
paint: Mapbox paint properties.
name: Layer name.
fit_bounds: Whether to fit map to data bounds.
**kwargs: Additional layer options.
"""
self.add_vector(
data,
layer_type=layer_type,
paint=paint,
name=name,
fit_bounds=fit_bounds,
**kwargs,
)
# -------------------------------------------------------------------------
# Raster Data Methods
# -------------------------------------------------------------------------
def add_tile_layer(
self,
url: str,
name: Optional[str] = None,
attribution: str = "",
min_zoom: int = 0,
max_zoom: int = 22,
**kwargs,
) -> None:
"""Add an XYZ tile layer.
Args:
url: Tile URL template with {x}, {y}, {z} placeholders.
name: Layer name.
attribution: Attribution text.
min_zoom: Minimum zoom level.
max_zoom: Maximum zoom level.
**kwargs: Additional options.
"""
layer_id = name or f"tiles-{len(self._layers)}"
self.call_js_method(
"addTileLayer",
url,
name=layer_id,
attribution=attribution,
minZoom=min_zoom,
maxZoom=max_zoom,
**kwargs,
)
# Track layer
self._layers = {
**self._layers,
layer_id: {
"id": layer_id,
"type": "raster",
"source": f"{layer_id}-source",
},
}
self._add_to_layer_dict(layer_id, "Raster")
def add_raster(
self,
source: str,
name: Optional[str] = None,
attribution: str = "",
indexes: Optional[List[int]] = None,
colormap: Optional[str] = None,
vmin: Optional[float] = None,
vmax: Optional[float] = None,
nodata: Optional[float] = None,
fit_bounds: bool = True,
**kwargs,
) -> None:
"""Add a raster layer from a local file using localtileserver."""
try:
from localtileserver import TileClient
except ImportError:
raise ImportError(
"localtileserver is required for local raster support. "
"Install with: pip install anymap-ts[raster]"
)
client = TileClient(source)
tile_params = {}
if indexes:
tile_params["indexes"] = indexes
if colormap:
tile_params["colormap"] = colormap
if vmin is not None or vmax is not None:
tile_params["vmin"] = vmin if vmin is not None else client.min
tile_params["vmax"] = vmax if vmax is not None else client.max
if nodata is not None:
tile_params["nodata"] = nodata
tile_url = client.get_tile_url(**tile_params)
layer_name = name or Path(source).stem
self.add_tile_layer(
tile_url,
name=layer_name,
attribution=attribution,
**kwargs,
)
if fit_bounds:
bounds = client.bounds()
if bounds:
self.fit_bounds([bounds[0], bounds[1], bounds[2], bounds[3]])
def add_stac_layer(
self,
url: Optional[str] = None,
item: Optional[Any] = None,
assets: Optional[List[str]] = None,
colormap: Optional[str] = None,
rescale: Optional[List[float]] = None,
opacity: float = 1.0,
layer_id: Optional[str] = None,
titiler_endpoint: str = "https://titiler.xyz",
attribution: str = "STAC",
fit_bounds: bool = True,
**kwargs,
) -> None:
"""Add a STAC (SpatioTemporal Asset Catalog) layer to the map."""
if url is None and item is None:
raise ValueError("Either 'url' or 'item' must be provided")
if url is not None and item is not None:
raise ValueError("Provide either 'url' or 'item', not both")
if item is not None:
try:
if hasattr(item, "to_dict") and hasattr(item, "self_href"):
stac_url = item.self_href
if not stac_url and hasattr(item, "links"):
for link in item.links:
if link.rel == "self":
stac_url = link.href
break
if not stac_url:
raise ValueError("STAC item must have a self_href or self link")
else:
raise ValueError(
"Item must be a pystac Item object with to_dict() and self_href"
)
except Exception as e:
raise ValueError(f"Invalid STAC item: {e}")
else:
stac_url = url
tile_params = {"url": stac_url}
if assets:
tile_params["assets"] = ",".join(assets)
if colormap:
tile_params["colormap_name"] = colormap
if rescale:
if len(rescale) == 2:
tile_params["rescale"] = f"{rescale[0]},{rescale[1]}"
else:
raise ValueError("rescale must be a list of two values [min, max]")
query_string = urlencode(tile_params)
tile_url = f"{titiler_endpoint.rstrip('/')}/stac/tiles/{{z}}/{{x}}/{{y}}?{query_string}"
layer_name = layer_id or f"stac-{len(self._layers)}"
self.add_tile_layer(
url=tile_url,
name=layer_name,
attribution=attribution,
**kwargs,
)
if fit_bounds and item is not None:
try:
bbox = item.bbox
if bbox and len(bbox) == 4:
self.fit_bounds([[bbox[0], bbox[1]], [bbox[2], bbox[3]]])
except Exception:
pass
# -------------------------------------------------------------------------
# COG Layer (deck.gl)
# -------------------------------------------------------------------------
def add_heatmap(
self,
data: Any,
weight_property: Optional[str] = None,
radius: int = 20,
intensity: float = 1.0,
colormap: Optional[List] = None,
opacity: float = 0.8,
name: Optional[str] = None,
fit_bounds: bool = True,
**kwargs,
) -> None:
"""Add a heatmap layer to the map."""
self._validate_opacity(opacity)
layer_id = name or f"heatmap-{len(self._layers)}"
geojson = to_geojson(data)
if geojson.get("type") == "url":
url = geojson["url"]
geojson = fetch_geojson(url)
if colormap is None:
colormap = [
[0, "rgba(33,102,172,0)"],
[0.2, "rgb(103,169,207)"],
[0.4, "rgb(209,229,240)"],
[0.6, "rgb(253,219,199)"],
[0.8, "rgb(239,138,98)"],
[1, "rgb(178,24,43)"],
]
paint = {
"heatmap-radius": radius,
"heatmap-intensity": intensity,
"heatmap-opacity": opacity,
"heatmap-color": [
"interpolate",
["linear"],
["heatmap-density"],
],
}
for stop, color in colormap:
paint["heatmap-color"].extend([stop, color])
if weight_property:
paint["heatmap-weight"] = ["get", weight_property]
bounds = get_bounds(geojson) if fit_bounds else None
self.call_js_method(
"addGeoJSON",
data=geojson,
name=layer_id,
layerType="heatmap",
paint=paint,
fitBounds=fit_bounds,
bounds=bounds,
**kwargs,
)
self._layers = {
**self._layers,
layer_id: {
"id": layer_id,
"type": "heatmap",
"source": f"{layer_id}-source",
"paint": paint,
},
}
self._add_to_layer_dict(layer_id, "Heatmap")
def add_cog_layer(
self,
url: str,
name: Optional[str] = None,
opacity: float = 1.0,
visible: bool = True,
debug: bool = False,
debug_opacity: float = 0.25,
max_error: float = 0.125,
fit_bounds: bool = True,
before_id: Optional[str] = None,
**kwargs,
) -> None:
"""Add a Cloud Optimized GeoTIFF (COG) layer using deck.gl-raster.
This method renders COG files directly in the browser using GPU-accelerated
deck.gl rendering with automatic reprojection support.
Args:
url: URL to the Cloud Optimized GeoTIFF file.
name: Layer ID. If None, auto-generated.
opacity: Layer opacity (0-1).
visible: Whether layer is visible.
debug: Show reprojection mesh for debugging.
debug_opacity: Opacity of debug mesh (0-1).
max_error: Maximum reprojection error in pixels. Lower values
create denser mesh for better accuracy.
fit_bounds: Whether to fit map to COG bounds after loading.
before_id: ID of layer to insert before.
**kwargs: Additional COGLayer props.
Example:
>>> from anymap_ts import MapboxMap
>>> m = MapboxMap()
>>> m.add_cog_layer(
... "https://example.com/landcover.tif",
... name="landcover",
... opacity=0.8
... )
"""
layer_id = name or f"cog-{len(self._layers)}"
self.call_js_method(
"addCOGLayer",
id=layer_id,
geotiff=url,
opacity=opacity,
visible=visible,
debug=debug,
debugOpacity=debug_opacity,
maxError=max_error,
fitBounds=fit_bounds,
beforeId=before_id,
**kwargs,
)
self._layers = {
**self._layers,
layer_id: {
"id": layer_id,
"type": "cog",
"url": url,
},
}
self._add_to_layer_dict(layer_id, "Raster")
def remove_cog_layer(self, layer_id: str) -> None:
"""Remove a COG layer."""
self._remove_layer_internal(layer_id, "removeCOGLayer")
def add_zarr_layer(
self,
url: str,
variable: str,
name: Optional[str] = None,
colormap: Optional[List[str]] = None,
clim: Optional[Tuple[float, float]] = None,
opacity: float = 1.0,
selector: Optional[Dict[str, Any]] = None,
minzoom: int = 0,
maxzoom: int = 22,
fill_value: Optional[float] = None,
spatial_dimensions: Optional[Dict[str, str]] = None,
zarr_version: Optional[int] = None,
bounds: Optional[List[float]] = None,
**kwargs,
) -> None:
"""Add a Zarr dataset layer for visualizing multidimensional array data."""
layer_id = name or f"zarr-{len(self._layers)}"
self.call_js_method(
"addZarrLayer",
id=layer_id,
source=url,
variable=variable,
colormap=colormap or ["#000000", "#ffffff"],
clim=list(clim) if clim else [0, 100],
opacity=opacity,
selector=selector or {},
minzoom=minzoom,
maxzoom=maxzoom,
fillValue=fill_value,
spatialDimensions=spatial_dimensions,
zarrVersion=zarr_version,
bounds=bounds,
**kwargs,
)
self._layers = {
**self._layers,
layer_id: {
"id": layer_id,
"type": "zarr",
"url": url,
"variable": variable,
},
}
self._add_to_layer_dict(layer_id, "Raster")
def remove_zarr_layer(self, layer_id: str) -> None:
"""Remove a Zarr layer."""
self._remove_layer_internal(layer_id, "removeZarrLayer")
def update_zarr_layer(
self,
layer_id: str,
selector: Optional[Dict[str, Any]] = None,
clim: Optional[Tuple[float, float]] = None,
colormap: Optional[List[str]] = None,
opacity: Optional[float] = None,
) -> None:
"""Update a Zarr layer's properties dynamically."""
update_kwargs: Dict[str, Any] = {"id": layer_id}
if selector is not None:
update_kwargs["selector"] = selector
if clim is not None:
update_kwargs["clim"] = list(clim)
if colormap is not None:
update_kwargs["colormap"] = colormap
if opacity is not None:
update_kwargs["opacity"] = opacity
self.call_js_method("updateZarrLayer", **update_kwargs)
def add_pmtiles_layer(
self,
url: str,
layer_id: Optional[str] = None,
style: Optional[Dict[str, Any]] = None,
opacity: float = 1.0,
visible: bool = True,
fit_bounds: bool = False,
source_type: str = "vector",
**kwargs,
) -> None:
"""Add a PMTiles layer for efficient vector or raster tile serving."""
layer_id = layer_id or f"pmtiles-{len(self._layers)}"
self.call_js_method(
"addPMTilesLayer",
url=url,
id=layer_id,
style=style or {},
opacity=opacity,
visible=visible,
fitBounds=fit_bounds,
sourceType=source_type,
name=layer_id,
**kwargs,
)
self._layers = {
**self._layers,
layer_id: {
"id": layer_id,
"type": "pmtiles",
"url": url,
"source_type": source_type,
},
}
category = "Vector" if source_type == "vector" else "Raster"
self._add_to_layer_dict(layer_id, category)
def remove_pmtiles_layer(self, layer_id: str) -> None:
"""Remove a PMTiles layer."""
self._remove_layer_internal(layer_id, "removePMTilesLayer")
# -------------------------------------------------------------------------
# Arc Layer (deck.gl)
# -------------------------------------------------------------------------
def add_arc_layer(
self,
data: Any,
name: Optional[str] = None,
get_source_position: Union[str, Any] = "source",
get_target_position: Union[str, Any] = "target",
get_source_color: Optional[List[int]] = None,
get_target_color: Optional[List[int]] = None,
get_width: Union[float, str] = 1,
get_height: float = 1,
great_circle: bool = False,
pickable: bool = True,
opacity: float = 0.8,
**kwargs,
) -> None:
"""Add an arc layer for origin-destination visualization using deck.gl.
Arc layers are ideal for visualizing connections between locations,
such as flight routes, migration patterns, or network flows.
Args:
data: Array of data objects with source/target coordinates.
Each object should have source and target positions.
name: Layer ID. If None, auto-generated.
get_source_position: Accessor for source position [lng, lat].
Can be a string (property name) or a value.
get_target_position: Accessor for target position [lng, lat].
Can be a string (property name) or a value.
get_source_color: Source end color as [r, g, b, a].
Default: [51, 136, 255, 255] (blue).
get_target_color: Target end color as [r, g, b, a].
Default: [255, 136, 51, 255] (orange).
get_width: Arc width in pixels. Can be a number or accessor.
get_height: Arc height multiplier. Higher values create more curved arcs.
great_circle: Whether to draw arcs along great circles.
pickable: Whether layer responds to hover/click events.
opacity: Layer opacity (0-1).
**kwargs: Additional ArcLayer props.
Example:
>>> from anymap_ts import MapboxMap
>>> m = MapboxMap()
>>> arcs = [
... {"source": [-122.4, 37.8], "target": [-73.9, 40.7]},
... {"source": [-122.4, 37.8], "target": [-0.1, 51.5]},
... ]
>>> m.add_arc_layer(arcs, name="flights")
"""
layer_id = name or f"arc-{len(self._layers)}"
processed_data = self._process_deck_data(data)
self.call_js_method(
"addArcLayer",
id=layer_id,
data=processed_data,
getSourcePosition=get_source_position,
getTargetPosition=get_target_position,
getSourceColor=get_source_color or [51, 136, 255, 255],
getTargetColor=get_target_color or [255, 136, 51, 255],
getWidth=get_width,
getHeight=get_height,
greatCircle=great_circle,
pickable=pickable,
opacity=opacity,
**kwargs,
)
self._layers = {
**self._layers,
layer_id: {
"id": layer_id,
"type": "arc",
},
}
self._add_to_layer_dict(layer_id, "Deck.gl")
def remove_arc_layer(self, layer_id: str) -> None:
"""Remove an arc layer."""
self._remove_layer_internal(layer_id, "removeArcLayer")
# -------------------------------------------------------------------------
# PointCloud Layer (deck.gl)
# -------------------------------------------------------------------------
def add_point_cloud_layer(
self,
data: Any,
name: Optional[str] = None,
get_position: Union[str, Any] = "position",
get_color: Optional[Union[List[int], str]] = None,
get_normal: Optional[Union[str, Any]] = None,
point_size: float = 2,
size_units: str = "pixels",
pickable: bool = True,
opacity: float = 1.0,
material: bool = True,
coordinate_system: Optional[int] = None,
coordinate_origin: Optional[List[float]] = None,
**kwargs,
) -> None:
"""Add a point cloud layer for 3D point visualization using deck.gl.
Point cloud layers render large collections of 3D points, ideal for
LiDAR data, photogrammetry outputs, or any 3D point dataset.
Args:
data: Array of point data with positions. Each point should have
x, y, z coordinates (or position array).
name: Layer ID. If None, auto-generated.
get_position: Accessor for point position [x, y, z].
Can be a string (property name) or a value.
get_color: Accessor or value for point color [r, g, b, a].
Default: [255, 255, 255, 255] (white).
get_normal: Accessor for point normal [nx, ny, nz] for lighting.
Default: [0, 0, 1] (pointing up).
point_size: Point size in pixels or meters (depends on size_units).
size_units: Size units: 'pixels', 'meters', or 'common'.
pickable: Whether layer responds to hover/click events.
opacity: Layer opacity (0-1).
material: Whether to enable lighting effects.
coordinate_system: Coordinate system for positions.
coordinate_origin: Origin for coordinate system [x, y, z].
**kwargs: Additional PointCloudLayer props.
Example:
>>> from anymap_ts import MapboxMap
>>> m = MapboxMap(pitch=45)
>>> points = [
... {"position": [-122.4, 37.8, 100], "color": [255, 0, 0, 255]},
... {"position": [-122.3, 37.7, 200], "color": [0, 255, 0, 255]},
... ]
>>> m.add_point_cloud_layer(points, point_size=5)
"""
layer_id = name or f"pointcloud-{len(self._layers)}"
processed_data = self._process_deck_data(data)
self.call_js_method(
"addPointCloudLayer",
id=layer_id,
data=processed_data,
getPosition=get_position,
getColor=get_color or [255, 255, 255, 255],
getNormal=get_normal,
pointSize=point_size,
sizeUnits=size_units,
pickable=pickable,
opacity=opacity,
material=material,
coordinateSystem=coordinate_system,
coordinateOrigin=coordinate_origin,
**kwargs,
)
self._layers = {
**self._layers,
layer_id: {
"id": layer_id,
"type": "pointcloud",
},
}
self._add_to_layer_dict(layer_id, "Deck.gl")
def remove_point_cloud_layer(self, layer_id: str) -> None:
"""Remove a point cloud layer."""
self._remove_layer_internal(layer_id, "removePointCloudLayer")
def add_scatterplot_layer(
self,
data: Any,
name: Optional[str] = None,
get_position: Union[str, Any] = "coordinates",
get_radius: Union[float, str] = 5,
get_fill_color: Optional[Union[List[int], str]] = None,
get_line_color: Optional[Union[List[int], str]] = None,
radius_scale: float = 1,
radius_min_pixels: float = 1,
radius_max_pixels: float = 100,
line_width_min_pixels: float = 1,
stroked: bool = True,
filled: bool = True,
pickable: bool = True,
opacity: float = 0.8,
**kwargs,
) -> None:
"""Add a scatterplot layer using deck.gl."""
layer_id = name or f"scatterplot-{len(self._layers)}"
processed_data = self._process_deck_data(data)
self.call_js_method(
"addScatterplotLayer",
id=layer_id,
data=processed_data,
getPosition=get_position,
getRadius=get_radius,
getFillColor=get_fill_color or [51, 136, 255, 200],
getLineColor=get_line_color or [255, 255, 255, 255],
radiusScale=radius_scale,
radiusMinPixels=radius_min_pixels,
radiusMaxPixels=radius_max_pixels,
lineWidthMinPixels=line_width_min_pixels,
stroked=stroked,
filled=filled,
pickable=pickable,
opacity=opacity,
**kwargs,
)
self._layers = {
**self._layers,
layer_id: {"id": layer_id, "type": "scatterplot"},
}
self._add_to_layer_dict(layer_id, "Deck.gl")
def add_path_layer(
self,
data: Any,
name: Optional[str] = None,
get_path: Union[str, Any] = "path",
get_color: Optional[Union[List[int], str]] = None,
get_width: Union[float, str] = 1,
width_scale: float = 1,
width_min_pixels: float = 1,
pickable: bool = True,
opacity: float = 0.8,
**kwargs,
) -> None:
"""Add a path layer using deck.gl."""
layer_id = name or f"path-{len(self._layers)}"
processed_data = self._process_deck_data(data)
self.call_js_method(
"addPathLayer",
id=layer_id,
data=processed_data,
getPath=get_path,
getColor=get_color or [51, 136, 255, 200],
getWidth=get_width,
widthScale=width_scale,
widthMinPixels=width_min_pixels,
pickable=pickable,
opacity=opacity,
**kwargs,
)
self._layers = {**self._layers, layer_id: {"id": layer_id, "type": "path"}}
self._add_to_layer_dict(layer_id, "Deck.gl")
def add_polygon_layer(
self,
data: Any,
name: Optional[str] = None,
get_polygon: Union[str, Any] = "polygon",
get_fill_color: Optional[Union[List[int], str]] = None,
get_line_color: Optional[Union[List[int], str]] = None,
get_line_width: Union[float, str] = 1,
get_elevation: Union[float, str] = 0,
extruded: bool = False,
wireframe: bool = False,
filled: bool = True,
stroked: bool = True,
line_width_min_pixels: float = 1,
pickable: bool = True,
opacity: float = 0.5,
**kwargs,
) -> None:
"""Add a polygon layer using deck.gl."""
layer_id = name or f"polygon-{len(self._layers)}"
processed_data = self._process_deck_data(data)
self.call_js_method(
"addPolygonLayer",
id=layer_id,
data=processed_data,
getPolygon=get_polygon,
getFillColor=get_fill_color or [51, 136, 255, 128],
getLineColor=get_line_color or [0, 0, 255, 255],
getLineWidth=get_line_width,
getElevation=get_elevation,
extruded=extruded,
wireframe=wireframe,
filled=filled,
stroked=stroked,
lineWidthMinPixels=line_width_min_pixels,
pickable=pickable,
opacity=opacity,
**kwargs,
)
self._layers = {**self._layers, layer_id: {"id": layer_id, "type": "polygon"}}
self._add_to_layer_dict(layer_id, "Deck.gl")
def add_hexagon_layer(
self,
data: Any,
name: Optional[str] = None,
get_position: Union[str, Any] = "coordinates",
radius: float = 1000,
elevation_scale: float = 4,
extruded: bool = True,
color_range: Optional[List[List[int]]] = None,
pickable: bool = True,
opacity: float = 0.8,
**kwargs,
) -> None:
"""Add a hexagon layer using deck.gl."""
layer_id = name or f"hexagon-{len(self._layers)}"
processed_data = self._process_deck_data(data)
default_color_range = [
[1, 152, 189],
[73, 227, 206],
[216, 254, 181],
[254, 237, 177],
[254, 173, 84],
[209, 55, 78],
]
self.call_js_method(
"addHexagonLayer",
id=layer_id,
data=processed_data,
getPosition=get_position,
radius=radius,
elevationScale=elevation_scale,
extruded=extruded,
colorRange=color_range or default_color_range,
pickable=pickable,
opacity=opacity,
**kwargs,
)
self._layers = {**self._layers, layer_id: {"id": layer_id, "type": "hexagon"}}
self._add_to_layer_dict(layer_id, "Deck.gl")
def add_deck_heatmap_layer(
self,
data: Any,
name: Optional[str] = None,
get_position: Union[str, Any] = "coordinates",
get_weight: Union[float, str] = 1,
radius_pixels: float = 30,
intensity: float = 1,
threshold: float = 0.05,
color_range: Optional[List[List[int]]] = None,
opacity: float = 1,
**kwargs,
) -> None:
"""Add a GPU-accelerated heatmap layer using deck.gl."""
layer_id = name or f"deck-heatmap-{len(self._layers)}"
processed_data = self._process_deck_data(data)
default_color_range = [
[255, 255, 178, 25],
[254, 217, 118, 85],
[254, 178, 76, 127],
[253, 141, 60, 170],
[240, 59, 32, 212],
[189, 0, 38, 255],
]
self.call_js_method(
"addHeatmapLayer",
id=layer_id,
data=processed_data,
getPosition=get_position,
getWeight=get_weight,
radiusPixels=radius_pixels,
intensity=intensity,
threshold=threshold,
colorRange=color_range or default_color_range,
opacity=opacity,
**kwargs,
)
self._layers = {
**self._layers,
layer_id: {"id": layer_id, "type": "deck-heatmap"},
}
self._add_to_layer_dict(layer_id, "Deck.gl")
def add_grid_layer(
self,
data: Any,
name: Optional[str] = None,
get_position: Union[str, Any] = "coordinates",
cell_size: float = 200,
elevation_scale: float = 4,
extruded: bool = True,
color_range: Optional[List[List[int]]] = None,
pickable: bool = True,
opacity: float = 0.8,
**kwargs,
) -> None:
"""Add a grid layer using deck.gl."""
layer_id = name or f"grid-{len(self._layers)}"
processed_data = self._process_deck_data(data)
default_color_range = [
[1, 152, 189],
[73, 227, 206],
[216, 254, 181],
[254, 237, 177],
[254, 173, 84],
[209, 55, 78],
]
self.call_js_method(
"addGridLayer",
id=layer_id,
data=processed_data,
getPosition=get_position,
cellSize=cell_size,
elevationScale=elevation_scale,
extruded=extruded,
colorRange=color_range or default_color_range,
pickable=pickable,
opacity=opacity,
**kwargs,
)
self._layers = {**self._layers, layer_id: {"id": layer_id, "type": "grid"}}
self._add_to_layer_dict(layer_id, "Deck.gl")
def add_icon_layer(
self,
data: Any,
name: Optional[str] = None,
get_position: Union[str, Any] = "coordinates",
get_icon: Union[str, Any] = "icon",
get_size: Union[float, str] = 20,
get_color: Optional[Union[List[int], str]] = None,
icon_atlas: Optional[str] = None,
icon_mapping: Optional[Dict] = None,
pickable: bool = True,
opacity: float = 1,
**kwargs,
) -> None:
"""Add an icon layer using deck.gl."""
layer_id = name or f"icon-{len(self._layers)}"
processed_data = self._process_deck_data(data)
self.call_js_method(
"addIconLayer",
id=layer_id,
data=processed_data,
getPosition=get_position,
getIcon=get_icon,
getSize=get_size,
getColor=get_color or [255, 255, 255, 255],
iconAtlas=icon_atlas,
iconMapping=icon_mapping,
pickable=pickable,
opacity=opacity,
**kwargs,
)
self._layers = {**self._layers, layer_id: {"id": layer_id, "type": "icon"}}
self._add_to_layer_dict(layer_id, "Deck.gl")
def add_text_layer(
self,
data: Any,
name: Optional[str] = None,
get_position: Union[str, Any] = "coordinates",
get_text: Union[str, Any] = "text",
get_size: Union[float, str] = 12,
get_color: Optional[Union[List[int], str]] = None,
get_angle: Union[float, str] = 0,
text_anchor: str = "middle",
alignment_baseline: str = "center",
pickable: bool = True,
opacity: float = 1,
**kwargs,
) -> None:
"""Add a text layer using deck.gl."""
layer_id = name or f"text-{len(self._layers)}"
processed_data = self._process_deck_data(data)
self.call_js_method(
"addTextLayer",
id=layer_id,
data=processed_data,
getPosition=get_position,
getText=get_text,
getSize=get_size,
getColor=get_color or [0, 0, 0, 255],
getAngle=get_angle,
getTextAnchor=text_anchor,
getAlignmentBaseline=alignment_baseline,
pickable=pickable,
opacity=opacity,
**kwargs,
)
self._layers = {**self._layers, layer_id: {"id": layer_id, "type": "text"}}
self._add_to_layer_dict(layer_id, "Deck.gl")
def add_geojson_layer(
self,
data: Any,
name: Optional[str] = None,
get_fill_color: Optional[Union[List[int], str]] = None,
get_line_color: Optional[Union[List[int], str]] = None,
get_line_width: Union[float, str] = 1,
get_point_radius: Union[float, str] = 5,
get_elevation: Union[float, str] = 0,
extruded: bool = False,
wireframe: bool = False,
filled: bool = True,
stroked: bool = True,
line_width_min_pixels: float = 1,
point_radius_min_pixels: float = 2,
pickable: bool = True,
opacity: float = 0.8,
**kwargs,
) -> None:
"""Add a GeoJSON layer with auto-styling using deck.gl."""
layer_id = name or f"geojson-deck-{len(self._layers)}"
processed_data = self._process_deck_data(data)
self.call_js_method(
"addGeoJsonLayer",
id=layer_id,
data=processed_data,
getFillColor=get_fill_color or [51, 136, 255, 128],
getLineColor=get_line_color or [0, 0, 0, 255],
getLineWidth=get_line_width,
getPointRadius=get_point_radius,
getElevation=get_elevation,
extruded=extruded,
wireframe=wireframe,
filled=filled,
stroked=stroked,
lineWidthMinPixels=line_width_min_pixels,
pointRadiusMinPixels=point_radius_min_pixels,
pickable=pickable,
opacity=opacity,
**kwargs,
)
self._layers = {
**self._layers,
layer_id: {"id": layer_id, "type": "geojson-deck"},
}
self._add_to_layer_dict(layer_id, "Deck.gl")
def add_contour_layer(
self,
data: Any,
name: Optional[str] = None,
get_position: Union[str, Any] = "coordinates",
get_weight: Union[float, str] = 1,
cell_size: float = 200,
contours: Optional[List[Dict]] = None,
pickable: bool = True,
opacity: float = 1,
**kwargs,
) -> None:
"""Add a contour layer using deck.gl."""
layer_id = name or f"contour-{len(self._layers)}"
processed_data = self._process_deck_data(data)
default_contours = [
{"threshold": 1, "color": [255, 255, 255], "strokeWidth": 1},
{"threshold": 5, "color": [51, 136, 255], "strokeWidth": 2},
{"threshold": 10, "color": [0, 0, 255], "strokeWidth": 3},
]
self.call_js_method(
"addContourLayer",
id=layer_id,
data=processed_data,
getPosition=get_position,
getWeight=get_weight,
cellSize=cell_size,
contours=contours or default_contours,
pickable=pickable,
opacity=opacity,
**kwargs,
)
self._layers = {**self._layers, layer_id: {"id": layer_id, "type": "contour"}}
self._add_to_layer_dict(layer_id, "Deck.gl")
def add_screen_grid_layer(
self,
data: Any,
name: Optional[str] = None,
get_position: Union[str, Any] = "coordinates",
get_weight: Union[float, str] = 1,
cell_size_pixels: float = 50,
color_range: Optional[List[List[int]]] = None,
pickable: bool = True,
opacity: float = 0.8,
**kwargs,
) -> None:
"""Add a screen grid layer using deck.gl."""
layer_id = name or f"screengrid-{len(self._layers)}"
processed_data = self._process_deck_data(data)
default_color_range = [
[255, 255, 178, 25],
[254, 217, 118, 85],
[254, 178, 76, 127],
[253, 141, 60, 170],
[240, 59, 32, 212],
[189, 0, 38, 255],
]
self.call_js_method(
"addScreenGridLayer",
id=layer_id,
data=processed_data,
getPosition=get_position,
getWeight=get_weight,
cellSizePixels=cell_size_pixels,
colorRange=color_range or default_color_range,
pickable=pickable,
opacity=opacity,
**kwargs,
)
self._layers = {
**self._layers,
layer_id: {"id": layer_id, "type": "screengrid"},
}
self._add_to_layer_dict(layer_id, "Deck.gl")
def add_trips_layer(
self,
data: Any,
name: Optional[str] = None,
get_path: Union[str, Any] = "waypoints",
get_timestamps: Union[str, Any] = "timestamps",
get_color: Optional[Union[List[int], str]] = None,
width_min_pixels: float = 2,
trail_length: float = 180,
current_time: float = 0,
pickable: bool = True,
opacity: float = 0.8,
**kwargs,
) -> None:
"""Add a trips layer using deck.gl."""
layer_id = name or f"trips-{len(self._layers)}"
processed_data = self._process_deck_data(data)
self.call_js_method(
"addTripsLayer",
id=layer_id,
data=processed_data,
getPath=get_path,
getTimestamps=get_timestamps,
getColor=get_color or [253, 128, 93],
widthMinPixels=width_min_pixels,
trailLength=trail_length,
currentTime=current_time,
pickable=pickable,
opacity=opacity,
**kwargs,
)
self._layers = {**self._layers, layer_id: {"id": layer_id, "type": "trips"}}
self._add_to_layer_dict(layer_id, "Deck.gl")
def add_line_layer(
self,
data: Any,
name: Optional[str] = None,
get_source_position: Union[str, Any] = "sourcePosition",
get_target_position: Union[str, Any] = "targetPosition",
get_color: Optional[Union[List[int], str]] = None,
get_width: Union[float, str] = 1,
width_min_pixels: float = 1,
pickable: bool = True,
opacity: float = 0.8,
**kwargs,
) -> None:
"""Add a line layer using deck.gl."""
layer_id = name or f"line-{len(self._layers)}"
processed_data = self._process_deck_data(data)
self.call_js_method(
"addLineLayer",
id=layer_id,
data=processed_data,
getSourcePosition=get_source_position,
getTargetPosition=get_target_position,
getColor=get_color or [51, 136, 255, 200],
getWidth=get_width,
widthMinPixels=width_min_pixels,
pickable=pickable,
opacity=opacity,
**kwargs,
)
self._layers = {**self._layers, layer_id: {"id": layer_id, "type": "line"}}
self._add_to_layer_dict(layer_id, "Deck.gl")
def add_deckgl_layer(
self,
layer_type: str,
data: Any,
name: Optional[str] = None,
**kwargs,
) -> None:
"""Add a generic deck.gl layer to the map."""
layer_type_clean = layer_type.replace("Layer", "")
prefix = layer_type_clean.lower()
layer_id = name or f"{prefix}-{len(self._layers)}"
processed_data = self._process_deck_data(data)
self.call_js_method(
"addDeckGLLayer",
layerType=layer_type,
id=layer_id,
data=processed_data,
**kwargs,
)
self._layers = {**self._layers, layer_id: {"id": layer_id, "type": layer_type}}
self._add_to_layer_dict(layer_id, "Deck.gl")
def remove_deck_layer(self, layer_id: str) -> None:
"""Remove a deck.gl layer from the map."""
self._remove_layer_internal(layer_id, "removeDeckLayer")
def add_column_layer(
self,
data: Any,
name: Optional[str] = None,
get_position: Union[str, Any] = "coordinates",
get_fill_color: Optional[Union[List[int], str]] = None,
get_line_color: Optional[Union[List[int], str]] = None,
get_elevation: Union[float, str] = 1000,
radius: float = 1000,
disk_resolution: int = 20,
elevation_scale: float = 1,
coverage: float = 1,
extruded: bool = True,
filled: bool = True,
stroked: bool = False,
wireframe: bool = False,
pickable: bool = True,
opacity: float = 0.8,
**kwargs,
) -> None:
"""Add a column layer using deck.gl."""
layer_id = name or f"column-{len(self._layers)}"
processed_data = self._process_deck_data(data)
self.call_js_method(
"addColumnLayer",
id=layer_id,
data=processed_data,
getPosition=get_position,
getFillColor=get_fill_color or [255, 140, 0, 200],
getLineColor=get_line_color or [0, 0, 0, 255],
getElevation=get_elevation,
radius=radius,
diskResolution=disk_resolution,
elevationScale=elevation_scale,
coverage=coverage,
extruded=extruded,
filled=filled,
stroked=stroked,
wireframe=wireframe,
pickable=pickable,
opacity=opacity,
**kwargs,
)
self._layers = {**self._layers, layer_id: {"id": layer_id, "type": "column"}}
self._add_to_layer_dict(layer_id, "Deck.gl")
def add_bitmap_layer(
self,
image: str,
bounds: List[float],
name: Optional[str] = None,
opacity: float = 1.0,
visible: bool = True,
pickable: bool = False,
desaturate: float = 0,
transparent_color: Optional[List[int]] = None,
tint_color: Optional[List[int]] = None,
**kwargs,
) -> None:
"""Add a bitmap layer using deck.gl."""
layer_id = name or f"bitmap-{len(self._layers)}"
self.call_js_method(
"addBitmapLayer",
id=layer_id,
image=image,
bounds=bounds,
opacity=opacity,
visible=visible,
pickable=pickable,
desaturate=desaturate,
transparentColor=transparent_color or [0, 0, 0, 0],
tintColor=tint_color or [255, 255, 255],
**kwargs,
)
self._layers = {**self._layers, layer_id: {"id": layer_id, "type": "bitmap"}}
self._add_to_layer_dict(layer_id, "Deck.gl")
def add_solid_polygon_layer(
self,
data: Any,
name: Optional[str] = None,
get_polygon: Union[str, Any] = "polygon",
get_fill_color: Optional[Union[List[int], str]] = None,
get_line_color: Optional[Union[List[int], str]] = None,
get_elevation: Union[float, str] = 0,
filled: bool = True,
extruded: bool = False,
wireframe: bool = False,
elevation_scale: float = 1,
pickable: bool = True,
opacity: float = 0.8,
**kwargs,
) -> None:
"""Add a solid polygon layer using deck.gl."""
layer_id = name or f"solidpolygon-{len(self._layers)}"
processed_data = self._process_deck_data(data)
self.call_js_method(
"addSolidPolygonLayer",
id=layer_id,
data=processed_data,
getPolygon=get_polygon,
getFillColor=get_fill_color or [51, 136, 255, 128],
getLineColor=get_line_color or [0, 0, 0, 255],
getElevation=get_elevation,
filled=filled,
extruded=extruded,
wireframe=wireframe,
elevationScale=elevation_scale,
pickable=pickable,
opacity=opacity,
**kwargs,
)
self._layers = {
**self._layers,
layer_id: {"id": layer_id, "type": "solidpolygon"},
}
self._add_to_layer_dict(layer_id, "Deck.gl")
def add_grid_cell_layer(
self,
data: Any,
name: Optional[str] = None,
get_position: Union[str, Any] = "coordinates",
get_color: Optional[Union[List[int], str]] = None,
get_elevation: Union[float, str] = 1000,
cell_size: float = 200,
coverage: float = 1,
elevation_scale: float = 1,
extruded: bool = True,
pickable: bool = True,
opacity: float = 0.8,
**kwargs,
) -> None:
"""Add a grid cell layer using deck.gl."""
layer_id = name or f"gridcell-{len(self._layers)}"
processed_data = self._process_deck_data(data)
self.call_js_method(
"addGridCellLayer",
id=layer_id,
data=processed_data,
getPosition=get_position,
getColor=get_color or [255, 140, 0, 200],
getElevation=get_elevation,
cellSize=cell_size,
coverage=coverage,
elevationScale=elevation_scale,
extruded=extruded,
pickable=pickable,
opacity=opacity,
**kwargs,
)
self._layers = {
**self._layers,
layer_id: {"id": layer_id, "type": "gridcell"},
}
self._add_to_layer_dict(layer_id, "Deck.gl")
# -------------------------------------------------------------------------
# LiDAR Layers (maplibre-gl-lidar)
# -------------------------------------------------------------------------
def add_lidar_control(
self,
position: str = "top-right",
collapsed: bool = True,
title: str = "LiDAR Viewer",
point_size: float = 2,
opacity: float = 1.0,
color_scheme: str = "elevation",
use_percentile: bool = True,
point_budget: int = 1000000,
pickable: bool = False,
auto_zoom: bool = True,
copc_loading_mode: Optional[str] = None,
streaming_point_budget: int = 5000000,
**kwargs,
) -> None:
"""Add an interactive LiDAR control panel.
The LiDAR control provides a UI panel for loading, visualizing, and
styling LiDAR point cloud files (LAS, LAZ, COPC formats).
Args:
position: Control position ('top-left', 'top-right', 'bottom-left', 'bottom-right').
collapsed: Whether the panel starts collapsed.
title: Title displayed on the panel.
point_size: Point size in pixels.
opacity: Layer opacity (0-1).
color_scheme: Color scheme ('elevation', 'intensity', 'classification', 'rgb').
use_percentile: Use 2-98% percentile for color scaling.
point_budget: Maximum number of points to display.
pickable: Enable hover/click interactions.
auto_zoom: Auto-zoom to point cloud after loading.
copc_loading_mode: COPC loading mode ('full' or 'dynamic').
streaming_point_budget: Point budget for streaming mode.
**kwargs: Additional control options.
Example:
>>> from anymap_ts import MapboxMap
>>> m = MapboxMap(pitch=60)
>>> m.add_lidar_control(color_scheme="classification", pickable=True)
"""
self.call_js_method(
"addLidarControl",
position=position,
collapsed=collapsed,
title=title,
pointSize=point_size,
opacity=opacity,
colorScheme=color_scheme,
usePercentile=use_percentile,
pointBudget=point_budget,
pickable=pickable,
autoZoom=auto_zoom,
copcLoadingMode=copc_loading_mode,
streamingPointBudget=streaming_point_budget,
**kwargs,
)
self._controls = {
**self._controls,
"lidar-control": {"position": position, "collapsed": collapsed},
}
def add_lidar_layer(
self,
source: Union[str, Path],
name: Optional[str] = None,
color_scheme: str = "elevation",
point_size: float = 2,
opacity: float = 1.0,
pickable: bool = True,
auto_zoom: bool = True,
streaming_mode: bool = True,
point_budget: int = 1000000,
**kwargs,
) -> None:
"""Load and display a LiDAR file from URL or local path.
Supports LAS, LAZ, and COPC (Cloud-Optimized Point Cloud) formats.
For local files, the file is read and sent as base64 to JavaScript.
For URLs, the data is loaded directly via streaming when possible.
Args:
source: URL or local file path to the LiDAR file.
name: Layer identifier. If None, auto-generated.
color_scheme: Color scheme ('elevation', 'intensity', 'classification', 'rgb').
point_size: Point size in pixels.
opacity: Layer opacity (0-1).
pickable: Enable hover/click interactions.
auto_zoom: Auto-zoom to point cloud after loading.
streaming_mode: Use streaming mode for large COPC files.
point_budget: Maximum number of points to display.
**kwargs: Additional layer options.
Example:
>>> from anymap_ts import MapboxMap
>>> m = MapboxMap(center=[-123.07, 44.05], zoom=14, pitch=60)
>>> m.add_lidar_layer(
... source="https://s3.amazonaws.com/hobu-lidar/autzen-classified.copc.laz",
... name="autzen",
... color_scheme="classification",
... )
"""
import base64
layer_id = name or f"lidar-{len(self._layers)}"
# Check if source is a local file
source_path = Path(source) if isinstance(source, (str, Path)) else None
is_local = source_path is not None and source_path.exists()
if is_local:
# Read local file and encode as base64
with open(source_path, "rb") as f:
file_data = f.read()
source_b64 = base64.b64encode(file_data).decode("utf-8")
self.call_js_method(
"addLidarLayer",
source=source_b64,
name=layer_id,
isBase64=True,
filename=source_path.name,
colorScheme=color_scheme,
pointSize=point_size,
opacity=opacity,
pickable=pickable,
autoZoom=auto_zoom,
streamingMode=streaming_mode,
pointBudget=point_budget,
**kwargs,
)
else:
# Load from URL
self.call_js_method(
"addLidarLayer",
source=str(source),
name=layer_id,
isBase64=False,
colorScheme=color_scheme,
pointSize=point_size,
opacity=opacity,
pickable=pickable,
autoZoom=auto_zoom,
streamingMode=streaming_mode,
pointBudget=point_budget,
**kwargs,
)
self._layers = {
**self._layers,
layer_id: {
"id": layer_id,
"type": "lidar",
"source": str(source),
},
}
def remove_lidar_layer(self, layer_id: Optional[str] = None) -> None:
"""Remove a LiDAR layer.
Args:
layer_id: Layer identifier to remove. If None, removes all LiDAR layers.
"""
if layer_id:
if layer_id in self._layers:
layers = dict(self._layers)
del layers[layer_id]
self._layers = layers
self.call_js_method("removeLidarLayer", id=layer_id)
else:
# Remove all lidar layers
layers = dict(self._layers)
self._layers = {k: v for k, v in layers.items() if v.get("type") != "lidar"}
self.call_js_method("removeLidarLayer")
def set_lidar_color_scheme(self, color_scheme: str) -> None:
"""Set the LiDAR color scheme.
Args:
color_scheme: Color scheme ('elevation', 'intensity', 'classification', 'rgb').
"""
self.call_js_method("setLidarColorScheme", colorScheme=color_scheme)
def set_lidar_point_size(self, point_size: float) -> None:
"""Set the LiDAR point size.
Args:
point_size: Point size in pixels.
"""
self.call_js_method("setLidarPointSize", pointSize=point_size)
def set_lidar_opacity(self, opacity: float) -> None:
"""Set the LiDAR layer opacity.
Args:
opacity: Opacity value between 0 and 1.
"""
self.call_js_method("setLidarOpacity", opacity=opacity)
def _process_deck_data(self, data: Any) -> Any:
"""Process data for deck.gl layers.
Handles GeoDataFrame, file paths, GeoJSON, and list of dicts.
Args:
data: Input data in various formats.
Returns:
Processed data suitable for deck.gl layers.
"""
# Handle GeoDataFrame
if hasattr(data, "__geo_interface__"):
return data.__geo_interface__
# Handle file paths
if isinstance(data, (str, Path)):
path = Path(data)
if path.exists():
try:
import geopandas as gpd
gdf = gpd.read_file(path)
return gdf.__geo_interface__
except ImportError:
pass
# Return as-is for lists, dicts, etc.
return data
# -------------------------------------------------------------------------
# Terrain Methods (Mapbox-specific)
# -------------------------------------------------------------------------
def add_terrain(
self, exaggeration: float = 1.0, source: str = "mapbox-dem"
) -> None:
"""Add 3D terrain to the map.
Args:
exaggeration: Terrain exaggeration factor.
source: Terrain source ID.
"""
self.call_js_method("addTerrain", source=source, exaggeration=exaggeration)
def remove_terrain(self) -> None:
"""Remove 3D terrain from the map."""
self.call_js_method("removeTerrain")
def add_3d_terrain(
self, exaggeration: float = 1.0, source: str = "mapbox-dem", **kwargs
) -> None:
"""Alias for add_terrain for MapLibre compatibility."""
self.add_terrain(exaggeration=exaggeration, source=source)
# -------------------------------------------------------------------------
# Layer Management
# -------------------------------------------------------------------------
def add_layer(
self,
layer_id: str,
layer_type: str,
source: Union[str, Dict],
paint: Optional[Dict] = None,
layout: Optional[Dict] = None,
before_id: Optional[str] = None,
**kwargs,
) -> None:
"""Add a generic layer to the map.
Args:
layer_id: Unique layer identifier.
layer_type: Mapbox layer type.
source: Source ID or source configuration dict.
paint: Paint properties.
layout: Layout properties.
before_id: ID of layer to insert before.
**kwargs: Additional layer options.
"""
layer_config = {
"id": layer_id,
"type": layer_type,
"paint": paint or {},
"layout": layout or {},
**kwargs,
}
if isinstance(source, str):
layer_config["source"] = source
else:
source_id = f"{layer_id}-source"
self._sources = {**self._sources, source_id: source}
self.call_js_method("addSource", source_id, **source)
layer_config["source"] = source_id
self._layers = {**self._layers, layer_id: layer_config}
self.call_js_method("addLayer", beforeId=before_id, **layer_config)
lt = layer_config.get("type", "")
self._add_to_layer_dict(layer_id, "Raster" if lt == "raster" else "Vector")
def remove_layer(self, layer_id: str) -> None:
"""Remove a layer from the map."""
if layer_id in self._layers:
layers = dict(self._layers)
del layers[layer_id]
self._layers = layers
self._remove_from_layer_dict(layer_id)
self.call_js_method("removeLayer", layer_id)
def set_visibility(self, layer_id: str, visible: bool) -> None:
"""Set layer visibility.
Args:
layer_id: Layer identifier.
visible: Whether layer should be visible.
"""
self.call_js_method("setVisibility", layer_id, visible)
def set_opacity(self, layer_id: str, opacity: float) -> None:
"""Set layer opacity."""
self._validate_opacity(opacity)
self.call_js_method("setOpacity", layer_id, opacity)
def set_paint_property(self, layer_id: str, property_name: str, value: Any) -> None:
"""Set a paint property for a layer."""
self.call_js_method("setPaintProperty", layer_id, property_name, value)
def set_layout_property(
self, layer_id: str, property_name: str, value: Any
) -> None:
"""Set a layout property for a layer."""
self.call_js_method("setLayoutProperty", layer_id, property_name, value)
def move_layer(self, layer_id: str, before_id: Optional[str] = None) -> None:
"""Move a layer in the layer stack."""
self.call_js_method("moveLayer", layer_id, before_id)
def get_layer(self, layer_id: str) -> Optional[Dict]:
"""Get layer configuration by ID."""
return self._layers.get(layer_id)
def get_layer_ids(self) -> List[str]:
"""Get list of all layer IDs."""
return list(self._layers.keys())
def add_popup(
self,
layer_id: str,
properties: Optional[List[str]] = None,
template: Optional[str] = None,
**kwargs,
) -> None:
"""Add popup on click for a layer."""
self.call_js_method(
"addPopup",
layerId=layer_id,
properties=properties,
template=template,
**kwargs,
)
# -------------------------------------------------------------------------
# Controls
# -------------------------------------------------------------------------
def add_control(
self,
control_type: str,
position: str = "top-right",
**kwargs,
) -> None:
"""Add a map control.
Args:
control_type: Type of control ('navigation', 'scale', 'fullscreen', etc.).
position: Control position.
**kwargs: Control-specific options.
"""
self.call_js_method("addControl", control_type, position=position, **kwargs)
self._controls = {
**self._controls,
control_type: {"type": control_type, "position": position, **kwargs},
}
def remove_control(self, control_type: str) -> None:
"""Remove a map control."""
self.call_js_method("removeControl", control_type)
if control_type in self._controls:
controls = dict(self._controls)
del controls[control_type]
self._controls = controls
def add_layer_control(
self,
layers: Optional[List[str]] = None,
position: str = "top-right",
collapsed: bool = True,
) -> None:
"""Add a layer visibility control."""
if layers is None:
layers = list(self._layers.keys())
self.call_js_method(
"addLayerControl",
layers=layers,
position=position,
collapsed=collapsed,
)
self._controls = {
**self._controls,
"layer-control": {
"layers": layers,
"position": position,
"collapsed": collapsed,
},
}
def add_colorbar(
self,
colormap: str = "viridis",
vmin: float = 0,
vmax: float = 1,
label: str = "",
units: str = "",
orientation: str = "horizontal",
position: str = "bottom-right",
bar_thickness: Optional[int] = None,
bar_length: Optional[int] = None,
ticks: Optional[Dict] = None,
opacity: Optional[float] = None,
colorbar_id: Optional[str] = None,
**kwargs,
) -> None:
"""Add a continuous gradient colorbar to the map."""
self._validate_position(position)
cbar_id = (
colorbar_id
or f"colorbar-{len([k for k in self._controls.keys() if k.startswith('colorbar')])}"
)
js_kwargs: Dict[str, Any] = {
"colormap": colormap,
"vmin": vmin,
"vmax": vmax,
"label": label,
"units": units,
"orientation": orientation,
"position": position,
"colorbarId": cbar_id,
**kwargs,
}
if bar_thickness is not None:
js_kwargs["barThickness"] = bar_thickness
if bar_length is not None:
js_kwargs["barLength"] = bar_length
if ticks is not None:
js_kwargs["ticks"] = ticks
if opacity is not None:
js_kwargs["opacity"] = opacity
self.call_js_method("addColorbar", **js_kwargs)
self._controls = {
**self._controls,
cbar_id: {
"type": "colorbar",
"colormap": colormap,
"vmin": vmin,
"vmax": vmax,
"label": label,
"units": units,
"orientation": orientation,
"position": position,
},
}
def remove_colorbar(self, colorbar_id: Optional[str] = None) -> None:
"""Remove a colorbar from the map."""
if colorbar_id is None:
cbar_keys = [k for k in self._controls.keys() if k.startswith("colorbar")]
for key in cbar_keys:
self.call_js_method("removeColorbar", colorbarId=key)
self._controls = {
k: v for k, v in self._controls.items() if not k.startswith("colorbar")
}
else:
self.call_js_method("removeColorbar", colorbarId=colorbar_id)
if colorbar_id in self._controls:
controls = dict(self._controls)
del controls[colorbar_id]
self._controls = controls
def update_colorbar(self, colorbar_id: Optional[str] = None, **kwargs) -> None:
"""Update an existing colorbar's properties."""
if colorbar_id is None:
cbar_keys = [k for k in self._controls.keys() if k.startswith("colorbar")]
if not cbar_keys:
raise ValueError("No colorbar found to update")
colorbar_id = cbar_keys[0]
if colorbar_id not in self._controls:
raise ValueError(f"Colorbar '{colorbar_id}' not found")
js_kwargs: Dict[str, Any] = {"colorbarId": colorbar_id}
key_map = {"bar_thickness": "barThickness", "bar_length": "barLength"}
for key, value in kwargs.items():
js_key = key_map.get(key, key)
js_kwargs[js_key] = value
self.call_js_method("updateColorbar", **js_kwargs)
def add_search_control(
self,
position: str = "top-left",
placeholder: str = "Search places...",
collapsed: bool = True,
fly_to_zoom: int = 14,
show_marker: bool = True,
marker_color: str = "#4264fb",
**kwargs,
) -> None:
"""Add a search/geocoder control."""
self._validate_position(position)
self.call_js_method(
"addSearchControl",
position=position,
placeholder=placeholder,
collapsed=collapsed,
flyToZoom=fly_to_zoom,
showMarker=show_marker,
markerColor=marker_color,
**kwargs,
)
self._controls = {
**self._controls,
"search-control": {
"type": "search-control",
"position": position,
"collapsed": collapsed,
},
}
def remove_search_control(self) -> None:
"""Remove the search/geocoder control."""
self.call_js_method("removeSearchControl")
if "search-control" in self._controls:
controls = dict(self._controls)
del controls["search-control"]
self._controls = controls
def add_measure_control(
self,
position: str = "top-right",
collapsed: bool = True,
default_mode: str = "distance",
distance_unit: str = "kilometers",
area_unit: str = "square-kilometers",
line_color: str = "#3b82f6",
fill_color: str = "rgba(59, 130, 246, 0.2)",
**kwargs,
) -> None:
"""Add a measurement control."""
self._validate_position(position)
self.call_js_method(
"addMeasureControl",
position=position,
collapsed=collapsed,
defaultMode=default_mode,
distanceUnit=distance_unit,
areaUnit=area_unit,
lineColor=line_color,
fillColor=fill_color,
**kwargs,
)
self._controls = {
**self._controls,
"measure-control": {
"type": "measure-control",
"position": position,
"collapsed": collapsed,
},
}
def remove_measure_control(self) -> None:
"""Remove the measurement control."""
self.call_js_method("removeMeasureControl")
if "measure-control" in self._controls:
controls = dict(self._controls)
del controls["measure-control"]
self._controls = controls
def add_print_control(
self,
position: str = "top-right",
collapsed: bool = True,
format: str = "png",
filename: str = "map-export",
include_north_arrow: bool = False,
include_scale_bar: bool = False,
**kwargs,
) -> None:
"""Add a print/export control."""
self._validate_position(position)
self.call_js_method(
"addPrintControl",
position=position,
collapsed=collapsed,
format=format,
filename=filename,
includeNorthArrow=include_north_arrow,
includeScaleBar=include_scale_bar,
**kwargs,
)
self._controls = {
**self._controls,
"print-control": {
"type": "print-control",
"position": position,
"collapsed": collapsed,
},
}
def remove_print_control(self) -> None:
"""Remove the print/export control."""
self.call_js_method("removePrintControl")
if "print-control" in self._controls:
controls = dict(self._controls)
del controls["print-control"]
self._controls = controls
def add_coordinates_control(
self,
position: str = "bottom-left",
precision: int = 4,
) -> None:
"""Add a coordinates display control."""
self.call_js_method(
"addCoordinatesControl",
position=position,
precision=precision,
)
def remove_coordinates_control(self) -> None:
"""Remove the coordinates display control."""
self.call_js_method("removeCoordinatesControl")
def add_time_slider(
self,
layer_id: str,
property: str,
min_value: float = 0,
max_value: float = 100,
step: float = 1,
position: str = "bottom-left",
label: str = "Time",
auto_play: bool = False,
interval: int = 500,
) -> None:
"""Add a time slider to filter data by a temporal property."""
self.call_js_method(
"addTimeSlider",
layerId=layer_id,
property=property,
min=min_value,
max=max_value,
step=step,
position=position,
label=label,
autoPlay=auto_play,
interval=interval,
)
def remove_time_slider(self) -> None:
"""Remove the time slider control."""
self.call_js_method("removeTimeSlider")
def add_opacity_slider(
self,
layer_id: str,
position: str = "top-right",
label: Optional[str] = None,
) -> None:
"""Add a UI slider to control layer opacity."""
self.call_js_method(
"addOpacitySlider",
layerId=layer_id,
position=position,
label=label or layer_id,
)
def remove_opacity_slider(self, layer_id: str) -> None:
"""Remove the opacity slider for a layer."""
self.call_js_method("removeOpacitySlider", layerId=layer_id)
def add_style_switcher(
self,
styles: Dict[str, str],
position: str = "top-right",
) -> None:
"""Add a dropdown to switch between map styles."""
self.call_js_method(
"addStyleSwitcher",
styles=styles,
position=position,
)
def remove_style_switcher(self) -> None:
"""Remove the style switcher control."""
self.call_js_method("removeStyleSwitcher")
def add_pmtiles_control(
self,
position: str = "top-right",
collapsed: bool = True,
default_url: Optional[str] = None,
load_default_url: bool = False,
default_opacity: float = 1.0,
default_fill_color: str = "steelblue",
default_line_color: str = "#333",
default_pickable: bool = True,
**kwargs,
) -> None:
"""Add a PMTiles layer control."""
self.call_js_method(
"addPMTilesControl",
position=position,
collapsed=collapsed,
defaultUrl=default_url or "",
loadDefaultUrl=load_default_url,
defaultOpacity=default_opacity,
defaultFillColor=default_fill_color,
defaultLineColor=default_line_color,
defaultPickable=default_pickable,
**kwargs,
)
self._controls = {
**self._controls,
"pmtiles-control": {"position": position, "collapsed": collapsed},
}
def add_cog_control(
self,
position: str = "top-right",
collapsed: bool = True,
default_url: Optional[str] = None,
load_default_url: bool = False,
default_opacity: float = 1.0,
default_colormap: str = "viridis",
default_bands: str = "1",
default_rescale_min: float = 0,
default_rescale_max: float = 255,
**kwargs,
) -> None:
"""Add a COG layer control."""
self.call_js_method(
"addCogControl",
position=position,
collapsed=collapsed,
defaultUrl=default_url or "",
loadDefaultUrl=load_default_url,
defaultOpacity=default_opacity,
defaultColormap=default_colormap,
defaultBands=default_bands,
defaultRescaleMin=default_rescale_min,
defaultRescaleMax=default_rescale_max,
**kwargs,
)
self._controls = {
**self._controls,
"cog-control": {"position": position, "collapsed": collapsed},
}
def add_zarr_control(
self,
position: str = "top-right",
collapsed: bool = True,
default_url: Optional[str] = None,
load_default_url: bool = False,
default_opacity: float = 1.0,
default_variable: str = "",
default_clim: Optional[Tuple[float, float]] = None,
**kwargs,
) -> None:
"""Add a Zarr layer control."""
self.call_js_method(
"addZarrControl",
position=position,
collapsed=collapsed,
defaultUrl=default_url or "",
loadDefaultUrl=load_default_url,
defaultOpacity=default_opacity,
defaultVariable=default_variable,
defaultClim=list(default_clim) if default_clim else [0, 1],
**kwargs,
)
self._controls = {
**self._controls,
"zarr-control": {"position": position, "collapsed": collapsed},
}
def add_vector_control(
self,
position: str = "top-right",
collapsed: bool = True,
default_url: Optional[str] = None,
load_default_url: bool = False,
default_opacity: float = 1.0,
default_fill_color: str = "#3388ff",
default_stroke_color: str = "#3388ff",
fit_bounds: bool = True,
**kwargs,
) -> None:
"""Add a vector layer control."""
self.call_js_method(
"addVectorControl",
position=position,
collapsed=collapsed,
defaultUrl=default_url or "",
loadDefaultUrl=load_default_url,
defaultOpacity=default_opacity,
defaultFillColor=default_fill_color,
defaultStrokeColor=default_stroke_color,
fitBounds=fit_bounds,
**kwargs,
)
self._controls = {
**self._controls,
"vector-control": {"position": position, "collapsed": collapsed},
}
def add_control_grid(
self,
position: str = "top-right",
default_controls: Optional[List[str]] = None,
exclude: Optional[List[str]] = None,
rows: Optional[int] = None,
columns: Optional[int] = None,
collapsed: bool = True,
collapsible: bool = True,
title: str = "",
show_row_column_controls: bool = True,
gap: int = 2,
basemap_style_url: Optional[str] = None,
exclude_layers: Optional[List[str]] = None,
**kwargs,
) -> None:
"""Add a ControlGrid with all default tools or a custom subset."""
js_kwargs: Dict[str, Any] = {
"position": position,
"collapsed": collapsed,
"collapsible": collapsible,
"showRowColumnControls": show_row_column_controls,
"gap": gap,
**kwargs,
}
if default_controls is not None:
js_kwargs["defaultControls"] = default_controls
if exclude is not None:
js_kwargs["exclude"] = exclude
if rows is not None:
js_kwargs["rows"] = rows
if columns is not None:
js_kwargs["columns"] = columns
if title:
js_kwargs["title"] = title
if basemap_style_url is not None:
js_kwargs["basemapStyleUrl"] = basemap_style_url
if exclude_layers is not None:
js_kwargs["excludeLayers"] = exclude_layers
self.call_js_method("addControlGrid", **js_kwargs)
self._controls = {
**self._controls,
"control-grid": {
"position": position,
"collapsed": collapsed,
"collapsible": collapsible,
},
}
def add_legend(
self,
title: str,
labels: List[str],
colors: List[str],
position: str = "bottom-right",
opacity: float = 1.0,
legend_id: Optional[str] = None,
**kwargs,
) -> None:
"""Add a floating legend control to the map."""
if len(labels) != len(colors):
raise ValueError("Number of labels must match number of colors")
self._validate_position(position)
for i, color in enumerate(colors):
if not isinstance(color, str) or not color.startswith("#"):
raise ValueError(
f"Color at index {i} must be a hex color string (e.g., '#ff0000')"
)
legend_id = (
legend_id
or f"legend-{len([k for k in self._controls.keys() if k.startswith('legend')])}"
)
legend_items = [
{"label": label, "color": color} for label, color in zip(labels, colors)
]
self.call_js_method(
"addLegend",
id=legend_id,
title=title,
items=legend_items,
position=position,
opacity=opacity,
**kwargs,
)
self._controls = {
**self._controls,
legend_id: {
"type": "legend",
"title": title,
"labels": labels,
"colors": colors,
"position": position,
"opacity": opacity,
},
}
def remove_legend(self, legend_id: Optional[str] = None) -> None:
"""Remove a legend control from the map."""
if legend_id is None:
legend_keys = [k for k in self._controls.keys() if k.startswith("legend")]
for key in legend_keys:
self.call_js_method("removeLegend", key)
self._controls = {
k: v for k, v in self._controls.items() if not k.startswith("legend")
}
else:
self.call_js_method("removeLegend", legend_id)
if legend_id in self._controls:
controls = dict(self._controls)
del controls[legend_id]
self._controls = controls
def update_legend(
self,
legend_id: str,
title: Optional[str] = None,
labels: Optional[List[str]] = None,
colors: Optional[List[str]] = None,
opacity: Optional[float] = None,
**kwargs,
) -> None:
"""Update an existing legend's properties."""
if legend_id not in self._controls:
raise ValueError(f"Legend '{legend_id}' not found")
update_params = {"id": legend_id}
if title is not None:
update_params["title"] = title
self._controls[legend_id]["title"] = title
if labels is not None and colors is not None:
if len(labels) != len(colors):
raise ValueError("Number of labels must match number of colors")
legend_items = [
{"label": label, "color": color} for label, color in zip(labels, colors)
]
update_params["items"] = legend_items
self._controls[legend_id]["labels"] = labels
self._controls[legend_id]["colors"] = colors
elif labels is not None or colors is not None:
raise ValueError("Both labels and colors must be provided together")
if opacity is not None:
update_params["opacity"] = opacity
self._controls[legend_id]["opacity"] = opacity
update_params.update(kwargs)
self.call_js_method("updateLegend", **update_params)
def add_tooltip(
self,
layer_id: str,
template: Optional[str] = None,
properties: Optional[List[str]] = None,
) -> None:
"""Add a tooltip that shows on feature hover."""
self.call_js_method(
"addTooltip",
layerId=layer_id,
template=template or "",
properties=properties,
)
def remove_tooltip(self, layer_id: str) -> None:
"""Remove tooltip from a layer."""
self.call_js_method("removeTooltip", layerId=layer_id)
def add_flatgeobuf(
self,
url: str,
name: Optional[str] = None,
layer_type: Optional[str] = None,
paint: Optional[Dict] = None,
fit_bounds: bool = True,
**kwargs,
) -> None:
"""Add a FlatGeobuf layer from a URL."""
layer_id = name or f"flatgeobuf-{len(self._layers)}"
self.call_js_method(
"addFlatGeobuf",
url=url,
name=layer_id,
layerType=layer_type,
paint=paint,
fitBounds=fit_bounds,
**kwargs,
)
self._layers = {
**self._layers,
layer_id: {
"id": layer_id,
"type": "flatgeobuf",
"url": url,
},
}
self._add_to_layer_dict(layer_id, "Vector")
def remove_flatgeobuf(self, name: str) -> None:
"""Remove a FlatGeobuf layer from the map."""
if name in self._layers:
layers = dict(self._layers)
del layers[name]
self._layers = layers
self._remove_from_layer_dict(name)
self.call_js_method("removeFlatGeobuf", name=name)
def add_draw_control(
self,
position: str = "top-right",
draw_modes: Optional[List[str]] = None,
edit_modes: Optional[List[str]] = None,
collapsed: bool = False,
**kwargs,
) -> None:
"""Add a drawing control."""
if draw_modes is None:
draw_modes = ["polygon", "line", "rectangle", "circle", "marker"]
if edit_modes is None:
edit_modes = ["select", "drag", "change", "rotate", "delete"]
self.call_js_method(
"addDrawControl",
position=position,
drawModes=draw_modes,
editModes=edit_modes,
collapsed=collapsed,
**kwargs,
)
self._controls = {
**self._controls,
"draw-control": {
"position": position,
"drawModes": draw_modes,
"editModes": edit_modes,
},
}
def get_draw_data(self) -> Dict:
"""Get the current drawn features as GeoJSON."""
self.call_js_method("getDrawData")
import time
time.sleep(0.1)
return self._draw_data or {"type": "FeatureCollection", "features": []}
@property
def draw_data(self) -> Dict:
"""Property to access current draw data."""
return self._draw_data or {"type": "FeatureCollection", "features": []}
def load_draw_data(self, geojson: Dict) -> None:
"""Load GeoJSON features into the drawing layer."""
self._draw_data = geojson
self.call_js_method("loadDrawData", geojson)
def clear_draw_data(self) -> None:
"""Clear all drawn features."""
self._draw_data = {"type": "FeatureCollection", "features": []}
self.call_js_method("clearDrawData")
def save_draw_data(
self,
filepath: Union[str, Path],
driver: Optional[str] = None,
) -> None:
"""Save drawn features to a file."""
try:
import geopandas as gpd
except ImportError:
raise ImportError(
"geopandas is required to save draw data. "
"Install with: pip install anymap-ts[vector]"
)
data = self.get_draw_data()
if not data.get("features"):
print("No features to save")
return
gdf = gpd.GeoDataFrame.from_features(data["features"])
filepath = Path(filepath)
if driver is None:
ext = filepath.suffix.lower()
driver_map = {
".geojson": "GeoJSON",
".json": "GeoJSON",
".shp": "ESRI Shapefile",
".gpkg": "GPKG",
}
driver = driver_map.get(ext, "GeoJSON")
gdf.to_file(filepath, driver=driver)
def add_cluster_layer(
self,
data: Any,
cluster_radius: int = 50,
cluster_max_zoom: int = 14,
cluster_colors: Optional[List[str]] = None,
cluster_steps: Optional[List[int]] = None,
cluster_min_radius: int = 15,
cluster_max_radius: int = 30,
unclustered_color: str = "#11b4da",
unclustered_radius: int = 8,
show_cluster_count: bool = True,
name: Optional[str] = None,
zoom_on_click: bool = True,
fit_bounds: bool = True,
**kwargs,
) -> str:
"""Add a clustered point layer."""
layer_id = name or f"cluster-{len(self._layers)}"
if cluster_colors is None:
cluster_colors = ["#51bbd6", "#f1f075", "#f28cb1"]
if cluster_steps is None:
cluster_steps = [100, 750]
if len(cluster_steps) != len(cluster_colors) - 1:
raise ValueError(
f"cluster_steps must have {len(cluster_colors) - 1} values "
f"(one less than cluster_colors), got {len(cluster_steps)}"
)
geojson = to_geojson(data)
if geojson.get("type") == "url":
url = geojson["url"]
geojson = fetch_geojson(url)
bounds = get_bounds(geojson) if fit_bounds else None
self.call_js_method(
"addClusterLayer",
data=geojson,
name=layer_id,
clusterRadius=cluster_radius,
clusterMaxZoom=cluster_max_zoom,
clusterColors=cluster_colors,
clusterSteps=cluster_steps,
clusterMinRadius=cluster_min_radius,
clusterMaxRadius=cluster_max_radius,
unclusteredColor=unclustered_color,
unclusteredRadius=unclustered_radius,
showClusterCount=show_cluster_count,
zoomOnClick=zoom_on_click,
fitBounds=fit_bounds,
bounds=bounds,
**kwargs,
)
self._layers = {
**self._layers,
layer_id: {
"id": layer_id,
"type": "cluster",
"source": f"{layer_id}-source",
},
}
self._add_to_layer_dict(layer_id, "Vector")
return layer_id
def remove_cluster_layer(self, layer_id: str) -> None:
"""Remove a cluster layer."""
self._remove_layer_internal(layer_id, "removeClusterLayer")
def add_choropleth(
self,
data: Any,
column: str,
cmap: str = "viridis",
classification: str = "quantile",
k: int = 5,
breaks: Optional[List[float]] = None,
fill_opacity: float = 0.7,
line_color: str = "#000000",
line_width: float = 1,
legend: bool = True,
legend_title: Optional[str] = None,
hover: bool = True,
layer_id: Optional[str] = None,
fit_bounds: bool = True,
**kwargs,
) -> None:
"""Add a choropleth (thematic) map layer."""
from .utils import (
get_choropleth_colors,
compute_breaks,
build_step_expression,
)
layer_name = layer_id or f"choropleth-{len(self._layers)}"
geojson = to_geojson(data)
if geojson.get("type") == "url":
url = geojson["url"]
geojson = fetch_geojson(url)
features = geojson.get("features", [])
values = []
for feature in features:
props = feature.get("properties", {})
val = props.get(column)
if val is not None:
try:
values.append(float(val))
except (TypeError, ValueError):
pass
if not values:
raise ValueError(f"No valid numeric values found for column '{column}'")
computed_breaks = compute_breaks(values, classification, k, breaks)
colors = get_choropleth_colors(cmap, k)
step_expr = build_step_expression(column, computed_breaks, colors)
bounds = get_bounds(geojson) if fit_bounds else None
self.call_js_method(
"addChoropleth",
data=geojson,
name=layer_name,
column=column,
stepExpression=step_expr,
fillOpacity=fill_opacity,
lineColor=line_color,
lineWidth=line_width,
hover=hover,
fitBounds=fit_bounds,
bounds=bounds,
**kwargs,
)
self._layers = {
**self._layers,
layer_name: {
"id": layer_name,
"type": "choropleth",
"source": f"{layer_name}-source",
"column": column,
},
}
self._add_to_layer_dict(layer_name, "Vector")
if legend:
title = legend_title or column
labels = []
for i in range(len(computed_breaks) - 1):
low = computed_breaks[i]
high = computed_breaks[i + 1]
labels.append(f"{low:.1f} - {high:.1f}")
self.add_legend(
title=title,
labels=labels,
colors=colors,
position="bottom-right",
)
def add_3d_buildings(
self,
source: str = "openmaptiles",
min_zoom: float = 14,
fill_extrusion_color: str = "#aaa",
fill_extrusion_opacity: float = 0.6,
height_property: str = "render_height",
base_property: str = "render_min_height",
layer_id: Optional[str] = None,
**kwargs,
) -> None:
"""Add 3D building extrusions from vector tiles."""
layer_name = layer_id or "3d-buildings"
self.call_js_method(
"add3DBuildings",
source=source,
minZoom=min_zoom,
fillExtrusionColor=fill_extrusion_color,
fillExtrusionOpacity=fill_extrusion_opacity,
heightProperty=height_property,
baseProperty=base_property,
layerId=layer_name,
**kwargs,
)
self._layers = {
**self._layers,
layer_name: {
"id": layer_name,
"type": "fill-extrusion",
},
}
self._add_to_layer_dict(layer_name, "Vector")
def animate_along_route(
self,
route: Any,
duration: int = 10000,
loop: bool = True,
marker_color: str = "#3388ff",
marker_size: float = 1.0,
show_trail: bool = False,
trail_color: str = "#3388ff",
trail_width: float = 3,
animation_id: Optional[str] = None,
**kwargs,
) -> str:
"""Animate a marker along a route."""
anim_id = animation_id or f"animation-{len(self._layers)}"
if isinstance(route, list) and len(route) > 0:
if isinstance(route[0], (list, tuple)):
coordinates = route
else:
raise ValueError("Route list must contain coordinate pairs")
elif isinstance(route, dict):
if route.get("type") == "LineString":
coordinates = route.get("coordinates", [])
elif route.get("type") == "Feature":
geometry = route.get("geometry", {})
if geometry.get("type") == "LineString":
coordinates = geometry.get("coordinates", [])
else:
raise ValueError("Feature geometry must be LineString")
elif route.get("type") == "FeatureCollection":
features = route.get("features", [])
if (
features
and features[0].get("geometry", {}).get("type") == "LineString"
):
coordinates = features[0]["geometry"]["coordinates"]
else:
raise ValueError(
"FeatureCollection must contain LineString features"
)
else:
raise ValueError(
"GeoJSON must be LineString, Feature, or FeatureCollection"
)
else:
geojson = to_geojson(route)
if geojson.get("type") == "url":
geojson = fetch_geojson(geojson["url"])
if geojson.get("type") == "FeatureCollection":
features = geojson.get("features", [])
if features:
coordinates = features[0].get("geometry", {}).get("coordinates", [])
else:
raise ValueError("No features found in data")
elif geojson.get("type") == "Feature":
coordinates = geojson.get("geometry", {}).get("coordinates", [])
else:
coordinates = geojson.get("coordinates", [])
if len(coordinates) < 2:
raise ValueError("Route must have at least 2 points")
self.call_js_method(
"animateAlongRoute",
id=anim_id,
coordinates=coordinates,
duration=duration,
loop=loop,
markerColor=marker_color,
markerSize=marker_size,
showTrail=show_trail,
trailColor=trail_color,
trailWidth=trail_width,
**kwargs,
)
self._layers = {
**self._layers,
anim_id: {
"id": anim_id,
"type": "animation",
},
}
return anim_id
def stop_animation(self, animation_id: str) -> None:
"""Stop a running animation."""
self.call_js_method("stopAnimation", animation_id)
if animation_id in self._layers:
layers = dict(self._layers)
del layers[animation_id]
self._layers = layers
def pause_animation(self, animation_id: str) -> None:
"""Pause a running animation."""
self.call_js_method("pauseAnimation", animation_id)
def resume_animation(self, animation_id: str) -> None:
"""Resume a paused animation."""
self.call_js_method("resumeAnimation", animation_id)
def set_animation_speed(self, animation_id: str, speed: float) -> None:
"""Set animation speed multiplier."""
self.call_js_method("setAnimationSpeed", animation_id, speed)
def add_hover_effect(
self,
layer_id: str,
highlight_color: Optional[str] = None,
highlight_opacity: Optional[float] = None,
highlight_outline_width: float = 2,
**kwargs,
) -> None:
"""Add hover highlight effect to an existing layer."""
self.call_js_method(
"addHoverEffect",
layerId=layer_id,
highlightColor=highlight_color,
highlightOpacity=highlight_opacity,
highlightOutlineWidth=highlight_outline_width,
**kwargs,
)
def set_fog(
self,
color: Optional[str] = None,
high_color: Optional[str] = None,
low_color: Optional[str] = None,
horizon_blend: Optional[float] = None,
range: Optional[List[float]] = None,
**kwargs,
) -> None:
"""Set fog atmospheric effect (Mapbox uses map.setFog() API)."""
self.call_js_method(
"setFog",
color=color,
highColor=high_color,
lowColor=low_color,
horizonBlend=horizon_blend,
range=range,
**kwargs,
)
def remove_fog(self) -> None:
"""Remove fog atmospheric effects from the map."""
self.call_js_method("removeFog")
def add_image_layer(
self,
url: str,
coordinates: List[List[float]],
name: Optional[str] = None,
opacity: float = 1.0,
**kwargs,
) -> None:
"""Add a georeferenced image overlay."""
self._validate_opacity(opacity)
layer_id = name or f"image-{len(self._layers)}"
if len(coordinates) != 4:
raise ValueError(
"coordinates must have exactly 4 corner points "
"[top-left, top-right, bottom-right, bottom-left]"
)
self.call_js_method(
"addImageLayer",
id=layer_id,
url=url,
coordinates=coordinates,
opacity=opacity,
**kwargs,
)
self._layers = {
**self._layers,
layer_id: {
"id": layer_id,
"type": "image",
"url": url,
"coordinates": coordinates,
},
}
self._add_to_layer_dict(layer_id, "Raster")
def add_video_layer(
self,
urls: List[str],
coordinates: List[List[float]],
name: Optional[str] = None,
opacity: float = 1.0,
**kwargs,
) -> None:
"""Add a georeferenced video overlay on the map."""
self._validate_opacity(opacity)
layer_id = name or f"video-{len(self._layers)}"
if len(coordinates) != 4:
raise ValueError(
"coordinates must have exactly 4 corner points "
"[top-left, top-right, bottom-right, bottom-left]"
)
self.call_js_method(
"addVideoLayer",
id=layer_id,
urls=urls,
coordinates=coordinates,
opacity=opacity,
**kwargs,
)
self._layers = {
**self._layers,
layer_id: {
"id": layer_id,
"type": "video",
"source": f"{layer_id}-source",
},
}
self._add_to_layer_dict(layer_id, "Raster")
def remove_video_layer(self, name: str) -> None:
"""Remove a video layer from the map."""
if name in self._layers:
layers = dict(self._layers)
del layers[name]
self._layers = layers
self._remove_from_layer_dict(name)
self.call_js_method("removeVideoLayer", id=name)
def play_video(self, name: str) -> None:
"""Start playing a video layer."""
self.call_js_method("playVideo", id=name)
def pause_video(self, name: str) -> None:
"""Pause a video layer."""
self.call_js_method("pauseVideo", id=name)
def seek_video(self, name: str, time: float) -> None:
"""Seek to a specific time in a video layer."""
self.call_js_method("seekVideo", id=name, time=time)
def add_split_map(
self,
left_layer: str,
right_layer: str,
position: int = 50,
) -> None:
"""Add a split map comparison view with a draggable divider."""
if not 0 <= position <= 100:
raise ValueError(f"position must be between 0 and 100, got {position}")
self.call_js_method(
"addSplitMap",
leftLayer=left_layer,
rightLayer=right_layer,
position=position,
)
def remove_split_map(self) -> None:
"""Remove the split map comparison view."""
self.call_js_method("removeSplitMap")
def set_projection(self, projection: str = "mercator") -> None:
"""Set the map projection (Mapbox supports 'globe' and 'mercator')."""
self.call_js_method("setProjection", projection=projection)
def update_geojson_source(self, source_id: str, data: Any) -> None:
"""Update the data of an existing GeoJSON source in place."""
processed_data = self._process_deck_data(data)
self.call_js_method(
"updateGeoJSONSource",
sourceId=source_id,
data=processed_data,
)
def add_image(self, name: str, url: str) -> None:
"""Load a custom icon image for use in symbol layers."""
self.call_js_method("addMapImage", name=name, url=url)
def add_swipe_map(self, left_layer: str, right_layer: str) -> None:
"""Add a drag-to-compare swipe control for two layers."""
self.call_js_method(
"addSwipeMap",
leftLayer=left_layer,
rightLayer=right_layer,
)
def remove_swipe_map(self) -> None:
"""Remove the swipe map comparison control."""
self.call_js_method("removeSwipeMap")
def set_filter(
self,
layer_id: str,
filter_expression: Optional[List] = None,
) -> None:
"""Set or clear a filter on a map layer."""
self.call_js_method(
"setFilter",
layerId=layer_id,
filter=filter_expression,
)
def query_rendered_features(
self,
geometry: Optional[Any] = None,
layers: Optional[List[str]] = None,
filter_expression: Optional[List] = None,
) -> Dict:
"""Query features currently rendered on the map."""
kwargs: Dict[str, Any] = {}
if geometry is not None:
kwargs["geometry"] = geometry
if layers is not None:
kwargs["layers"] = layers
if filter_expression is not None:
kwargs["filter"] = filter_expression
self.call_js_method("queryRenderedFeatures", **kwargs)
return self._queried_features
def query_source_features(
self,
source_id: str,
source_layer: Optional[str] = None,
filter_expression: Optional[List] = None,
) -> Dict:
"""Query features from a source."""
kwargs: Dict[str, Any] = {"sourceId": source_id}
if source_layer is not None:
kwargs["sourceLayer"] = source_layer
if filter_expression is not None:
kwargs["filter"] = filter_expression
self.call_js_method("querySourceFeatures", **kwargs)
return self._queried_features
@property
def queried_features(self) -> Dict:
"""Get the most recent query results."""
return self._queried_features
def get_visible_features(
self,
layers: Optional[List[str]] = None,
) -> Optional[Dict]:
"""Get all features currently visible in the viewport."""
if layers is not None:
self.call_js_method("getVisibleFeatures", layers=layers)
features = self._queried_features
if features and "data" in features:
return features["data"]
return None
def to_geojson(self, layer_id: Optional[str] = None) -> Optional[Dict]:
"""Get layer data as GeoJSON."""
if layer_id:
self.call_js_method("getLayerData", sourceId=layer_id)
features = self._queried_features
if features and "data" in features:
return features["data"]
return None
def to_geopandas(self, layer_id: Optional[str] = None) -> Any:
"""Get layer data as a GeoDataFrame."""
geojson = self.to_geojson(layer_id)
if geojson is None:
return None
try:
import geopandas as gpd
return gpd.GeoDataFrame.from_features(geojson.get("features", []))
except ImportError:
raise ImportError("geopandas is required for to_geopandas()")
# -------------------------------------------------------------------------
# Markers
# -------------------------------------------------------------------------
def add_marker(
self,
lng: float,
lat: float,
popup: Optional[str] = None,
tooltip: Optional[str] = None,
color: str = "#3388ff",
draggable: bool = False,
scale: float = 1.0,
popup_max_width: str = "240px",
tooltip_max_width: str = "240px",
name: Optional[str] = None,
**kwargs,
) -> str:
"""Add a single marker to the map."""
marker_id = name or f"marker-{len(self._layers)}"
self.call_js_method(
"addMarker",
lng,
lat,
id=marker_id,
popup=popup,
tooltip=tooltip,
color=color,
draggable=draggable,
scale=scale,
popupMaxWidth=popup_max_width,
tooltipMaxWidth=tooltip_max_width,
**kwargs,
)
self._layers = {
**self._layers,
marker_id: {
"id": marker_id,
"type": "marker",
"lngLat": [lng, lat],
},
}
self._add_to_layer_dict(marker_id, "Markers")
return marker_id
def add_markers(
self,
data: Any,
lng_column: Optional[str] = None,
lat_column: Optional[str] = None,
popup_column: Optional[str] = None,
tooltip_column: Optional[str] = None,
color: str = "#3388ff",
scale: float = 1.0,
popup_max_width: str = "240px",
tooltip_max_width: str = "240px",
draggable: bool = False,
name: Optional[str] = None,
**kwargs,
) -> str:
"""Add multiple markers from data."""
layer_id = name or f"markers-{len(self._layers)}"
markers = []
if hasattr(data, "geometry"):
for _, row in data.iterrows():
geom = row.geometry
if geom.geom_type == "Point":
marker = {"lngLat": [geom.x, geom.y]}
if popup_column and popup_column in row:
marker["popup"] = str(row[popup_column])
if tooltip_column and tooltip_column in row:
marker["tooltip"] = str(row[tooltip_column])
markers.append(marker)
elif isinstance(data, dict) and data.get("type") == "FeatureCollection":
for feature in data.get("features", []):
geom = feature.get("geometry", {})
if geom.get("type") == "Point":
coords = geom.get("coordinates", [])
marker = {"lngLat": coords[:2]}
props = feature.get("properties", {})
if popup_column and popup_column in props:
marker["popup"] = str(props[popup_column])
if tooltip_column and tooltip_column in props:
marker["tooltip"] = str(props[tooltip_column])
markers.append(marker)
elif isinstance(data, list):
lng_keys = ["lng", "lon", "longitude", "x"]
lat_keys = ["lat", "latitude", "y"]
for item in data:
if not isinstance(item, dict):
continue
lng_val = None
lat_val = None
if lng_column and lng_column in item:
lng_val = item[lng_column]
else:
for key in lng_keys:
if key in item:
lng_val = item[key]
break
if lat_column and lat_column in item:
lat_val = item[lat_column]
else:
for key in lat_keys:
if key in item:
lat_val = item[key]
break
if lng_val is not None and lat_val is not None:
marker = {"lngLat": [float(lng_val), float(lat_val)]}
if popup_column and popup_column in item:
marker["popup"] = str(item[popup_column])
if tooltip_column and tooltip_column in item:
marker["tooltip"] = str(item[tooltip_column])
markers.append(marker)
if not markers:
raise ValueError("No valid point data found in input")
self.call_js_method(
"addMarkers",
id=layer_id,
markers=markers,
color=color,
scale=scale,
popupMaxWidth=popup_max_width,
tooltipMaxWidth=tooltip_max_width,
draggable=draggable,
**kwargs,
)
self._layers = {
**self._layers,
layer_id: {
"id": layer_id,
"type": "markers",
"count": len(markers),
},
}
self._add_to_layer_dict(layer_id, "Markers")
return layer_id
def remove_marker(self, marker_id: str) -> None:
"""Remove a marker from the map."""
self._remove_layer_internal(marker_id, "removeMarker")
# -------------------------------------------------------------------------
# HTML Export
# -------------------------------------------------------------------------
def _generate_html_template(self) -> str:
"""Generate standalone HTML for the map."""
template_path = Path(__file__).parent / "templates" / "mapbox.html"
if template_path.exists():
template = template_path.read_text(encoding="utf-8")
else:
template = self._get_default_template()
# Serialize state
state = {
"center": self.center,
"zoom": self.zoom,
"style": self.style,
"bearing": self.bearing,
"pitch": self.pitch,
"width": self.width,
"height": self.height,
"layers": self._layers,
"sources": self._sources,
"controls": self._controls,
"js_calls": self._js_calls,
"access_token": self.access_token,
}
template = template.replace("{{state}}", json.dumps(state, indent=2))
return template
def _get_default_template(self) -> str:
"""Get default HTML template."""
return """<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>{{title}}</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<script src="https://api.mapbox.com/mapbox-gl-js/v3.0.0/mapbox-gl.js"></script>
<link href="https://api.mapbox.com/mapbox-gl-js/v3.0.0/mapbox-gl.css" rel="stylesheet" />
<style>
body { margin: 0; padding: 0; }
#map { position: absolute; top: 0; bottom: 0; width: 100%; }
</style>
</head>
<body>
<div id="map"></div>
<script>
const state = {{state}};
mapboxgl.accessToken = state.access_token;
const map = new mapboxgl.Map({
container: 'map',
style: state.style,
center: state.center,
zoom: state.zoom,
bearing: state.bearing || 0,
pitch: state.pitch || 0
});
map.on('load', function() {
// Replay JS calls
for (const call of state.js_calls || []) {
try {
executeMethod(call.method, call.args, call.kwargs);
} catch (e) {
console.error('Error executing', call.method, e);
}
}
});
function executeMethod(method, args, kwargs) {
switch (method) {
case 'addBasemap':
const url = args[0];
const name = kwargs.name || 'basemap';
const sourceId = 'basemap-' + name;
if (!map.getSource(sourceId)) {
map.addSource(sourceId, {
type: 'raster',
tiles: [url],
tileSize: 256,
attribution: kwargs.attribution || ''
});
}
if (!map.getLayer(sourceId)) {
map.addLayer({
id: sourceId,
type: 'raster',
source: sourceId
});
}
break;
case 'addGeoJSON':
const layerName = kwargs.name;
const sourceIdGeo = layerName + '-source';
if (!map.getSource(sourceIdGeo)) {
map.addSource(sourceIdGeo, {
type: 'geojson',
data: kwargs.data
});
}
if (!map.getLayer(layerName)) {
map.addLayer({
id: layerName,
type: kwargs.layerType || 'circle',
source: sourceIdGeo,
paint: kwargs.paint || {}
});
}
if (kwargs.fitBounds && kwargs.bounds) {
map.fitBounds([
[kwargs.bounds[0], kwargs.bounds[1]],
[kwargs.bounds[2], kwargs.bounds[3]]
], { padding: 50 });
}
break;
case 'addTileLayer':
const tileUrl = args[0];
const tileName = kwargs.name;
const tileSourceId = tileName + '-source';
if (!map.getSource(tileSourceId)) {
map.addSource(tileSourceId, {
type: 'raster',
tiles: [tileUrl],
tileSize: 256,
attribution: kwargs.attribution || ''
});
}
if (!map.getLayer(tileName)) {
map.addLayer({
id: tileName,
type: 'raster',
source: tileSourceId
});
}
break;
case 'addControl':
const controlType = args[0];
const position = kwargs.position || 'top-right';
let control;
switch (controlType) {
case 'navigation':
control = new mapboxgl.NavigationControl();
break;
case 'scale':
control = new mapboxgl.ScaleControl();
break;
case 'fullscreen':
control = new mapboxgl.FullscreenControl();
break;
}
if (control) {
map.addControl(control, position);
}
break;
case 'addTerrain':
const terrainSource = kwargs.source || 'mapbox-dem';
if (!map.getSource(terrainSource)) {
map.addSource(terrainSource, {
type: 'raster-dem',
url: 'mapbox://mapbox.mapbox-terrain-dem-v1',
tileSize: 512,
maxzoom: 14
});
}
map.setTerrain({ source: terrainSource, exaggeration: kwargs.exaggeration || 1 });
break;
case 'removeTerrain':
map.setTerrain(null);
break;
case 'flyTo':
map.flyTo({
center: [args[0], args[1]],
zoom: kwargs.zoom,
duration: kwargs.duration || 2000
});
break;
case 'fitBounds':
const bounds = args[0];
map.fitBounds([
[bounds[0], bounds[1]],
[bounds[2], bounds[3]]
], {
padding: kwargs.padding || 50,
duration: kwargs.duration || 1000
});
break;
case 'addMarker':
new mapboxgl.Marker({ color: kwargs.color || '#3388ff' })
.setLngLat([args[0], args[1]])
.addTo(map);
break;
default:
console.log('Unknown method:', method);
}
}
</script>
</body>
</html>"""
draw_data: Dict
property
readonly
¶
Property to access current draw data.
queried_features: Dict
property
readonly
¶
Get the most recent query results.
__init__(self, center=(0.0, 0.0), zoom=2.0, width='100%', height='600px', style='mapbox://styles/mapbox/streets-v12', bearing=0.0, pitch=0.0, max_pitch=85.0, access_token=None, controls=None, **kwargs)
special
¶
Initialize a Mapbox map.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
center |
Tuple[float, float] |
Map center as (longitude, latitude). |
(0.0, 0.0) |
zoom |
float |
Initial zoom level. |
2.0 |
width |
str |
Map width as CSS string. |
'100%' |
height |
str |
Map height as CSS string. |
'600px' |
style |
str |
Mapbox style URL (e.g., "mapbox://styles/mapbox/streets-v12"). |
'mapbox://styles/mapbox/streets-v12' |
bearing |
float |
Map bearing in degrees. |
0.0 |
pitch |
float |
Map pitch in degrees. |
0.0 |
max_pitch |
float |
Maximum pitch angle in degrees (default: 85). |
85.0 |
access_token |
Optional[str] |
Mapbox access token. If None, reads from MAPBOX_TOKEN env var. |
None |
controls |
Optional[Dict[str, Any]] |
Dict of controls to add (e.g., {"navigation": True}). |
None |
**kwargs |
Additional widget arguments. |
{} |
Source code in anymap_ts/mapbox.py
def __init__(
self,
center: Tuple[float, float] = (0.0, 0.0),
zoom: float = 2.0,
width: str = "100%",
height: str = "600px",
style: str = "mapbox://styles/mapbox/streets-v12",
bearing: float = 0.0,
pitch: float = 0.0,
max_pitch: float = 85.0,
access_token: Optional[str] = None,
controls: Optional[Dict[str, Any]] = None,
**kwargs,
):
"""Initialize a Mapbox map.
Args:
center: Map center as (longitude, latitude).
zoom: Initial zoom level.
width: Map width as CSS string.
height: Map height as CSS string.
style: Mapbox style URL (e.g., "mapbox://styles/mapbox/streets-v12").
bearing: Map bearing in degrees.
pitch: Map pitch in degrees.
max_pitch: Maximum pitch angle in degrees (default: 85).
access_token: Mapbox access token. If None, reads from MAPBOX_TOKEN env var.
controls: Dict of controls to add (e.g., {"navigation": True}).
**kwargs: Additional widget arguments.
"""
# Get access token
token = access_token or get_mapbox_token()
if not token:
print(
"Warning: No Mapbox access token provided. "
"Set MAPBOX_TOKEN environment variable or pass access_token parameter."
)
super().__init__(
center=list(center),
zoom=zoom,
width=width,
height=height,
style=style,
bearing=bearing,
pitch=pitch,
max_pitch=max_pitch,
access_token=token,
**kwargs,
)
# Initialize layer dictionary
self._layer_dict = {"Background": []}
# Add default controls
if controls is None:
controls = {"navigation": True, "fullscreen": True}
for control_name, config in controls.items():
if config:
self.add_control(
control_name, **(config if isinstance(config, dict) else {})
)
add_3d_buildings(self, source='openmaptiles', min_zoom=14, fill_extrusion_color='#aaa', fill_extrusion_opacity=0.6, height_property='render_height', base_property='render_min_height', layer_id=None, **kwargs)
¶
Add 3D building extrusions from vector tiles.
Source code in anymap_ts/mapbox.py
def add_3d_buildings(
self,
source: str = "openmaptiles",
min_zoom: float = 14,
fill_extrusion_color: str = "#aaa",
fill_extrusion_opacity: float = 0.6,
height_property: str = "render_height",
base_property: str = "render_min_height",
layer_id: Optional[str] = None,
**kwargs,
) -> None:
"""Add 3D building extrusions from vector tiles."""
layer_name = layer_id or "3d-buildings"
self.call_js_method(
"add3DBuildings",
source=source,
minZoom=min_zoom,
fillExtrusionColor=fill_extrusion_color,
fillExtrusionOpacity=fill_extrusion_opacity,
heightProperty=height_property,
baseProperty=base_property,
layerId=layer_name,
**kwargs,
)
self._layers = {
**self._layers,
layer_name: {
"id": layer_name,
"type": "fill-extrusion",
},
}
self._add_to_layer_dict(layer_name, "Vector")
add_3d_terrain(self, exaggeration=1.0, source='mapbox-dem', **kwargs)
¶
Alias for add_terrain for MapLibre compatibility.
Source code in anymap_ts/mapbox.py
def add_3d_terrain(
self, exaggeration: float = 1.0, source: str = "mapbox-dem", **kwargs
) -> None:
"""Alias for add_terrain for MapLibre compatibility."""
self.add_terrain(exaggeration=exaggeration, source=source)
add_arc_layer(self, data, name=None, get_source_position='source', get_target_position='target', get_source_color=None, get_target_color=None, get_width=1, get_height=1, great_circle=False, pickable=True, opacity=0.8, **kwargs)
¶
Add an arc layer for origin-destination visualization using deck.gl.
Arc layers are ideal for visualizing connections between locations, such as flight routes, migration patterns, or network flows.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data |
Any |
Array of data objects with source/target coordinates. Each object should have source and target positions. |
required |
name |
Optional[str] |
Layer ID. If None, auto-generated. |
None |
get_source_position |
Union[str, Any] |
Accessor for source position [lng, lat]. Can be a string (property name) or a value. |
'source' |
get_target_position |
Union[str, Any] |
Accessor for target position [lng, lat]. Can be a string (property name) or a value. |
'target' |
get_source_color |
Optional[List[int]] |
Source end color as [r, g, b, a]. Default: [51, 136, 255, 255] (blue). |
None |
get_target_color |
Optional[List[int]] |
Target end color as [r, g, b, a]. Default: [255, 136, 51, 255] (orange). |
None |
get_width |
Union[float, str] |
Arc width in pixels. Can be a number or accessor. |
1 |
get_height |
float |
Arc height multiplier. Higher values create more curved arcs. |
1 |
great_circle |
bool |
Whether to draw arcs along great circles. |
False |
pickable |
bool |
Whether layer responds to hover/click events. |
True |
opacity |
float |
Layer opacity (0-1). |
0.8 |
**kwargs |
Additional ArcLayer props. |
{} |
Examples:
>>> from anymap_ts import MapboxMap
>>> m = MapboxMap()
>>> arcs = [
... {"source": [-122.4, 37.8], "target": [-73.9, 40.7]},
... {"source": [-122.4, 37.8], "target": [-0.1, 51.5]},
... ]
>>> m.add_arc_layer(arcs, name="flights")
Source code in anymap_ts/mapbox.py
def add_arc_layer(
self,
data: Any,
name: Optional[str] = None,
get_source_position: Union[str, Any] = "source",
get_target_position: Union[str, Any] = "target",
get_source_color: Optional[List[int]] = None,
get_target_color: Optional[List[int]] = None,
get_width: Union[float, str] = 1,
get_height: float = 1,
great_circle: bool = False,
pickable: bool = True,
opacity: float = 0.8,
**kwargs,
) -> None:
"""Add an arc layer for origin-destination visualization using deck.gl.
Arc layers are ideal for visualizing connections between locations,
such as flight routes, migration patterns, or network flows.
Args:
data: Array of data objects with source/target coordinates.
Each object should have source and target positions.
name: Layer ID. If None, auto-generated.
get_source_position: Accessor for source position [lng, lat].
Can be a string (property name) or a value.
get_target_position: Accessor for target position [lng, lat].
Can be a string (property name) or a value.
get_source_color: Source end color as [r, g, b, a].
Default: [51, 136, 255, 255] (blue).
get_target_color: Target end color as [r, g, b, a].
Default: [255, 136, 51, 255] (orange).
get_width: Arc width in pixels. Can be a number or accessor.
get_height: Arc height multiplier. Higher values create more curved arcs.
great_circle: Whether to draw arcs along great circles.
pickable: Whether layer responds to hover/click events.
opacity: Layer opacity (0-1).
**kwargs: Additional ArcLayer props.
Example:
>>> from anymap_ts import MapboxMap
>>> m = MapboxMap()
>>> arcs = [
... {"source": [-122.4, 37.8], "target": [-73.9, 40.7]},
... {"source": [-122.4, 37.8], "target": [-0.1, 51.5]},
... ]
>>> m.add_arc_layer(arcs, name="flights")
"""
layer_id = name or f"arc-{len(self._layers)}"
processed_data = self._process_deck_data(data)
self.call_js_method(
"addArcLayer",
id=layer_id,
data=processed_data,
getSourcePosition=get_source_position,
getTargetPosition=get_target_position,
getSourceColor=get_source_color or [51, 136, 255, 255],
getTargetColor=get_target_color or [255, 136, 51, 255],
getWidth=get_width,
getHeight=get_height,
greatCircle=great_circle,
pickable=pickable,
opacity=opacity,
**kwargs,
)
self._layers = {
**self._layers,
layer_id: {
"id": layer_id,
"type": "arc",
},
}
self._add_to_layer_dict(layer_id, "Deck.gl")
add_basemap(self, basemap='mapbox://styles/mapbox/streets-v12', attribution=None, **kwargs)
¶
Add a basemap layer.
For Mapbox styles, use the style URL format: - "mapbox://styles/mapbox/streets-v12" - "mapbox://styles/mapbox/satellite-v9" - "mapbox://styles/mapbox/satellite-streets-v12" - "mapbox://styles/mapbox/light-v11" - "mapbox://styles/mapbox/dark-v11" - "mapbox://styles/mapbox/outdoors-v12"
Or use XYZ tile URLs for custom basemaps.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
basemap |
str |
Mapbox style URL or XYZ tile URL. |
'mapbox://styles/mapbox/streets-v12' |
attribution |
Optional[str] |
Custom attribution text. |
None |
**kwargs |
Additional options. |
{} |
Source code in anymap_ts/mapbox.py
def add_basemap(
self,
basemap: str = "mapbox://styles/mapbox/streets-v12",
attribution: Optional[str] = None,
**kwargs,
) -> None:
"""Add a basemap layer.
For Mapbox styles, use the style URL format:
- "mapbox://styles/mapbox/streets-v12"
- "mapbox://styles/mapbox/satellite-v9"
- "mapbox://styles/mapbox/satellite-streets-v12"
- "mapbox://styles/mapbox/light-v11"
- "mapbox://styles/mapbox/dark-v11"
- "mapbox://styles/mapbox/outdoors-v12"
Or use XYZ tile URLs for custom basemaps.
Args:
basemap: Mapbox style URL or XYZ tile URL.
attribution: Custom attribution text.
**kwargs: Additional options.
"""
# If it's a Mapbox style URL, set it as the map style
if basemap.startswith("mapbox://"):
self.style = basemap
return
# Otherwise, treat as XYZ tile URL
try:
url, default_attribution = get_basemap_url(basemap)
except (ValueError, KeyError):
url = basemap
default_attribution = ""
self.call_js_method(
"addBasemap",
url,
attribution=attribution or default_attribution,
name=basemap,
**kwargs,
)
# Track in layer dict
basemaps = self._layer_dict.get("Basemaps", [])
if basemap not in basemaps:
self._layer_dict = {
**self._layer_dict,
"Basemaps": basemaps + [basemap],
}
add_bitmap_layer(self, image, bounds, name=None, opacity=1.0, visible=True, pickable=False, desaturate=0, transparent_color=None, tint_color=None, **kwargs)
¶
Add a bitmap layer using deck.gl.
Source code in anymap_ts/mapbox.py
def add_bitmap_layer(
self,
image: str,
bounds: List[float],
name: Optional[str] = None,
opacity: float = 1.0,
visible: bool = True,
pickable: bool = False,
desaturate: float = 0,
transparent_color: Optional[List[int]] = None,
tint_color: Optional[List[int]] = None,
**kwargs,
) -> None:
"""Add a bitmap layer using deck.gl."""
layer_id = name or f"bitmap-{len(self._layers)}"
self.call_js_method(
"addBitmapLayer",
id=layer_id,
image=image,
bounds=bounds,
opacity=opacity,
visible=visible,
pickable=pickable,
desaturate=desaturate,
transparentColor=transparent_color or [0, 0, 0, 0],
tintColor=tint_color or [255, 255, 255],
**kwargs,
)
self._layers = {**self._layers, layer_id: {"id": layer_id, "type": "bitmap"}}
self._add_to_layer_dict(layer_id, "Deck.gl")
add_choropleth(self, data, column, cmap='viridis', classification='quantile', k=5, breaks=None, fill_opacity=0.7, line_color='#000000', line_width=1, legend=True, legend_title=None, hover=True, layer_id=None, fit_bounds=True, **kwargs)
¶
Add a choropleth (thematic) map layer.
Source code in anymap_ts/mapbox.py
def add_choropleth(
self,
data: Any,
column: str,
cmap: str = "viridis",
classification: str = "quantile",
k: int = 5,
breaks: Optional[List[float]] = None,
fill_opacity: float = 0.7,
line_color: str = "#000000",
line_width: float = 1,
legend: bool = True,
legend_title: Optional[str] = None,
hover: bool = True,
layer_id: Optional[str] = None,
fit_bounds: bool = True,
**kwargs,
) -> None:
"""Add a choropleth (thematic) map layer."""
from .utils import (
get_choropleth_colors,
compute_breaks,
build_step_expression,
)
layer_name = layer_id or f"choropleth-{len(self._layers)}"
geojson = to_geojson(data)
if geojson.get("type") == "url":
url = geojson["url"]
geojson = fetch_geojson(url)
features = geojson.get("features", [])
values = []
for feature in features:
props = feature.get("properties", {})
val = props.get(column)
if val is not None:
try:
values.append(float(val))
except (TypeError, ValueError):
pass
if not values:
raise ValueError(f"No valid numeric values found for column '{column}'")
computed_breaks = compute_breaks(values, classification, k, breaks)
colors = get_choropleth_colors(cmap, k)
step_expr = build_step_expression(column, computed_breaks, colors)
bounds = get_bounds(geojson) if fit_bounds else None
self.call_js_method(
"addChoropleth",
data=geojson,
name=layer_name,
column=column,
stepExpression=step_expr,
fillOpacity=fill_opacity,
lineColor=line_color,
lineWidth=line_width,
hover=hover,
fitBounds=fit_bounds,
bounds=bounds,
**kwargs,
)
self._layers = {
**self._layers,
layer_name: {
"id": layer_name,
"type": "choropleth",
"source": f"{layer_name}-source",
"column": column,
},
}
self._add_to_layer_dict(layer_name, "Vector")
if legend:
title = legend_title or column
labels = []
for i in range(len(computed_breaks) - 1):
low = computed_breaks[i]
high = computed_breaks[i + 1]
labels.append(f"{low:.1f} - {high:.1f}")
self.add_legend(
title=title,
labels=labels,
colors=colors,
position="bottom-right",
)
add_cluster_layer(self, data, cluster_radius=50, cluster_max_zoom=14, cluster_colors=None, cluster_steps=None, cluster_min_radius=15, cluster_max_radius=30, unclustered_color='#11b4da', unclustered_radius=8, show_cluster_count=True, name=None, zoom_on_click=True, fit_bounds=True, **kwargs)
¶
Add a clustered point layer.
Source code in anymap_ts/mapbox.py
def add_cluster_layer(
self,
data: Any,
cluster_radius: int = 50,
cluster_max_zoom: int = 14,
cluster_colors: Optional[List[str]] = None,
cluster_steps: Optional[List[int]] = None,
cluster_min_radius: int = 15,
cluster_max_radius: int = 30,
unclustered_color: str = "#11b4da",
unclustered_radius: int = 8,
show_cluster_count: bool = True,
name: Optional[str] = None,
zoom_on_click: bool = True,
fit_bounds: bool = True,
**kwargs,
) -> str:
"""Add a clustered point layer."""
layer_id = name or f"cluster-{len(self._layers)}"
if cluster_colors is None:
cluster_colors = ["#51bbd6", "#f1f075", "#f28cb1"]
if cluster_steps is None:
cluster_steps = [100, 750]
if len(cluster_steps) != len(cluster_colors) - 1:
raise ValueError(
f"cluster_steps must have {len(cluster_colors) - 1} values "
f"(one less than cluster_colors), got {len(cluster_steps)}"
)
geojson = to_geojson(data)
if geojson.get("type") == "url":
url = geojson["url"]
geojson = fetch_geojson(url)
bounds = get_bounds(geojson) if fit_bounds else None
self.call_js_method(
"addClusterLayer",
data=geojson,
name=layer_id,
clusterRadius=cluster_radius,
clusterMaxZoom=cluster_max_zoom,
clusterColors=cluster_colors,
clusterSteps=cluster_steps,
clusterMinRadius=cluster_min_radius,
clusterMaxRadius=cluster_max_radius,
unclusteredColor=unclustered_color,
unclusteredRadius=unclustered_radius,
showClusterCount=show_cluster_count,
zoomOnClick=zoom_on_click,
fitBounds=fit_bounds,
bounds=bounds,
**kwargs,
)
self._layers = {
**self._layers,
layer_id: {
"id": layer_id,
"type": "cluster",
"source": f"{layer_id}-source",
},
}
self._add_to_layer_dict(layer_id, "Vector")
return layer_id
add_cog_control(self, position='top-right', collapsed=True, default_url=None, load_default_url=False, default_opacity=1.0, default_colormap='viridis', default_bands='1', default_rescale_min=0, default_rescale_max=255, **kwargs)
¶
Add a COG layer control.
Source code in anymap_ts/mapbox.py
def add_cog_control(
self,
position: str = "top-right",
collapsed: bool = True,
default_url: Optional[str] = None,
load_default_url: bool = False,
default_opacity: float = 1.0,
default_colormap: str = "viridis",
default_bands: str = "1",
default_rescale_min: float = 0,
default_rescale_max: float = 255,
**kwargs,
) -> None:
"""Add a COG layer control."""
self.call_js_method(
"addCogControl",
position=position,
collapsed=collapsed,
defaultUrl=default_url or "",
loadDefaultUrl=load_default_url,
defaultOpacity=default_opacity,
defaultColormap=default_colormap,
defaultBands=default_bands,
defaultRescaleMin=default_rescale_min,
defaultRescaleMax=default_rescale_max,
**kwargs,
)
self._controls = {
**self._controls,
"cog-control": {"position": position, "collapsed": collapsed},
}
add_cog_layer(self, url, name=None, opacity=1.0, visible=True, debug=False, debug_opacity=0.25, max_error=0.125, fit_bounds=True, before_id=None, **kwargs)
¶
Add a Cloud Optimized GeoTIFF (COG) layer using deck.gl-raster.
This method renders COG files directly in the browser using GPU-accelerated deck.gl rendering with automatic reprojection support.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
url |
str |
URL to the Cloud Optimized GeoTIFF file. |
required |
name |
Optional[str] |
Layer ID. If None, auto-generated. |
None |
opacity |
float |
Layer opacity (0-1). |
1.0 |
visible |
bool |
Whether layer is visible. |
True |
debug |
bool |
Show reprojection mesh for debugging. |
False |
debug_opacity |
float |
Opacity of debug mesh (0-1). |
0.25 |
max_error |
float |
Maximum reprojection error in pixels. Lower values create denser mesh for better accuracy. |
0.125 |
fit_bounds |
bool |
Whether to fit map to COG bounds after loading. |
True |
before_id |
Optional[str] |
ID of layer to insert before. |
None |
**kwargs |
Additional COGLayer props. |
{} |
Examples:
>>> from anymap_ts import MapboxMap
>>> m = MapboxMap()
>>> m.add_cog_layer(
... "https://example.com/landcover.tif",
... name="landcover",
... opacity=0.8
... )
Source code in anymap_ts/mapbox.py
def add_cog_layer(
self,
url: str,
name: Optional[str] = None,
opacity: float = 1.0,
visible: bool = True,
debug: bool = False,
debug_opacity: float = 0.25,
max_error: float = 0.125,
fit_bounds: bool = True,
before_id: Optional[str] = None,
**kwargs,
) -> None:
"""Add a Cloud Optimized GeoTIFF (COG) layer using deck.gl-raster.
This method renders COG files directly in the browser using GPU-accelerated
deck.gl rendering with automatic reprojection support.
Args:
url: URL to the Cloud Optimized GeoTIFF file.
name: Layer ID. If None, auto-generated.
opacity: Layer opacity (0-1).
visible: Whether layer is visible.
debug: Show reprojection mesh for debugging.
debug_opacity: Opacity of debug mesh (0-1).
max_error: Maximum reprojection error in pixels. Lower values
create denser mesh for better accuracy.
fit_bounds: Whether to fit map to COG bounds after loading.
before_id: ID of layer to insert before.
**kwargs: Additional COGLayer props.
Example:
>>> from anymap_ts import MapboxMap
>>> m = MapboxMap()
>>> m.add_cog_layer(
... "https://example.com/landcover.tif",
... name="landcover",
... opacity=0.8
... )
"""
layer_id = name or f"cog-{len(self._layers)}"
self.call_js_method(
"addCOGLayer",
id=layer_id,
geotiff=url,
opacity=opacity,
visible=visible,
debug=debug,
debugOpacity=debug_opacity,
maxError=max_error,
fitBounds=fit_bounds,
beforeId=before_id,
**kwargs,
)
self._layers = {
**self._layers,
layer_id: {
"id": layer_id,
"type": "cog",
"url": url,
},
}
self._add_to_layer_dict(layer_id, "Raster")
add_colorbar(self, colormap='viridis', vmin=0, vmax=1, label='', units='', orientation='horizontal', position='bottom-right', bar_thickness=None, bar_length=None, ticks=None, opacity=None, colorbar_id=None, **kwargs)
¶
Add a continuous gradient colorbar to the map.
Source code in anymap_ts/mapbox.py
def add_colorbar(
self,
colormap: str = "viridis",
vmin: float = 0,
vmax: float = 1,
label: str = "",
units: str = "",
orientation: str = "horizontal",
position: str = "bottom-right",
bar_thickness: Optional[int] = None,
bar_length: Optional[int] = None,
ticks: Optional[Dict] = None,
opacity: Optional[float] = None,
colorbar_id: Optional[str] = None,
**kwargs,
) -> None:
"""Add a continuous gradient colorbar to the map."""
self._validate_position(position)
cbar_id = (
colorbar_id
or f"colorbar-{len([k for k in self._controls.keys() if k.startswith('colorbar')])}"
)
js_kwargs: Dict[str, Any] = {
"colormap": colormap,
"vmin": vmin,
"vmax": vmax,
"label": label,
"units": units,
"orientation": orientation,
"position": position,
"colorbarId": cbar_id,
**kwargs,
}
if bar_thickness is not None:
js_kwargs["barThickness"] = bar_thickness
if bar_length is not None:
js_kwargs["barLength"] = bar_length
if ticks is not None:
js_kwargs["ticks"] = ticks
if opacity is not None:
js_kwargs["opacity"] = opacity
self.call_js_method("addColorbar", **js_kwargs)
self._controls = {
**self._controls,
cbar_id: {
"type": "colorbar",
"colormap": colormap,
"vmin": vmin,
"vmax": vmax,
"label": label,
"units": units,
"orientation": orientation,
"position": position,
},
}
add_column_layer(self, data, name=None, get_position='coordinates', get_fill_color=None, get_line_color=None, get_elevation=1000, radius=1000, disk_resolution=20, elevation_scale=1, coverage=1, extruded=True, filled=True, stroked=False, wireframe=False, pickable=True, opacity=0.8, **kwargs)
¶
Add a column layer using deck.gl.
Source code in anymap_ts/mapbox.py
def add_column_layer(
self,
data: Any,
name: Optional[str] = None,
get_position: Union[str, Any] = "coordinates",
get_fill_color: Optional[Union[List[int], str]] = None,
get_line_color: Optional[Union[List[int], str]] = None,
get_elevation: Union[float, str] = 1000,
radius: float = 1000,
disk_resolution: int = 20,
elevation_scale: float = 1,
coverage: float = 1,
extruded: bool = True,
filled: bool = True,
stroked: bool = False,
wireframe: bool = False,
pickable: bool = True,
opacity: float = 0.8,
**kwargs,
) -> None:
"""Add a column layer using deck.gl."""
layer_id = name or f"column-{len(self._layers)}"
processed_data = self._process_deck_data(data)
self.call_js_method(
"addColumnLayer",
id=layer_id,
data=processed_data,
getPosition=get_position,
getFillColor=get_fill_color or [255, 140, 0, 200],
getLineColor=get_line_color or [0, 0, 0, 255],
getElevation=get_elevation,
radius=radius,
diskResolution=disk_resolution,
elevationScale=elevation_scale,
coverage=coverage,
extruded=extruded,
filled=filled,
stroked=stroked,
wireframe=wireframe,
pickable=pickable,
opacity=opacity,
**kwargs,
)
self._layers = {**self._layers, layer_id: {"id": layer_id, "type": "column"}}
self._add_to_layer_dict(layer_id, "Deck.gl")
add_contour_layer(self, data, name=None, get_position='coordinates', get_weight=1, cell_size=200, contours=None, pickable=True, opacity=1, **kwargs)
¶
Add a contour layer using deck.gl.
Source code in anymap_ts/mapbox.py
def add_contour_layer(
self,
data: Any,
name: Optional[str] = None,
get_position: Union[str, Any] = "coordinates",
get_weight: Union[float, str] = 1,
cell_size: float = 200,
contours: Optional[List[Dict]] = None,
pickable: bool = True,
opacity: float = 1,
**kwargs,
) -> None:
"""Add a contour layer using deck.gl."""
layer_id = name or f"contour-{len(self._layers)}"
processed_data = self._process_deck_data(data)
default_contours = [
{"threshold": 1, "color": [255, 255, 255], "strokeWidth": 1},
{"threshold": 5, "color": [51, 136, 255], "strokeWidth": 2},
{"threshold": 10, "color": [0, 0, 255], "strokeWidth": 3},
]
self.call_js_method(
"addContourLayer",
id=layer_id,
data=processed_data,
getPosition=get_position,
getWeight=get_weight,
cellSize=cell_size,
contours=contours or default_contours,
pickable=pickable,
opacity=opacity,
**kwargs,
)
self._layers = {**self._layers, layer_id: {"id": layer_id, "type": "contour"}}
self._add_to_layer_dict(layer_id, "Deck.gl")
add_control(self, control_type, position='top-right', **kwargs)
¶
Add a map control.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
control_type |
str |
Type of control ('navigation', 'scale', 'fullscreen', etc.). |
required |
position |
str |
Control position. |
'top-right' |
**kwargs |
Control-specific options. |
{} |
Source code in anymap_ts/mapbox.py
def add_control(
self,
control_type: str,
position: str = "top-right",
**kwargs,
) -> None:
"""Add a map control.
Args:
control_type: Type of control ('navigation', 'scale', 'fullscreen', etc.).
position: Control position.
**kwargs: Control-specific options.
"""
self.call_js_method("addControl", control_type, position=position, **kwargs)
self._controls = {
**self._controls,
control_type: {"type": control_type, "position": position, **kwargs},
}
add_control_grid(self, position='top-right', default_controls=None, exclude=None, rows=None, columns=None, collapsed=True, collapsible=True, title='', show_row_column_controls=True, gap=2, basemap_style_url=None, exclude_layers=None, **kwargs)
¶
Add a ControlGrid with all default tools or a custom subset.
Source code in anymap_ts/mapbox.py
def add_control_grid(
self,
position: str = "top-right",
default_controls: Optional[List[str]] = None,
exclude: Optional[List[str]] = None,
rows: Optional[int] = None,
columns: Optional[int] = None,
collapsed: bool = True,
collapsible: bool = True,
title: str = "",
show_row_column_controls: bool = True,
gap: int = 2,
basemap_style_url: Optional[str] = None,
exclude_layers: Optional[List[str]] = None,
**kwargs,
) -> None:
"""Add a ControlGrid with all default tools or a custom subset."""
js_kwargs: Dict[str, Any] = {
"position": position,
"collapsed": collapsed,
"collapsible": collapsible,
"showRowColumnControls": show_row_column_controls,
"gap": gap,
**kwargs,
}
if default_controls is not None:
js_kwargs["defaultControls"] = default_controls
if exclude is not None:
js_kwargs["exclude"] = exclude
if rows is not None:
js_kwargs["rows"] = rows
if columns is not None:
js_kwargs["columns"] = columns
if title:
js_kwargs["title"] = title
if basemap_style_url is not None:
js_kwargs["basemapStyleUrl"] = basemap_style_url
if exclude_layers is not None:
js_kwargs["excludeLayers"] = exclude_layers
self.call_js_method("addControlGrid", **js_kwargs)
self._controls = {
**self._controls,
"control-grid": {
"position": position,
"collapsed": collapsed,
"collapsible": collapsible,
},
}
add_coordinates_control(self, position='bottom-left', precision=4)
¶
Add a coordinates display control.
Source code in anymap_ts/mapbox.py
def add_coordinates_control(
self,
position: str = "bottom-left",
precision: int = 4,
) -> None:
"""Add a coordinates display control."""
self.call_js_method(
"addCoordinatesControl",
position=position,
precision=precision,
)
add_deck_heatmap_layer(self, data, name=None, get_position='coordinates', get_weight=1, radius_pixels=30, intensity=1, threshold=0.05, color_range=None, opacity=1, **kwargs)
¶
Add a GPU-accelerated heatmap layer using deck.gl.
Source code in anymap_ts/mapbox.py
def add_deck_heatmap_layer(
self,
data: Any,
name: Optional[str] = None,
get_position: Union[str, Any] = "coordinates",
get_weight: Union[float, str] = 1,
radius_pixels: float = 30,
intensity: float = 1,
threshold: float = 0.05,
color_range: Optional[List[List[int]]] = None,
opacity: float = 1,
**kwargs,
) -> None:
"""Add a GPU-accelerated heatmap layer using deck.gl."""
layer_id = name or f"deck-heatmap-{len(self._layers)}"
processed_data = self._process_deck_data(data)
default_color_range = [
[255, 255, 178, 25],
[254, 217, 118, 85],
[254, 178, 76, 127],
[253, 141, 60, 170],
[240, 59, 32, 212],
[189, 0, 38, 255],
]
self.call_js_method(
"addHeatmapLayer",
id=layer_id,
data=processed_data,
getPosition=get_position,
getWeight=get_weight,
radiusPixels=radius_pixels,
intensity=intensity,
threshold=threshold,
colorRange=color_range or default_color_range,
opacity=opacity,
**kwargs,
)
self._layers = {
**self._layers,
layer_id: {"id": layer_id, "type": "deck-heatmap"},
}
self._add_to_layer_dict(layer_id, "Deck.gl")
add_deckgl_layer(self, layer_type, data, name=None, **kwargs)
¶
Add a generic deck.gl layer to the map.
Source code in anymap_ts/mapbox.py
def add_deckgl_layer(
self,
layer_type: str,
data: Any,
name: Optional[str] = None,
**kwargs,
) -> None:
"""Add a generic deck.gl layer to the map."""
layer_type_clean = layer_type.replace("Layer", "")
prefix = layer_type_clean.lower()
layer_id = name or f"{prefix}-{len(self._layers)}"
processed_data = self._process_deck_data(data)
self.call_js_method(
"addDeckGLLayer",
layerType=layer_type,
id=layer_id,
data=processed_data,
**kwargs,
)
self._layers = {**self._layers, layer_id: {"id": layer_id, "type": layer_type}}
self._add_to_layer_dict(layer_id, "Deck.gl")
add_draw_control(self, position='top-right', draw_modes=None, edit_modes=None, collapsed=False, **kwargs)
¶
Add a drawing control.
Source code in anymap_ts/mapbox.py
def add_draw_control(
self,
position: str = "top-right",
draw_modes: Optional[List[str]] = None,
edit_modes: Optional[List[str]] = None,
collapsed: bool = False,
**kwargs,
) -> None:
"""Add a drawing control."""
if draw_modes is None:
draw_modes = ["polygon", "line", "rectangle", "circle", "marker"]
if edit_modes is None:
edit_modes = ["select", "drag", "change", "rotate", "delete"]
self.call_js_method(
"addDrawControl",
position=position,
drawModes=draw_modes,
editModes=edit_modes,
collapsed=collapsed,
**kwargs,
)
self._controls = {
**self._controls,
"draw-control": {
"position": position,
"drawModes": draw_modes,
"editModes": edit_modes,
},
}
add_flatgeobuf(self, url, name=None, layer_type=None, paint=None, fit_bounds=True, **kwargs)
¶
Add a FlatGeobuf layer from a URL.
Source code in anymap_ts/mapbox.py
def add_flatgeobuf(
self,
url: str,
name: Optional[str] = None,
layer_type: Optional[str] = None,
paint: Optional[Dict] = None,
fit_bounds: bool = True,
**kwargs,
) -> None:
"""Add a FlatGeobuf layer from a URL."""
layer_id = name or f"flatgeobuf-{len(self._layers)}"
self.call_js_method(
"addFlatGeobuf",
url=url,
name=layer_id,
layerType=layer_type,
paint=paint,
fitBounds=fit_bounds,
**kwargs,
)
self._layers = {
**self._layers,
layer_id: {
"id": layer_id,
"type": "flatgeobuf",
"url": url,
},
}
self._add_to_layer_dict(layer_id, "Vector")
add_geojson(self, data, layer_type=None, paint=None, name=None, fit_bounds=True, **kwargs)
¶
Add GeoJSON data to the map.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data |
Union[str, Dict] |
GeoJSON dict or URL to GeoJSON file. |
required |
layer_type |
Optional[str] |
Mapbox layer type. |
None |
paint |
Optional[Dict] |
Mapbox paint properties. |
None |
name |
Optional[str] |
Layer name. |
None |
fit_bounds |
bool |
Whether to fit map to data bounds. |
True |
**kwargs |
Additional layer options. |
{} |
Source code in anymap_ts/mapbox.py
def add_geojson(
self,
data: Union[str, Dict],
layer_type: Optional[str] = None,
paint: Optional[Dict] = None,
name: Optional[str] = None,
fit_bounds: bool = True,
**kwargs,
) -> None:
"""Add GeoJSON data to the map.
Args:
data: GeoJSON dict or URL to GeoJSON file.
layer_type: Mapbox layer type.
paint: Mapbox paint properties.
name: Layer name.
fit_bounds: Whether to fit map to data bounds.
**kwargs: Additional layer options.
"""
self.add_vector(
data,
layer_type=layer_type,
paint=paint,
name=name,
fit_bounds=fit_bounds,
**kwargs,
)
add_geojson_layer(self, data, name=None, get_fill_color=None, get_line_color=None, get_line_width=1, get_point_radius=5, get_elevation=0, extruded=False, wireframe=False, filled=True, stroked=True, line_width_min_pixels=1, point_radius_min_pixels=2, pickable=True, opacity=0.8, **kwargs)
¶
Add a GeoJSON layer with auto-styling using deck.gl.
Source code in anymap_ts/mapbox.py
def add_geojson_layer(
self,
data: Any,
name: Optional[str] = None,
get_fill_color: Optional[Union[List[int], str]] = None,
get_line_color: Optional[Union[List[int], str]] = None,
get_line_width: Union[float, str] = 1,
get_point_radius: Union[float, str] = 5,
get_elevation: Union[float, str] = 0,
extruded: bool = False,
wireframe: bool = False,
filled: bool = True,
stroked: bool = True,
line_width_min_pixels: float = 1,
point_radius_min_pixels: float = 2,
pickable: bool = True,
opacity: float = 0.8,
**kwargs,
) -> None:
"""Add a GeoJSON layer with auto-styling using deck.gl."""
layer_id = name or f"geojson-deck-{len(self._layers)}"
processed_data = self._process_deck_data(data)
self.call_js_method(
"addGeoJsonLayer",
id=layer_id,
data=processed_data,
getFillColor=get_fill_color or [51, 136, 255, 128],
getLineColor=get_line_color or [0, 0, 0, 255],
getLineWidth=get_line_width,
getPointRadius=get_point_radius,
getElevation=get_elevation,
extruded=extruded,
wireframe=wireframe,
filled=filled,
stroked=stroked,
lineWidthMinPixels=line_width_min_pixels,
pointRadiusMinPixels=point_radius_min_pixels,
pickable=pickable,
opacity=opacity,
**kwargs,
)
self._layers = {
**self._layers,
layer_id: {"id": layer_id, "type": "geojson-deck"},
}
self._add_to_layer_dict(layer_id, "Deck.gl")
add_grid_cell_layer(self, data, name=None, get_position='coordinates', get_color=None, get_elevation=1000, cell_size=200, coverage=1, elevation_scale=1, extruded=True, pickable=True, opacity=0.8, **kwargs)
¶
Add a grid cell layer using deck.gl.
Source code in anymap_ts/mapbox.py
def add_grid_cell_layer(
self,
data: Any,
name: Optional[str] = None,
get_position: Union[str, Any] = "coordinates",
get_color: Optional[Union[List[int], str]] = None,
get_elevation: Union[float, str] = 1000,
cell_size: float = 200,
coverage: float = 1,
elevation_scale: float = 1,
extruded: bool = True,
pickable: bool = True,
opacity: float = 0.8,
**kwargs,
) -> None:
"""Add a grid cell layer using deck.gl."""
layer_id = name or f"gridcell-{len(self._layers)}"
processed_data = self._process_deck_data(data)
self.call_js_method(
"addGridCellLayer",
id=layer_id,
data=processed_data,
getPosition=get_position,
getColor=get_color or [255, 140, 0, 200],
getElevation=get_elevation,
cellSize=cell_size,
coverage=coverage,
elevationScale=elevation_scale,
extruded=extruded,
pickable=pickable,
opacity=opacity,
**kwargs,
)
self._layers = {
**self._layers,
layer_id: {"id": layer_id, "type": "gridcell"},
}
self._add_to_layer_dict(layer_id, "Deck.gl")
add_grid_layer(self, data, name=None, get_position='coordinates', cell_size=200, elevation_scale=4, extruded=True, color_range=None, pickable=True, opacity=0.8, **kwargs)
¶
Add a grid layer using deck.gl.
Source code in anymap_ts/mapbox.py
def add_grid_layer(
self,
data: Any,
name: Optional[str] = None,
get_position: Union[str, Any] = "coordinates",
cell_size: float = 200,
elevation_scale: float = 4,
extruded: bool = True,
color_range: Optional[List[List[int]]] = None,
pickable: bool = True,
opacity: float = 0.8,
**kwargs,
) -> None:
"""Add a grid layer using deck.gl."""
layer_id = name or f"grid-{len(self._layers)}"
processed_data = self._process_deck_data(data)
default_color_range = [
[1, 152, 189],
[73, 227, 206],
[216, 254, 181],
[254, 237, 177],
[254, 173, 84],
[209, 55, 78],
]
self.call_js_method(
"addGridLayer",
id=layer_id,
data=processed_data,
getPosition=get_position,
cellSize=cell_size,
elevationScale=elevation_scale,
extruded=extruded,
colorRange=color_range or default_color_range,
pickable=pickable,
opacity=opacity,
**kwargs,
)
self._layers = {**self._layers, layer_id: {"id": layer_id, "type": "grid"}}
self._add_to_layer_dict(layer_id, "Deck.gl")
add_heatmap(self, data, weight_property=None, radius=20, intensity=1.0, colormap=None, opacity=0.8, name=None, fit_bounds=True, **kwargs)
¶
Add a heatmap layer to the map.
Source code in anymap_ts/mapbox.py
def add_heatmap(
self,
data: Any,
weight_property: Optional[str] = None,
radius: int = 20,
intensity: float = 1.0,
colormap: Optional[List] = None,
opacity: float = 0.8,
name: Optional[str] = None,
fit_bounds: bool = True,
**kwargs,
) -> None:
"""Add a heatmap layer to the map."""
self._validate_opacity(opacity)
layer_id = name or f"heatmap-{len(self._layers)}"
geojson = to_geojson(data)
if geojson.get("type") == "url":
url = geojson["url"]
geojson = fetch_geojson(url)
if colormap is None:
colormap = [
[0, "rgba(33,102,172,0)"],
[0.2, "rgb(103,169,207)"],
[0.4, "rgb(209,229,240)"],
[0.6, "rgb(253,219,199)"],
[0.8, "rgb(239,138,98)"],
[1, "rgb(178,24,43)"],
]
paint = {
"heatmap-radius": radius,
"heatmap-intensity": intensity,
"heatmap-opacity": opacity,
"heatmap-color": [
"interpolate",
["linear"],
["heatmap-density"],
],
}
for stop, color in colormap:
paint["heatmap-color"].extend([stop, color])
if weight_property:
paint["heatmap-weight"] = ["get", weight_property]
bounds = get_bounds(geojson) if fit_bounds else None
self.call_js_method(
"addGeoJSON",
data=geojson,
name=layer_id,
layerType="heatmap",
paint=paint,
fitBounds=fit_bounds,
bounds=bounds,
**kwargs,
)
self._layers = {
**self._layers,
layer_id: {
"id": layer_id,
"type": "heatmap",
"source": f"{layer_id}-source",
"paint": paint,
},
}
self._add_to_layer_dict(layer_id, "Heatmap")
add_hexagon_layer(self, data, name=None, get_position='coordinates', radius=1000, elevation_scale=4, extruded=True, color_range=None, pickable=True, opacity=0.8, **kwargs)
¶
Add a hexagon layer using deck.gl.
Source code in anymap_ts/mapbox.py
def add_hexagon_layer(
self,
data: Any,
name: Optional[str] = None,
get_position: Union[str, Any] = "coordinates",
radius: float = 1000,
elevation_scale: float = 4,
extruded: bool = True,
color_range: Optional[List[List[int]]] = None,
pickable: bool = True,
opacity: float = 0.8,
**kwargs,
) -> None:
"""Add a hexagon layer using deck.gl."""
layer_id = name or f"hexagon-{len(self._layers)}"
processed_data = self._process_deck_data(data)
default_color_range = [
[1, 152, 189],
[73, 227, 206],
[216, 254, 181],
[254, 237, 177],
[254, 173, 84],
[209, 55, 78],
]
self.call_js_method(
"addHexagonLayer",
id=layer_id,
data=processed_data,
getPosition=get_position,
radius=radius,
elevationScale=elevation_scale,
extruded=extruded,
colorRange=color_range or default_color_range,
pickable=pickable,
opacity=opacity,
**kwargs,
)
self._layers = {**self._layers, layer_id: {"id": layer_id, "type": "hexagon"}}
self._add_to_layer_dict(layer_id, "Deck.gl")
add_hover_effect(self, layer_id, highlight_color=None, highlight_opacity=None, highlight_outline_width=2, **kwargs)
¶
Add hover highlight effect to an existing layer.
Source code in anymap_ts/mapbox.py
def add_hover_effect(
self,
layer_id: str,
highlight_color: Optional[str] = None,
highlight_opacity: Optional[float] = None,
highlight_outline_width: float = 2,
**kwargs,
) -> None:
"""Add hover highlight effect to an existing layer."""
self.call_js_method(
"addHoverEffect",
layerId=layer_id,
highlightColor=highlight_color,
highlightOpacity=highlight_opacity,
highlightOutlineWidth=highlight_outline_width,
**kwargs,
)
add_icon_layer(self, data, name=None, get_position='coordinates', get_icon='icon', get_size=20, get_color=None, icon_atlas=None, icon_mapping=None, pickable=True, opacity=1, **kwargs)
¶
Add an icon layer using deck.gl.
Source code in anymap_ts/mapbox.py
def add_icon_layer(
self,
data: Any,
name: Optional[str] = None,
get_position: Union[str, Any] = "coordinates",
get_icon: Union[str, Any] = "icon",
get_size: Union[float, str] = 20,
get_color: Optional[Union[List[int], str]] = None,
icon_atlas: Optional[str] = None,
icon_mapping: Optional[Dict] = None,
pickable: bool = True,
opacity: float = 1,
**kwargs,
) -> None:
"""Add an icon layer using deck.gl."""
layer_id = name or f"icon-{len(self._layers)}"
processed_data = self._process_deck_data(data)
self.call_js_method(
"addIconLayer",
id=layer_id,
data=processed_data,
getPosition=get_position,
getIcon=get_icon,
getSize=get_size,
getColor=get_color or [255, 255, 255, 255],
iconAtlas=icon_atlas,
iconMapping=icon_mapping,
pickable=pickable,
opacity=opacity,
**kwargs,
)
self._layers = {**self._layers, layer_id: {"id": layer_id, "type": "icon"}}
self._add_to_layer_dict(layer_id, "Deck.gl")
add_image(self, name, url)
¶
Load a custom icon image for use in symbol layers.
Source code in anymap_ts/mapbox.py
def add_image(self, name: str, url: str) -> None:
"""Load a custom icon image for use in symbol layers."""
self.call_js_method("addMapImage", name=name, url=url)
add_image_layer(self, url, coordinates, name=None, opacity=1.0, **kwargs)
¶
Add a georeferenced image overlay.
Source code in anymap_ts/mapbox.py
def add_image_layer(
self,
url: str,
coordinates: List[List[float]],
name: Optional[str] = None,
opacity: float = 1.0,
**kwargs,
) -> None:
"""Add a georeferenced image overlay."""
self._validate_opacity(opacity)
layer_id = name or f"image-{len(self._layers)}"
if len(coordinates) != 4:
raise ValueError(
"coordinates must have exactly 4 corner points "
"[top-left, top-right, bottom-right, bottom-left]"
)
self.call_js_method(
"addImageLayer",
id=layer_id,
url=url,
coordinates=coordinates,
opacity=opacity,
**kwargs,
)
self._layers = {
**self._layers,
layer_id: {
"id": layer_id,
"type": "image",
"url": url,
"coordinates": coordinates,
},
}
self._add_to_layer_dict(layer_id, "Raster")
add_layer(self, layer_id, layer_type, source, paint=None, layout=None, before_id=None, **kwargs)
¶
Add a generic layer to the map.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
layer_id |
str |
Unique layer identifier. |
required |
layer_type |
str |
Mapbox layer type. |
required |
source |
Union[str, Dict] |
Source ID or source configuration dict. |
required |
paint |
Optional[Dict] |
Paint properties. |
None |
layout |
Optional[Dict] |
Layout properties. |
None |
before_id |
Optional[str] |
ID of layer to insert before. |
None |
**kwargs |
Additional layer options. |
{} |
Source code in anymap_ts/mapbox.py
def add_layer(
self,
layer_id: str,
layer_type: str,
source: Union[str, Dict],
paint: Optional[Dict] = None,
layout: Optional[Dict] = None,
before_id: Optional[str] = None,
**kwargs,
) -> None:
"""Add a generic layer to the map.
Args:
layer_id: Unique layer identifier.
layer_type: Mapbox layer type.
source: Source ID or source configuration dict.
paint: Paint properties.
layout: Layout properties.
before_id: ID of layer to insert before.
**kwargs: Additional layer options.
"""
layer_config = {
"id": layer_id,
"type": layer_type,
"paint": paint or {},
"layout": layout or {},
**kwargs,
}
if isinstance(source, str):
layer_config["source"] = source
else:
source_id = f"{layer_id}-source"
self._sources = {**self._sources, source_id: source}
self.call_js_method("addSource", source_id, **source)
layer_config["source"] = source_id
self._layers = {**self._layers, layer_id: layer_config}
self.call_js_method("addLayer", beforeId=before_id, **layer_config)
lt = layer_config.get("type", "")
self._add_to_layer_dict(layer_id, "Raster" if lt == "raster" else "Vector")
add_layer_control(self, layers=None, position='top-right', collapsed=True)
¶
Add a layer visibility control.
Source code in anymap_ts/mapbox.py
def add_layer_control(
self,
layers: Optional[List[str]] = None,
position: str = "top-right",
collapsed: bool = True,
) -> None:
"""Add a layer visibility control."""
if layers is None:
layers = list(self._layers.keys())
self.call_js_method(
"addLayerControl",
layers=layers,
position=position,
collapsed=collapsed,
)
self._controls = {
**self._controls,
"layer-control": {
"layers": layers,
"position": position,
"collapsed": collapsed,
},
}
add_legend(self, title, labels, colors, position='bottom-right', opacity=1.0, legend_id=None, **kwargs)
¶
Add a floating legend control to the map.
Source code in anymap_ts/mapbox.py
def add_legend(
self,
title: str,
labels: List[str],
colors: List[str],
position: str = "bottom-right",
opacity: float = 1.0,
legend_id: Optional[str] = None,
**kwargs,
) -> None:
"""Add a floating legend control to the map."""
if len(labels) != len(colors):
raise ValueError("Number of labels must match number of colors")
self._validate_position(position)
for i, color in enumerate(colors):
if not isinstance(color, str) or not color.startswith("#"):
raise ValueError(
f"Color at index {i} must be a hex color string (e.g., '#ff0000')"
)
legend_id = (
legend_id
or f"legend-{len([k for k in self._controls.keys() if k.startswith('legend')])}"
)
legend_items = [
{"label": label, "color": color} for label, color in zip(labels, colors)
]
self.call_js_method(
"addLegend",
id=legend_id,
title=title,
items=legend_items,
position=position,
opacity=opacity,
**kwargs,
)
self._controls = {
**self._controls,
legend_id: {
"type": "legend",
"title": title,
"labels": labels,
"colors": colors,
"position": position,
"opacity": opacity,
},
}
add_lidar_control(self, position='top-right', collapsed=True, title='LiDAR Viewer', point_size=2, opacity=1.0, color_scheme='elevation', use_percentile=True, point_budget=1000000, pickable=False, auto_zoom=True, copc_loading_mode=None, streaming_point_budget=5000000, **kwargs)
¶
Add an interactive LiDAR control panel.
The LiDAR control provides a UI panel for loading, visualizing, and styling LiDAR point cloud files (LAS, LAZ, COPC formats).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
position |
str |
Control position ('top-left', 'top-right', 'bottom-left', 'bottom-right'). |
'top-right' |
collapsed |
bool |
Whether the panel starts collapsed. |
True |
title |
str |
Title displayed on the panel. |
'LiDAR Viewer' |
point_size |
float |
Point size in pixels. |
2 |
opacity |
float |
Layer opacity (0-1). |
1.0 |
color_scheme |
str |
Color scheme ('elevation', 'intensity', 'classification', 'rgb'). |
'elevation' |
use_percentile |
bool |
Use 2-98% percentile for color scaling. |
True |
point_budget |
int |
Maximum number of points to display. |
1000000 |
pickable |
bool |
Enable hover/click interactions. |
False |
auto_zoom |
bool |
Auto-zoom to point cloud after loading. |
True |
copc_loading_mode |
Optional[str] |
COPC loading mode ('full' or 'dynamic'). |
None |
streaming_point_budget |
int |
Point budget for streaming mode. |
5000000 |
**kwargs |
Additional control options. |
{} |
Examples:
>>> from anymap_ts import MapboxMap
>>> m = MapboxMap(pitch=60)
>>> m.add_lidar_control(color_scheme="classification", pickable=True)
Source code in anymap_ts/mapbox.py
def add_lidar_control(
self,
position: str = "top-right",
collapsed: bool = True,
title: str = "LiDAR Viewer",
point_size: float = 2,
opacity: float = 1.0,
color_scheme: str = "elevation",
use_percentile: bool = True,
point_budget: int = 1000000,
pickable: bool = False,
auto_zoom: bool = True,
copc_loading_mode: Optional[str] = None,
streaming_point_budget: int = 5000000,
**kwargs,
) -> None:
"""Add an interactive LiDAR control panel.
The LiDAR control provides a UI panel for loading, visualizing, and
styling LiDAR point cloud files (LAS, LAZ, COPC formats).
Args:
position: Control position ('top-left', 'top-right', 'bottom-left', 'bottom-right').
collapsed: Whether the panel starts collapsed.
title: Title displayed on the panel.
point_size: Point size in pixels.
opacity: Layer opacity (0-1).
color_scheme: Color scheme ('elevation', 'intensity', 'classification', 'rgb').
use_percentile: Use 2-98% percentile for color scaling.
point_budget: Maximum number of points to display.
pickable: Enable hover/click interactions.
auto_zoom: Auto-zoom to point cloud after loading.
copc_loading_mode: COPC loading mode ('full' or 'dynamic').
streaming_point_budget: Point budget for streaming mode.
**kwargs: Additional control options.
Example:
>>> from anymap_ts import MapboxMap
>>> m = MapboxMap(pitch=60)
>>> m.add_lidar_control(color_scheme="classification", pickable=True)
"""
self.call_js_method(
"addLidarControl",
position=position,
collapsed=collapsed,
title=title,
pointSize=point_size,
opacity=opacity,
colorScheme=color_scheme,
usePercentile=use_percentile,
pointBudget=point_budget,
pickable=pickable,
autoZoom=auto_zoom,
copcLoadingMode=copc_loading_mode,
streamingPointBudget=streaming_point_budget,
**kwargs,
)
self._controls = {
**self._controls,
"lidar-control": {"position": position, "collapsed": collapsed},
}
add_lidar_layer(self, source, name=None, color_scheme='elevation', point_size=2, opacity=1.0, pickable=True, auto_zoom=True, streaming_mode=True, point_budget=1000000, **kwargs)
¶
Load and display a LiDAR file from URL or local path.
Supports LAS, LAZ, and COPC (Cloud-Optimized Point Cloud) formats. For local files, the file is read and sent as base64 to JavaScript. For URLs, the data is loaded directly via streaming when possible.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
source |
Union[str, Path] |
URL or local file path to the LiDAR file. |
required |
name |
Optional[str] |
Layer identifier. If None, auto-generated. |
None |
color_scheme |
str |
Color scheme ('elevation', 'intensity', 'classification', 'rgb'). |
'elevation' |
point_size |
float |
Point size in pixels. |
2 |
opacity |
float |
Layer opacity (0-1). |
1.0 |
pickable |
bool |
Enable hover/click interactions. |
True |
auto_zoom |
bool |
Auto-zoom to point cloud after loading. |
True |
streaming_mode |
bool |
Use streaming mode for large COPC files. |
True |
point_budget |
int |
Maximum number of points to display. |
1000000 |
**kwargs |
Additional layer options. |
{} |
Examples:
>>> from anymap_ts import MapboxMap
>>> m = MapboxMap(center=[-123.07, 44.05], zoom=14, pitch=60)
>>> m.add_lidar_layer(
... source="https://s3.amazonaws.com/hobu-lidar/autzen-classified.copc.laz",
... name="autzen",
... color_scheme="classification",
... )
Source code in anymap_ts/mapbox.py
def add_lidar_layer(
self,
source: Union[str, Path],
name: Optional[str] = None,
color_scheme: str = "elevation",
point_size: float = 2,
opacity: float = 1.0,
pickable: bool = True,
auto_zoom: bool = True,
streaming_mode: bool = True,
point_budget: int = 1000000,
**kwargs,
) -> None:
"""Load and display a LiDAR file from URL or local path.
Supports LAS, LAZ, and COPC (Cloud-Optimized Point Cloud) formats.
For local files, the file is read and sent as base64 to JavaScript.
For URLs, the data is loaded directly via streaming when possible.
Args:
source: URL or local file path to the LiDAR file.
name: Layer identifier. If None, auto-generated.
color_scheme: Color scheme ('elevation', 'intensity', 'classification', 'rgb').
point_size: Point size in pixels.
opacity: Layer opacity (0-1).
pickable: Enable hover/click interactions.
auto_zoom: Auto-zoom to point cloud after loading.
streaming_mode: Use streaming mode for large COPC files.
point_budget: Maximum number of points to display.
**kwargs: Additional layer options.
Example:
>>> from anymap_ts import MapboxMap
>>> m = MapboxMap(center=[-123.07, 44.05], zoom=14, pitch=60)
>>> m.add_lidar_layer(
... source="https://s3.amazonaws.com/hobu-lidar/autzen-classified.copc.laz",
... name="autzen",
... color_scheme="classification",
... )
"""
import base64
layer_id = name or f"lidar-{len(self._layers)}"
# Check if source is a local file
source_path = Path(source) if isinstance(source, (str, Path)) else None
is_local = source_path is not None and source_path.exists()
if is_local:
# Read local file and encode as base64
with open(source_path, "rb") as f:
file_data = f.read()
source_b64 = base64.b64encode(file_data).decode("utf-8")
self.call_js_method(
"addLidarLayer",
source=source_b64,
name=layer_id,
isBase64=True,
filename=source_path.name,
colorScheme=color_scheme,
pointSize=point_size,
opacity=opacity,
pickable=pickable,
autoZoom=auto_zoom,
streamingMode=streaming_mode,
pointBudget=point_budget,
**kwargs,
)
else:
# Load from URL
self.call_js_method(
"addLidarLayer",
source=str(source),
name=layer_id,
isBase64=False,
colorScheme=color_scheme,
pointSize=point_size,
opacity=opacity,
pickable=pickable,
autoZoom=auto_zoom,
streamingMode=streaming_mode,
pointBudget=point_budget,
**kwargs,
)
self._layers = {
**self._layers,
layer_id: {
"id": layer_id,
"type": "lidar",
"source": str(source),
},
}
add_line_layer(self, data, name=None, get_source_position='sourcePosition', get_target_position='targetPosition', get_color=None, get_width=1, width_min_pixels=1, pickable=True, opacity=0.8, **kwargs)
¶
Add a line layer using deck.gl.
Source code in anymap_ts/mapbox.py
def add_line_layer(
self,
data: Any,
name: Optional[str] = None,
get_source_position: Union[str, Any] = "sourcePosition",
get_target_position: Union[str, Any] = "targetPosition",
get_color: Optional[Union[List[int], str]] = None,
get_width: Union[float, str] = 1,
width_min_pixels: float = 1,
pickable: bool = True,
opacity: float = 0.8,
**kwargs,
) -> None:
"""Add a line layer using deck.gl."""
layer_id = name or f"line-{len(self._layers)}"
processed_data = self._process_deck_data(data)
self.call_js_method(
"addLineLayer",
id=layer_id,
data=processed_data,
getSourcePosition=get_source_position,
getTargetPosition=get_target_position,
getColor=get_color or [51, 136, 255, 200],
getWidth=get_width,
widthMinPixels=width_min_pixels,
pickable=pickable,
opacity=opacity,
**kwargs,
)
self._layers = {**self._layers, layer_id: {"id": layer_id, "type": "line"}}
self._add_to_layer_dict(layer_id, "Deck.gl")
add_marker(self, lng, lat, popup=None, tooltip=None, color='#3388ff', draggable=False, scale=1.0, popup_max_width='240px', tooltip_max_width='240px', name=None, **kwargs)
¶
Add a single marker to the map.
Source code in anymap_ts/mapbox.py
def add_marker(
self,
lng: float,
lat: float,
popup: Optional[str] = None,
tooltip: Optional[str] = None,
color: str = "#3388ff",
draggable: bool = False,
scale: float = 1.0,
popup_max_width: str = "240px",
tooltip_max_width: str = "240px",
name: Optional[str] = None,
**kwargs,
) -> str:
"""Add a single marker to the map."""
marker_id = name or f"marker-{len(self._layers)}"
self.call_js_method(
"addMarker",
lng,
lat,
id=marker_id,
popup=popup,
tooltip=tooltip,
color=color,
draggable=draggable,
scale=scale,
popupMaxWidth=popup_max_width,
tooltipMaxWidth=tooltip_max_width,
**kwargs,
)
self._layers = {
**self._layers,
marker_id: {
"id": marker_id,
"type": "marker",
"lngLat": [lng, lat],
},
}
self._add_to_layer_dict(marker_id, "Markers")
return marker_id
add_markers(self, data, lng_column=None, lat_column=None, popup_column=None, tooltip_column=None, color='#3388ff', scale=1.0, popup_max_width='240px', tooltip_max_width='240px', draggable=False, name=None, **kwargs)
¶
Add multiple markers from data.
Source code in anymap_ts/mapbox.py
def add_markers(
self,
data: Any,
lng_column: Optional[str] = None,
lat_column: Optional[str] = None,
popup_column: Optional[str] = None,
tooltip_column: Optional[str] = None,
color: str = "#3388ff",
scale: float = 1.0,
popup_max_width: str = "240px",
tooltip_max_width: str = "240px",
draggable: bool = False,
name: Optional[str] = None,
**kwargs,
) -> str:
"""Add multiple markers from data."""
layer_id = name or f"markers-{len(self._layers)}"
markers = []
if hasattr(data, "geometry"):
for _, row in data.iterrows():
geom = row.geometry
if geom.geom_type == "Point":
marker = {"lngLat": [geom.x, geom.y]}
if popup_column and popup_column in row:
marker["popup"] = str(row[popup_column])
if tooltip_column and tooltip_column in row:
marker["tooltip"] = str(row[tooltip_column])
markers.append(marker)
elif isinstance(data, dict) and data.get("type") == "FeatureCollection":
for feature in data.get("features", []):
geom = feature.get("geometry", {})
if geom.get("type") == "Point":
coords = geom.get("coordinates", [])
marker = {"lngLat": coords[:2]}
props = feature.get("properties", {})
if popup_column and popup_column in props:
marker["popup"] = str(props[popup_column])
if tooltip_column and tooltip_column in props:
marker["tooltip"] = str(props[tooltip_column])
markers.append(marker)
elif isinstance(data, list):
lng_keys = ["lng", "lon", "longitude", "x"]
lat_keys = ["lat", "latitude", "y"]
for item in data:
if not isinstance(item, dict):
continue
lng_val = None
lat_val = None
if lng_column and lng_column in item:
lng_val = item[lng_column]
else:
for key in lng_keys:
if key in item:
lng_val = item[key]
break
if lat_column and lat_column in item:
lat_val = item[lat_column]
else:
for key in lat_keys:
if key in item:
lat_val = item[key]
break
if lng_val is not None and lat_val is not None:
marker = {"lngLat": [float(lng_val), float(lat_val)]}
if popup_column and popup_column in item:
marker["popup"] = str(item[popup_column])
if tooltip_column and tooltip_column in item:
marker["tooltip"] = str(item[tooltip_column])
markers.append(marker)
if not markers:
raise ValueError("No valid point data found in input")
self.call_js_method(
"addMarkers",
id=layer_id,
markers=markers,
color=color,
scale=scale,
popupMaxWidth=popup_max_width,
tooltipMaxWidth=tooltip_max_width,
draggable=draggable,
**kwargs,
)
self._layers = {
**self._layers,
layer_id: {
"id": layer_id,
"type": "markers",
"count": len(markers),
},
}
self._add_to_layer_dict(layer_id, "Markers")
return layer_id
add_measure_control(self, position='top-right', collapsed=True, default_mode='distance', distance_unit='kilometers', area_unit='square-kilometers', line_color='#3b82f6', fill_color='rgba(59, 130, 246, 0.2)', **kwargs)
¶
Add a measurement control.
Source code in anymap_ts/mapbox.py
def add_measure_control(
self,
position: str = "top-right",
collapsed: bool = True,
default_mode: str = "distance",
distance_unit: str = "kilometers",
area_unit: str = "square-kilometers",
line_color: str = "#3b82f6",
fill_color: str = "rgba(59, 130, 246, 0.2)",
**kwargs,
) -> None:
"""Add a measurement control."""
self._validate_position(position)
self.call_js_method(
"addMeasureControl",
position=position,
collapsed=collapsed,
defaultMode=default_mode,
distanceUnit=distance_unit,
areaUnit=area_unit,
lineColor=line_color,
fillColor=fill_color,
**kwargs,
)
self._controls = {
**self._controls,
"measure-control": {
"type": "measure-control",
"position": position,
"collapsed": collapsed,
},
}
add_opacity_slider(self, layer_id, position='top-right', label=None)
¶
Add a UI slider to control layer opacity.
Source code in anymap_ts/mapbox.py
def add_opacity_slider(
self,
layer_id: str,
position: str = "top-right",
label: Optional[str] = None,
) -> None:
"""Add a UI slider to control layer opacity."""
self.call_js_method(
"addOpacitySlider",
layerId=layer_id,
position=position,
label=label or layer_id,
)
add_path_layer(self, data, name=None, get_path='path', get_color=None, get_width=1, width_scale=1, width_min_pixels=1, pickable=True, opacity=0.8, **kwargs)
¶
Add a path layer using deck.gl.
Source code in anymap_ts/mapbox.py
def add_path_layer(
self,
data: Any,
name: Optional[str] = None,
get_path: Union[str, Any] = "path",
get_color: Optional[Union[List[int], str]] = None,
get_width: Union[float, str] = 1,
width_scale: float = 1,
width_min_pixels: float = 1,
pickable: bool = True,
opacity: float = 0.8,
**kwargs,
) -> None:
"""Add a path layer using deck.gl."""
layer_id = name or f"path-{len(self._layers)}"
processed_data = self._process_deck_data(data)
self.call_js_method(
"addPathLayer",
id=layer_id,
data=processed_data,
getPath=get_path,
getColor=get_color or [51, 136, 255, 200],
getWidth=get_width,
widthScale=width_scale,
widthMinPixels=width_min_pixels,
pickable=pickable,
opacity=opacity,
**kwargs,
)
self._layers = {**self._layers, layer_id: {"id": layer_id, "type": "path"}}
self._add_to_layer_dict(layer_id, "Deck.gl")
add_pmtiles_control(self, position='top-right', collapsed=True, default_url=None, load_default_url=False, default_opacity=1.0, default_fill_color='steelblue', default_line_color='#333', default_pickable=True, **kwargs)
¶
Add a PMTiles layer control.
Source code in anymap_ts/mapbox.py
def add_pmtiles_control(
self,
position: str = "top-right",
collapsed: bool = True,
default_url: Optional[str] = None,
load_default_url: bool = False,
default_opacity: float = 1.0,
default_fill_color: str = "steelblue",
default_line_color: str = "#333",
default_pickable: bool = True,
**kwargs,
) -> None:
"""Add a PMTiles layer control."""
self.call_js_method(
"addPMTilesControl",
position=position,
collapsed=collapsed,
defaultUrl=default_url or "",
loadDefaultUrl=load_default_url,
defaultOpacity=default_opacity,
defaultFillColor=default_fill_color,
defaultLineColor=default_line_color,
defaultPickable=default_pickable,
**kwargs,
)
self._controls = {
**self._controls,
"pmtiles-control": {"position": position, "collapsed": collapsed},
}
add_pmtiles_layer(self, url, layer_id=None, style=None, opacity=1.0, visible=True, fit_bounds=False, source_type='vector', **kwargs)
¶
Add a PMTiles layer for efficient vector or raster tile serving.
Source code in anymap_ts/mapbox.py
def add_pmtiles_layer(
self,
url: str,
layer_id: Optional[str] = None,
style: Optional[Dict[str, Any]] = None,
opacity: float = 1.0,
visible: bool = True,
fit_bounds: bool = False,
source_type: str = "vector",
**kwargs,
) -> None:
"""Add a PMTiles layer for efficient vector or raster tile serving."""
layer_id = layer_id or f"pmtiles-{len(self._layers)}"
self.call_js_method(
"addPMTilesLayer",
url=url,
id=layer_id,
style=style or {},
opacity=opacity,
visible=visible,
fitBounds=fit_bounds,
sourceType=source_type,
name=layer_id,
**kwargs,
)
self._layers = {
**self._layers,
layer_id: {
"id": layer_id,
"type": "pmtiles",
"url": url,
"source_type": source_type,
},
}
category = "Vector" if source_type == "vector" else "Raster"
self._add_to_layer_dict(layer_id, category)
add_point_cloud_layer(self, data, name=None, get_position='position', get_color=None, get_normal=None, point_size=2, size_units='pixels', pickable=True, opacity=1.0, material=True, coordinate_system=None, coordinate_origin=None, **kwargs)
¶
Add a point cloud layer for 3D point visualization using deck.gl.
Point cloud layers render large collections of 3D points, ideal for LiDAR data, photogrammetry outputs, or any 3D point dataset.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data |
Any |
Array of point data with positions. Each point should have x, y, z coordinates (or position array). |
required |
name |
Optional[str] |
Layer ID. If None, auto-generated. |
None |
get_position |
Union[str, Any] |
Accessor for point position [x, y, z]. Can be a string (property name) or a value. |
'position' |
get_color |
Optional[Union[List[int], str]] |
Accessor or value for point color [r, g, b, a]. Default: [255, 255, 255, 255] (white). |
None |
get_normal |
Optional[Union[str, Any]] |
Accessor for point normal [nx, ny, nz] for lighting. Default: [0, 0, 1] (pointing up). |
None |
point_size |
float |
Point size in pixels or meters (depends on size_units). |
2 |
size_units |
str |
Size units: 'pixels', 'meters', or 'common'. |
'pixels' |
pickable |
bool |
Whether layer responds to hover/click events. |
True |
opacity |
float |
Layer opacity (0-1). |
1.0 |
material |
bool |
Whether to enable lighting effects. |
True |
coordinate_system |
Optional[int] |
Coordinate system for positions. |
None |
coordinate_origin |
Optional[List[float]] |
Origin for coordinate system [x, y, z]. |
None |
**kwargs |
Additional PointCloudLayer props. |
{} |
Examples:
>>> from anymap_ts import MapboxMap
>>> m = MapboxMap(pitch=45)
>>> points = [
... {"position": [-122.4, 37.8, 100], "color": [255, 0, 0, 255]},
... {"position": [-122.3, 37.7, 200], "color": [0, 255, 0, 255]},
... ]
>>> m.add_point_cloud_layer(points, point_size=5)
Source code in anymap_ts/mapbox.py
def add_point_cloud_layer(
self,
data: Any,
name: Optional[str] = None,
get_position: Union[str, Any] = "position",
get_color: Optional[Union[List[int], str]] = None,
get_normal: Optional[Union[str, Any]] = None,
point_size: float = 2,
size_units: str = "pixels",
pickable: bool = True,
opacity: float = 1.0,
material: bool = True,
coordinate_system: Optional[int] = None,
coordinate_origin: Optional[List[float]] = None,
**kwargs,
) -> None:
"""Add a point cloud layer for 3D point visualization using deck.gl.
Point cloud layers render large collections of 3D points, ideal for
LiDAR data, photogrammetry outputs, or any 3D point dataset.
Args:
data: Array of point data with positions. Each point should have
x, y, z coordinates (or position array).
name: Layer ID. If None, auto-generated.
get_position: Accessor for point position [x, y, z].
Can be a string (property name) or a value.
get_color: Accessor or value for point color [r, g, b, a].
Default: [255, 255, 255, 255] (white).
get_normal: Accessor for point normal [nx, ny, nz] for lighting.
Default: [0, 0, 1] (pointing up).
point_size: Point size in pixels or meters (depends on size_units).
size_units: Size units: 'pixels', 'meters', or 'common'.
pickable: Whether layer responds to hover/click events.
opacity: Layer opacity (0-1).
material: Whether to enable lighting effects.
coordinate_system: Coordinate system for positions.
coordinate_origin: Origin for coordinate system [x, y, z].
**kwargs: Additional PointCloudLayer props.
Example:
>>> from anymap_ts import MapboxMap
>>> m = MapboxMap(pitch=45)
>>> points = [
... {"position": [-122.4, 37.8, 100], "color": [255, 0, 0, 255]},
... {"position": [-122.3, 37.7, 200], "color": [0, 255, 0, 255]},
... ]
>>> m.add_point_cloud_layer(points, point_size=5)
"""
layer_id = name or f"pointcloud-{len(self._layers)}"
processed_data = self._process_deck_data(data)
self.call_js_method(
"addPointCloudLayer",
id=layer_id,
data=processed_data,
getPosition=get_position,
getColor=get_color or [255, 255, 255, 255],
getNormal=get_normal,
pointSize=point_size,
sizeUnits=size_units,
pickable=pickable,
opacity=opacity,
material=material,
coordinateSystem=coordinate_system,
coordinateOrigin=coordinate_origin,
**kwargs,
)
self._layers = {
**self._layers,
layer_id: {
"id": layer_id,
"type": "pointcloud",
},
}
self._add_to_layer_dict(layer_id, "Deck.gl")
add_polygon_layer(self, data, name=None, get_polygon='polygon', get_fill_color=None, get_line_color=None, get_line_width=1, get_elevation=0, extruded=False, wireframe=False, filled=True, stroked=True, line_width_min_pixels=1, pickable=True, opacity=0.5, **kwargs)
¶
Add a polygon layer using deck.gl.
Source code in anymap_ts/mapbox.py
def add_polygon_layer(
self,
data: Any,
name: Optional[str] = None,
get_polygon: Union[str, Any] = "polygon",
get_fill_color: Optional[Union[List[int], str]] = None,
get_line_color: Optional[Union[List[int], str]] = None,
get_line_width: Union[float, str] = 1,
get_elevation: Union[float, str] = 0,
extruded: bool = False,
wireframe: bool = False,
filled: bool = True,
stroked: bool = True,
line_width_min_pixels: float = 1,
pickable: bool = True,
opacity: float = 0.5,
**kwargs,
) -> None:
"""Add a polygon layer using deck.gl."""
layer_id = name or f"polygon-{len(self._layers)}"
processed_data = self._process_deck_data(data)
self.call_js_method(
"addPolygonLayer",
id=layer_id,
data=processed_data,
getPolygon=get_polygon,
getFillColor=get_fill_color or [51, 136, 255, 128],
getLineColor=get_line_color or [0, 0, 255, 255],
getLineWidth=get_line_width,
getElevation=get_elevation,
extruded=extruded,
wireframe=wireframe,
filled=filled,
stroked=stroked,
lineWidthMinPixels=line_width_min_pixels,
pickable=pickable,
opacity=opacity,
**kwargs,
)
self._layers = {**self._layers, layer_id: {"id": layer_id, "type": "polygon"}}
self._add_to_layer_dict(layer_id, "Deck.gl")
add_popup(self, layer_id, properties=None, template=None, **kwargs)
¶
Add popup on click for a layer.
Source code in anymap_ts/mapbox.py
def add_popup(
self,
layer_id: str,
properties: Optional[List[str]] = None,
template: Optional[str] = None,
**kwargs,
) -> None:
"""Add popup on click for a layer."""
self.call_js_method(
"addPopup",
layerId=layer_id,
properties=properties,
template=template,
**kwargs,
)
add_print_control(self, position='top-right', collapsed=True, format='png', filename='map-export', include_north_arrow=False, include_scale_bar=False, **kwargs)
¶
Add a print/export control.
Source code in anymap_ts/mapbox.py
def add_print_control(
self,
position: str = "top-right",
collapsed: bool = True,
format: str = "png",
filename: str = "map-export",
include_north_arrow: bool = False,
include_scale_bar: bool = False,
**kwargs,
) -> None:
"""Add a print/export control."""
self._validate_position(position)
self.call_js_method(
"addPrintControl",
position=position,
collapsed=collapsed,
format=format,
filename=filename,
includeNorthArrow=include_north_arrow,
includeScaleBar=include_scale_bar,
**kwargs,
)
self._controls = {
**self._controls,
"print-control": {
"type": "print-control",
"position": position,
"collapsed": collapsed,
},
}
add_raster(self, source, name=None, attribution='', indexes=None, colormap=None, vmin=None, vmax=None, nodata=None, fit_bounds=True, **kwargs)
¶
Add a raster layer from a local file using localtileserver.
Source code in anymap_ts/mapbox.py
def add_raster(
self,
source: str,
name: Optional[str] = None,
attribution: str = "",
indexes: Optional[List[int]] = None,
colormap: Optional[str] = None,
vmin: Optional[float] = None,
vmax: Optional[float] = None,
nodata: Optional[float] = None,
fit_bounds: bool = True,
**kwargs,
) -> None:
"""Add a raster layer from a local file using localtileserver."""
try:
from localtileserver import TileClient
except ImportError:
raise ImportError(
"localtileserver is required for local raster support. "
"Install with: pip install anymap-ts[raster]"
)
client = TileClient(source)
tile_params = {}
if indexes:
tile_params["indexes"] = indexes
if colormap:
tile_params["colormap"] = colormap
if vmin is not None or vmax is not None:
tile_params["vmin"] = vmin if vmin is not None else client.min
tile_params["vmax"] = vmax if vmax is not None else client.max
if nodata is not None:
tile_params["nodata"] = nodata
tile_url = client.get_tile_url(**tile_params)
layer_name = name or Path(source).stem
self.add_tile_layer(
tile_url,
name=layer_name,
attribution=attribution,
**kwargs,
)
if fit_bounds:
bounds = client.bounds()
if bounds:
self.fit_bounds([bounds[0], bounds[1], bounds[2], bounds[3]])
add_scatterplot_layer(self, data, name=None, get_position='coordinates', get_radius=5, get_fill_color=None, get_line_color=None, radius_scale=1, radius_min_pixels=1, radius_max_pixels=100, line_width_min_pixels=1, stroked=True, filled=True, pickable=True, opacity=0.8, **kwargs)
¶
Add a scatterplot layer using deck.gl.
Source code in anymap_ts/mapbox.py
def add_scatterplot_layer(
self,
data: Any,
name: Optional[str] = None,
get_position: Union[str, Any] = "coordinates",
get_radius: Union[float, str] = 5,
get_fill_color: Optional[Union[List[int], str]] = None,
get_line_color: Optional[Union[List[int], str]] = None,
radius_scale: float = 1,
radius_min_pixels: float = 1,
radius_max_pixels: float = 100,
line_width_min_pixels: float = 1,
stroked: bool = True,
filled: bool = True,
pickable: bool = True,
opacity: float = 0.8,
**kwargs,
) -> None:
"""Add a scatterplot layer using deck.gl."""
layer_id = name or f"scatterplot-{len(self._layers)}"
processed_data = self._process_deck_data(data)
self.call_js_method(
"addScatterplotLayer",
id=layer_id,
data=processed_data,
getPosition=get_position,
getRadius=get_radius,
getFillColor=get_fill_color or [51, 136, 255, 200],
getLineColor=get_line_color or [255, 255, 255, 255],
radiusScale=radius_scale,
radiusMinPixels=radius_min_pixels,
radiusMaxPixels=radius_max_pixels,
lineWidthMinPixels=line_width_min_pixels,
stroked=stroked,
filled=filled,
pickable=pickable,
opacity=opacity,
**kwargs,
)
self._layers = {
**self._layers,
layer_id: {"id": layer_id, "type": "scatterplot"},
}
self._add_to_layer_dict(layer_id, "Deck.gl")
add_screen_grid_layer(self, data, name=None, get_position='coordinates', get_weight=1, cell_size_pixels=50, color_range=None, pickable=True, opacity=0.8, **kwargs)
¶
Add a screen grid layer using deck.gl.
Source code in anymap_ts/mapbox.py
def add_screen_grid_layer(
self,
data: Any,
name: Optional[str] = None,
get_position: Union[str, Any] = "coordinates",
get_weight: Union[float, str] = 1,
cell_size_pixels: float = 50,
color_range: Optional[List[List[int]]] = None,
pickable: bool = True,
opacity: float = 0.8,
**kwargs,
) -> None:
"""Add a screen grid layer using deck.gl."""
layer_id = name or f"screengrid-{len(self._layers)}"
processed_data = self._process_deck_data(data)
default_color_range = [
[255, 255, 178, 25],
[254, 217, 118, 85],
[254, 178, 76, 127],
[253, 141, 60, 170],
[240, 59, 32, 212],
[189, 0, 38, 255],
]
self.call_js_method(
"addScreenGridLayer",
id=layer_id,
data=processed_data,
getPosition=get_position,
getWeight=get_weight,
cellSizePixels=cell_size_pixels,
colorRange=color_range or default_color_range,
pickable=pickable,
opacity=opacity,
**kwargs,
)
self._layers = {
**self._layers,
layer_id: {"id": layer_id, "type": "screengrid"},
}
self._add_to_layer_dict(layer_id, "Deck.gl")
add_search_control(self, position='top-left', placeholder='Search places...', collapsed=True, fly_to_zoom=14, show_marker=True, marker_color='#4264fb', **kwargs)
¶
Add a search/geocoder control.
Source code in anymap_ts/mapbox.py
def add_search_control(
self,
position: str = "top-left",
placeholder: str = "Search places...",
collapsed: bool = True,
fly_to_zoom: int = 14,
show_marker: bool = True,
marker_color: str = "#4264fb",
**kwargs,
) -> None:
"""Add a search/geocoder control."""
self._validate_position(position)
self.call_js_method(
"addSearchControl",
position=position,
placeholder=placeholder,
collapsed=collapsed,
flyToZoom=fly_to_zoom,
showMarker=show_marker,
markerColor=marker_color,
**kwargs,
)
self._controls = {
**self._controls,
"search-control": {
"type": "search-control",
"position": position,
"collapsed": collapsed,
},
}
add_solid_polygon_layer(self, data, name=None, get_polygon='polygon', get_fill_color=None, get_line_color=None, get_elevation=0, filled=True, extruded=False, wireframe=False, elevation_scale=1, pickable=True, opacity=0.8, **kwargs)
¶
Add a solid polygon layer using deck.gl.
Source code in anymap_ts/mapbox.py
def add_solid_polygon_layer(
self,
data: Any,
name: Optional[str] = None,
get_polygon: Union[str, Any] = "polygon",
get_fill_color: Optional[Union[List[int], str]] = None,
get_line_color: Optional[Union[List[int], str]] = None,
get_elevation: Union[float, str] = 0,
filled: bool = True,
extruded: bool = False,
wireframe: bool = False,
elevation_scale: float = 1,
pickable: bool = True,
opacity: float = 0.8,
**kwargs,
) -> None:
"""Add a solid polygon layer using deck.gl."""
layer_id = name or f"solidpolygon-{len(self._layers)}"
processed_data = self._process_deck_data(data)
self.call_js_method(
"addSolidPolygonLayer",
id=layer_id,
data=processed_data,
getPolygon=get_polygon,
getFillColor=get_fill_color or [51, 136, 255, 128],
getLineColor=get_line_color or [0, 0, 0, 255],
getElevation=get_elevation,
filled=filled,
extruded=extruded,
wireframe=wireframe,
elevationScale=elevation_scale,
pickable=pickable,
opacity=opacity,
**kwargs,
)
self._layers = {
**self._layers,
layer_id: {"id": layer_id, "type": "solidpolygon"},
}
self._add_to_layer_dict(layer_id, "Deck.gl")
add_split_map(self, left_layer, right_layer, position=50)
¶
Add a split map comparison view with a draggable divider.
Source code in anymap_ts/mapbox.py
def add_split_map(
self,
left_layer: str,
right_layer: str,
position: int = 50,
) -> None:
"""Add a split map comparison view with a draggable divider."""
if not 0 <= position <= 100:
raise ValueError(f"position must be between 0 and 100, got {position}")
self.call_js_method(
"addSplitMap",
leftLayer=left_layer,
rightLayer=right_layer,
position=position,
)
add_stac_layer(self, url=None, item=None, assets=None, colormap=None, rescale=None, opacity=1.0, layer_id=None, titiler_endpoint='https://titiler.xyz', attribution='STAC', fit_bounds=True, **kwargs)
¶
Add a STAC (SpatioTemporal Asset Catalog) layer to the map.
Source code in anymap_ts/mapbox.py
def add_stac_layer(
self,
url: Optional[str] = None,
item: Optional[Any] = None,
assets: Optional[List[str]] = None,
colormap: Optional[str] = None,
rescale: Optional[List[float]] = None,
opacity: float = 1.0,
layer_id: Optional[str] = None,
titiler_endpoint: str = "https://titiler.xyz",
attribution: str = "STAC",
fit_bounds: bool = True,
**kwargs,
) -> None:
"""Add a STAC (SpatioTemporal Asset Catalog) layer to the map."""
if url is None and item is None:
raise ValueError("Either 'url' or 'item' must be provided")
if url is not None and item is not None:
raise ValueError("Provide either 'url' or 'item', not both")
if item is not None:
try:
if hasattr(item, "to_dict") and hasattr(item, "self_href"):
stac_url = item.self_href
if not stac_url and hasattr(item, "links"):
for link in item.links:
if link.rel == "self":
stac_url = link.href
break
if not stac_url:
raise ValueError("STAC item must have a self_href or self link")
else:
raise ValueError(
"Item must be a pystac Item object with to_dict() and self_href"
)
except Exception as e:
raise ValueError(f"Invalid STAC item: {e}")
else:
stac_url = url
tile_params = {"url": stac_url}
if assets:
tile_params["assets"] = ",".join(assets)
if colormap:
tile_params["colormap_name"] = colormap
if rescale:
if len(rescale) == 2:
tile_params["rescale"] = f"{rescale[0]},{rescale[1]}"
else:
raise ValueError("rescale must be a list of two values [min, max]")
query_string = urlencode(tile_params)
tile_url = f"{titiler_endpoint.rstrip('/')}/stac/tiles/{{z}}/{{x}}/{{y}}?{query_string}"
layer_name = layer_id or f"stac-{len(self._layers)}"
self.add_tile_layer(
url=tile_url,
name=layer_name,
attribution=attribution,
**kwargs,
)
if fit_bounds and item is not None:
try:
bbox = item.bbox
if bbox and len(bbox) == 4:
self.fit_bounds([[bbox[0], bbox[1]], [bbox[2], bbox[3]]])
except Exception:
pass
add_style_switcher(self, styles, position='top-right')
¶
Add a dropdown to switch between map styles.
Source code in anymap_ts/mapbox.py
def add_style_switcher(
self,
styles: Dict[str, str],
position: str = "top-right",
) -> None:
"""Add a dropdown to switch between map styles."""
self.call_js_method(
"addStyleSwitcher",
styles=styles,
position=position,
)
add_swipe_map(self, left_layer, right_layer)
¶
Add a drag-to-compare swipe control for two layers.
Source code in anymap_ts/mapbox.py
def add_swipe_map(self, left_layer: str, right_layer: str) -> None:
"""Add a drag-to-compare swipe control for two layers."""
self.call_js_method(
"addSwipeMap",
leftLayer=left_layer,
rightLayer=right_layer,
)
add_terrain(self, exaggeration=1.0, source='mapbox-dem')
¶
Add 3D terrain to the map.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
exaggeration |
float |
Terrain exaggeration factor. |
1.0 |
source |
str |
Terrain source ID. |
'mapbox-dem' |
Source code in anymap_ts/mapbox.py
def add_terrain(
self, exaggeration: float = 1.0, source: str = "mapbox-dem"
) -> None:
"""Add 3D terrain to the map.
Args:
exaggeration: Terrain exaggeration factor.
source: Terrain source ID.
"""
self.call_js_method("addTerrain", source=source, exaggeration=exaggeration)
add_text_layer(self, data, name=None, get_position='coordinates', get_text='text', get_size=12, get_color=None, get_angle=0, text_anchor='middle', alignment_baseline='center', pickable=True, opacity=1, **kwargs)
¶
Add a text layer using deck.gl.
Source code in anymap_ts/mapbox.py
def add_text_layer(
self,
data: Any,
name: Optional[str] = None,
get_position: Union[str, Any] = "coordinates",
get_text: Union[str, Any] = "text",
get_size: Union[float, str] = 12,
get_color: Optional[Union[List[int], str]] = None,
get_angle: Union[float, str] = 0,
text_anchor: str = "middle",
alignment_baseline: str = "center",
pickable: bool = True,
opacity: float = 1,
**kwargs,
) -> None:
"""Add a text layer using deck.gl."""
layer_id = name or f"text-{len(self._layers)}"
processed_data = self._process_deck_data(data)
self.call_js_method(
"addTextLayer",
id=layer_id,
data=processed_data,
getPosition=get_position,
getText=get_text,
getSize=get_size,
getColor=get_color or [0, 0, 0, 255],
getAngle=get_angle,
getTextAnchor=text_anchor,
getAlignmentBaseline=alignment_baseline,
pickable=pickable,
opacity=opacity,
**kwargs,
)
self._layers = {**self._layers, layer_id: {"id": layer_id, "type": "text"}}
self._add_to_layer_dict(layer_id, "Deck.gl")
add_tile_layer(self, url, name=None, attribution='', min_zoom=0, max_zoom=22, **kwargs)
¶
Add an XYZ tile layer.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
url |
str |
Tile URL template with {x}, {y}, {z} placeholders. |
required |
name |
Optional[str] |
Layer name. |
None |
attribution |
str |
Attribution text. |
'' |
min_zoom |
int |
Minimum zoom level. |
0 |
max_zoom |
int |
Maximum zoom level. |
22 |
**kwargs |
Additional options. |
{} |
Source code in anymap_ts/mapbox.py
def add_tile_layer(
self,
url: str,
name: Optional[str] = None,
attribution: str = "",
min_zoom: int = 0,
max_zoom: int = 22,
**kwargs,
) -> None:
"""Add an XYZ tile layer.
Args:
url: Tile URL template with {x}, {y}, {z} placeholders.
name: Layer name.
attribution: Attribution text.
min_zoom: Minimum zoom level.
max_zoom: Maximum zoom level.
**kwargs: Additional options.
"""
layer_id = name or f"tiles-{len(self._layers)}"
self.call_js_method(
"addTileLayer",
url,
name=layer_id,
attribution=attribution,
minZoom=min_zoom,
maxZoom=max_zoom,
**kwargs,
)
# Track layer
self._layers = {
**self._layers,
layer_id: {
"id": layer_id,
"type": "raster",
"source": f"{layer_id}-source",
},
}
self._add_to_layer_dict(layer_id, "Raster")
add_time_slider(self, layer_id, property, min_value=0, max_value=100, step=1, position='bottom-left', label='Time', auto_play=False, interval=500)
¶
Add a time slider to filter data by a temporal property.
Source code in anymap_ts/mapbox.py
def add_time_slider(
self,
layer_id: str,
property: str,
min_value: float = 0,
max_value: float = 100,
step: float = 1,
position: str = "bottom-left",
label: str = "Time",
auto_play: bool = False,
interval: int = 500,
) -> None:
"""Add a time slider to filter data by a temporal property."""
self.call_js_method(
"addTimeSlider",
layerId=layer_id,
property=property,
min=min_value,
max=max_value,
step=step,
position=position,
label=label,
autoPlay=auto_play,
interval=interval,
)
add_tooltip(self, layer_id, template=None, properties=None)
¶
Add a tooltip that shows on feature hover.
Source code in anymap_ts/mapbox.py
def add_tooltip(
self,
layer_id: str,
template: Optional[str] = None,
properties: Optional[List[str]] = None,
) -> None:
"""Add a tooltip that shows on feature hover."""
self.call_js_method(
"addTooltip",
layerId=layer_id,
template=template or "",
properties=properties,
)
add_trips_layer(self, data, name=None, get_path='waypoints', get_timestamps='timestamps', get_color=None, width_min_pixels=2, trail_length=180, current_time=0, pickable=True, opacity=0.8, **kwargs)
¶
Add a trips layer using deck.gl.
Source code in anymap_ts/mapbox.py
def add_trips_layer(
self,
data: Any,
name: Optional[str] = None,
get_path: Union[str, Any] = "waypoints",
get_timestamps: Union[str, Any] = "timestamps",
get_color: Optional[Union[List[int], str]] = None,
width_min_pixels: float = 2,
trail_length: float = 180,
current_time: float = 0,
pickable: bool = True,
opacity: float = 0.8,
**kwargs,
) -> None:
"""Add a trips layer using deck.gl."""
layer_id = name or f"trips-{len(self._layers)}"
processed_data = self._process_deck_data(data)
self.call_js_method(
"addTripsLayer",
id=layer_id,
data=processed_data,
getPath=get_path,
getTimestamps=get_timestamps,
getColor=get_color or [253, 128, 93],
widthMinPixels=width_min_pixels,
trailLength=trail_length,
currentTime=current_time,
pickable=pickable,
opacity=opacity,
**kwargs,
)
self._layers = {**self._layers, layer_id: {"id": layer_id, "type": "trips"}}
self._add_to_layer_dict(layer_id, "Deck.gl")
add_vector(self, data, layer_type=None, paint=None, name=None, fit_bounds=True, **kwargs)
¶
Add vector data to the map.
Supports GeoJSON, GeoDataFrame, or file paths to vector formats.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data |
Any |
GeoJSON dict, GeoDataFrame, or path to vector file. |
required |
layer_type |
Optional[str] |
Mapbox layer type ('circle', 'line', 'fill', 'symbol'). |
None |
paint |
Optional[Dict] |
Mapbox paint properties. |
None |
name |
Optional[str] |
Layer name. |
None |
fit_bounds |
bool |
Whether to fit map to data bounds. |
True |
**kwargs |
Additional layer options. |
{} |
Source code in anymap_ts/mapbox.py
def add_vector(
self,
data: Any,
layer_type: Optional[str] = None,
paint: Optional[Dict] = None,
name: Optional[str] = None,
fit_bounds: bool = True,
**kwargs,
) -> None:
"""Add vector data to the map.
Supports GeoJSON, GeoDataFrame, or file paths to vector formats.
Args:
data: GeoJSON dict, GeoDataFrame, or path to vector file.
layer_type: Mapbox layer type ('circle', 'line', 'fill', 'symbol').
paint: Mapbox paint properties.
name: Layer name.
fit_bounds: Whether to fit map to data bounds.
**kwargs: Additional layer options.
"""
geojson = to_geojson(data)
layer_id = name or f"vector-{len(self._layers)}"
# Handle URL data - fetch GeoJSON to get bounds and infer layer type
if geojson.get("type") == "url":
url = geojson["url"]
geojson = fetch_geojson(url)
# Infer layer type if not specified
if layer_type is None:
layer_type = infer_layer_type(geojson)
# Get default paint if not provided
if paint is None:
paint = get_default_paint(layer_type)
# Get bounds (use geojson dict, not original data which may be a URL)
bounds = get_bounds(geojson) if fit_bounds else None
# Call JavaScript
self.call_js_method(
"addGeoJSON",
data=geojson,
name=layer_id,
layerType=layer_type,
paint=paint,
fitBounds=fit_bounds,
bounds=bounds,
**kwargs,
)
# Track layer
self._layers = {
**self._layers,
layer_id: {
"id": layer_id,
"type": layer_type,
"source": f"{layer_id}-source",
"paint": paint,
},
}
self._add_to_layer_dict(layer_id, "Vector")
add_vector_control(self, position='top-right', collapsed=True, default_url=None, load_default_url=False, default_opacity=1.0, default_fill_color='#3388ff', default_stroke_color='#3388ff', fit_bounds=True, **kwargs)
¶
Add a vector layer control.
Source code in anymap_ts/mapbox.py
def add_vector_control(
self,
position: str = "top-right",
collapsed: bool = True,
default_url: Optional[str] = None,
load_default_url: bool = False,
default_opacity: float = 1.0,
default_fill_color: str = "#3388ff",
default_stroke_color: str = "#3388ff",
fit_bounds: bool = True,
**kwargs,
) -> None:
"""Add a vector layer control."""
self.call_js_method(
"addVectorControl",
position=position,
collapsed=collapsed,
defaultUrl=default_url or "",
loadDefaultUrl=load_default_url,
defaultOpacity=default_opacity,
defaultFillColor=default_fill_color,
defaultStrokeColor=default_stroke_color,
fitBounds=fit_bounds,
**kwargs,
)
self._controls = {
**self._controls,
"vector-control": {"position": position, "collapsed": collapsed},
}
add_video_layer(self, urls, coordinates, name=None, opacity=1.0, **kwargs)
¶
Add a georeferenced video overlay on the map.
Source code in anymap_ts/mapbox.py
def add_video_layer(
self,
urls: List[str],
coordinates: List[List[float]],
name: Optional[str] = None,
opacity: float = 1.0,
**kwargs,
) -> None:
"""Add a georeferenced video overlay on the map."""
self._validate_opacity(opacity)
layer_id = name or f"video-{len(self._layers)}"
if len(coordinates) != 4:
raise ValueError(
"coordinates must have exactly 4 corner points "
"[top-left, top-right, bottom-right, bottom-left]"
)
self.call_js_method(
"addVideoLayer",
id=layer_id,
urls=urls,
coordinates=coordinates,
opacity=opacity,
**kwargs,
)
self._layers = {
**self._layers,
layer_id: {
"id": layer_id,
"type": "video",
"source": f"{layer_id}-source",
},
}
self._add_to_layer_dict(layer_id, "Raster")
add_zarr_control(self, position='top-right', collapsed=True, default_url=None, load_default_url=False, default_opacity=1.0, default_variable='', default_clim=None, **kwargs)
¶
Add a Zarr layer control.
Source code in anymap_ts/mapbox.py
def add_zarr_control(
self,
position: str = "top-right",
collapsed: bool = True,
default_url: Optional[str] = None,
load_default_url: bool = False,
default_opacity: float = 1.0,
default_variable: str = "",
default_clim: Optional[Tuple[float, float]] = None,
**kwargs,
) -> None:
"""Add a Zarr layer control."""
self.call_js_method(
"addZarrControl",
position=position,
collapsed=collapsed,
defaultUrl=default_url or "",
loadDefaultUrl=load_default_url,
defaultOpacity=default_opacity,
defaultVariable=default_variable,
defaultClim=list(default_clim) if default_clim else [0, 1],
**kwargs,
)
self._controls = {
**self._controls,
"zarr-control": {"position": position, "collapsed": collapsed},
}
add_zarr_layer(self, url, variable, name=None, colormap=None, clim=None, opacity=1.0, selector=None, minzoom=0, maxzoom=22, fill_value=None, spatial_dimensions=None, zarr_version=None, bounds=None, **kwargs)
¶
Add a Zarr dataset layer for visualizing multidimensional array data.
Source code in anymap_ts/mapbox.py
def add_zarr_layer(
self,
url: str,
variable: str,
name: Optional[str] = None,
colormap: Optional[List[str]] = None,
clim: Optional[Tuple[float, float]] = None,
opacity: float = 1.0,
selector: Optional[Dict[str, Any]] = None,
minzoom: int = 0,
maxzoom: int = 22,
fill_value: Optional[float] = None,
spatial_dimensions: Optional[Dict[str, str]] = None,
zarr_version: Optional[int] = None,
bounds: Optional[List[float]] = None,
**kwargs,
) -> None:
"""Add a Zarr dataset layer for visualizing multidimensional array data."""
layer_id = name or f"zarr-{len(self._layers)}"
self.call_js_method(
"addZarrLayer",
id=layer_id,
source=url,
variable=variable,
colormap=colormap or ["#000000", "#ffffff"],
clim=list(clim) if clim else [0, 100],
opacity=opacity,
selector=selector or {},
minzoom=minzoom,
maxzoom=maxzoom,
fillValue=fill_value,
spatialDimensions=spatial_dimensions,
zarrVersion=zarr_version,
bounds=bounds,
**kwargs,
)
self._layers = {
**self._layers,
layer_id: {
"id": layer_id,
"type": "zarr",
"url": url,
"variable": variable,
},
}
self._add_to_layer_dict(layer_id, "Raster")
animate_along_route(self, route, duration=10000, loop=True, marker_color='#3388ff', marker_size=1.0, show_trail=False, trail_color='#3388ff', trail_width=3, animation_id=None, **kwargs)
¶
Animate a marker along a route.
Source code in anymap_ts/mapbox.py
def animate_along_route(
self,
route: Any,
duration: int = 10000,
loop: bool = True,
marker_color: str = "#3388ff",
marker_size: float = 1.0,
show_trail: bool = False,
trail_color: str = "#3388ff",
trail_width: float = 3,
animation_id: Optional[str] = None,
**kwargs,
) -> str:
"""Animate a marker along a route."""
anim_id = animation_id or f"animation-{len(self._layers)}"
if isinstance(route, list) and len(route) > 0:
if isinstance(route[0], (list, tuple)):
coordinates = route
else:
raise ValueError("Route list must contain coordinate pairs")
elif isinstance(route, dict):
if route.get("type") == "LineString":
coordinates = route.get("coordinates", [])
elif route.get("type") == "Feature":
geometry = route.get("geometry", {})
if geometry.get("type") == "LineString":
coordinates = geometry.get("coordinates", [])
else:
raise ValueError("Feature geometry must be LineString")
elif route.get("type") == "FeatureCollection":
features = route.get("features", [])
if (
features
and features[0].get("geometry", {}).get("type") == "LineString"
):
coordinates = features[0]["geometry"]["coordinates"]
else:
raise ValueError(
"FeatureCollection must contain LineString features"
)
else:
raise ValueError(
"GeoJSON must be LineString, Feature, or FeatureCollection"
)
else:
geojson = to_geojson(route)
if geojson.get("type") == "url":
geojson = fetch_geojson(geojson["url"])
if geojson.get("type") == "FeatureCollection":
features = geojson.get("features", [])
if features:
coordinates = features[0].get("geometry", {}).get("coordinates", [])
else:
raise ValueError("No features found in data")
elif geojson.get("type") == "Feature":
coordinates = geojson.get("geometry", {}).get("coordinates", [])
else:
coordinates = geojson.get("coordinates", [])
if len(coordinates) < 2:
raise ValueError("Route must have at least 2 points")
self.call_js_method(
"animateAlongRoute",
id=anim_id,
coordinates=coordinates,
duration=duration,
loop=loop,
markerColor=marker_color,
markerSize=marker_size,
showTrail=show_trail,
trailColor=trail_color,
trailWidth=trail_width,
**kwargs,
)
self._layers = {
**self._layers,
anim_id: {
"id": anim_id,
"type": "animation",
},
}
return anim_id
clear_draw_data(self)
¶
Clear all drawn features.
Source code in anymap_ts/mapbox.py
def clear_draw_data(self) -> None:
"""Clear all drawn features."""
self._draw_data = {"type": "FeatureCollection", "features": []}
self.call_js_method("clearDrawData")
get_draw_data(self)
¶
Get the current drawn features as GeoJSON.
Source code in anymap_ts/mapbox.py
def get_draw_data(self) -> Dict:
"""Get the current drawn features as GeoJSON."""
self.call_js_method("getDrawData")
import time
time.sleep(0.1)
return self._draw_data or {"type": "FeatureCollection", "features": []}
get_layer(self, layer_id)
¶
Get layer configuration by ID.
Source code in anymap_ts/mapbox.py
def get_layer(self, layer_id: str) -> Optional[Dict]:
"""Get layer configuration by ID."""
return self._layers.get(layer_id)
get_layer_ids(self)
¶
Get list of all layer IDs.
Source code in anymap_ts/mapbox.py
def get_layer_ids(self) -> List[str]:
"""Get list of all layer IDs."""
return list(self._layers.keys())
get_visible_features(self, layers=None)
¶
Get all features currently visible in the viewport.
Source code in anymap_ts/mapbox.py
def get_visible_features(
self,
layers: Optional[List[str]] = None,
) -> Optional[Dict]:
"""Get all features currently visible in the viewport."""
if layers is not None:
self.call_js_method("getVisibleFeatures", layers=layers)
features = self._queried_features
if features and "data" in features:
return features["data"]
return None
load_draw_data(self, geojson)
¶
Load GeoJSON features into the drawing layer.
Source code in anymap_ts/mapbox.py
def load_draw_data(self, geojson: Dict) -> None:
"""Load GeoJSON features into the drawing layer."""
self._draw_data = geojson
self.call_js_method("loadDrawData", geojson)
move_layer(self, layer_id, before_id=None)
¶
Move a layer in the layer stack.
Source code in anymap_ts/mapbox.py
def move_layer(self, layer_id: str, before_id: Optional[str] = None) -> None:
"""Move a layer in the layer stack."""
self.call_js_method("moveLayer", layer_id, before_id)
pause_animation(self, animation_id)
¶
Pause a running animation.
Source code in anymap_ts/mapbox.py
def pause_animation(self, animation_id: str) -> None:
"""Pause a running animation."""
self.call_js_method("pauseAnimation", animation_id)
pause_video(self, name)
¶
Pause a video layer.
Source code in anymap_ts/mapbox.py
def pause_video(self, name: str) -> None:
"""Pause a video layer."""
self.call_js_method("pauseVideo", id=name)
play_video(self, name)
¶
Start playing a video layer.
Source code in anymap_ts/mapbox.py
def play_video(self, name: str) -> None:
"""Start playing a video layer."""
self.call_js_method("playVideo", id=name)
query_rendered_features(self, geometry=None, layers=None, filter_expression=None)
¶
Query features currently rendered on the map.
Source code in anymap_ts/mapbox.py
def query_rendered_features(
self,
geometry: Optional[Any] = None,
layers: Optional[List[str]] = None,
filter_expression: Optional[List] = None,
) -> Dict:
"""Query features currently rendered on the map."""
kwargs: Dict[str, Any] = {}
if geometry is not None:
kwargs["geometry"] = geometry
if layers is not None:
kwargs["layers"] = layers
if filter_expression is not None:
kwargs["filter"] = filter_expression
self.call_js_method("queryRenderedFeatures", **kwargs)
return self._queried_features
query_source_features(self, source_id, source_layer=None, filter_expression=None)
¶
Query features from a source.
Source code in anymap_ts/mapbox.py
def query_source_features(
self,
source_id: str,
source_layer: Optional[str] = None,
filter_expression: Optional[List] = None,
) -> Dict:
"""Query features from a source."""
kwargs: Dict[str, Any] = {"sourceId": source_id}
if source_layer is not None:
kwargs["sourceLayer"] = source_layer
if filter_expression is not None:
kwargs["filter"] = filter_expression
self.call_js_method("querySourceFeatures", **kwargs)
return self._queried_features
remove_arc_layer(self, layer_id)
¶
Remove an arc layer.
Source code in anymap_ts/mapbox.py
def remove_arc_layer(self, layer_id: str) -> None:
"""Remove an arc layer."""
self._remove_layer_internal(layer_id, "removeArcLayer")
remove_cluster_layer(self, layer_id)
¶
Remove a cluster layer.
Source code in anymap_ts/mapbox.py
def remove_cluster_layer(self, layer_id: str) -> None:
"""Remove a cluster layer."""
self._remove_layer_internal(layer_id, "removeClusterLayer")
remove_cog_layer(self, layer_id)
¶
Remove a COG layer.
Source code in anymap_ts/mapbox.py
def remove_cog_layer(self, layer_id: str) -> None:
"""Remove a COG layer."""
self._remove_layer_internal(layer_id, "removeCOGLayer")
remove_colorbar(self, colorbar_id=None)
¶
Remove a colorbar from the map.
Source code in anymap_ts/mapbox.py
def remove_colorbar(self, colorbar_id: Optional[str] = None) -> None:
"""Remove a colorbar from the map."""
if colorbar_id is None:
cbar_keys = [k for k in self._controls.keys() if k.startswith("colorbar")]
for key in cbar_keys:
self.call_js_method("removeColorbar", colorbarId=key)
self._controls = {
k: v for k, v in self._controls.items() if not k.startswith("colorbar")
}
else:
self.call_js_method("removeColorbar", colorbarId=colorbar_id)
if colorbar_id in self._controls:
controls = dict(self._controls)
del controls[colorbar_id]
self._controls = controls
remove_control(self, control_type)
¶
Remove a map control.
Source code in anymap_ts/mapbox.py
def remove_control(self, control_type: str) -> None:
"""Remove a map control."""
self.call_js_method("removeControl", control_type)
if control_type in self._controls:
controls = dict(self._controls)
del controls[control_type]
self._controls = controls
remove_coordinates_control(self)
¶
Remove the coordinates display control.
Source code in anymap_ts/mapbox.py
def remove_coordinates_control(self) -> None:
"""Remove the coordinates display control."""
self.call_js_method("removeCoordinatesControl")
remove_deck_layer(self, layer_id)
¶
Remove a deck.gl layer from the map.
Source code in anymap_ts/mapbox.py
def remove_deck_layer(self, layer_id: str) -> None:
"""Remove a deck.gl layer from the map."""
self._remove_layer_internal(layer_id, "removeDeckLayer")
remove_flatgeobuf(self, name)
¶
Remove a FlatGeobuf layer from the map.
Source code in anymap_ts/mapbox.py
def remove_flatgeobuf(self, name: str) -> None:
"""Remove a FlatGeobuf layer from the map."""
if name in self._layers:
layers = dict(self._layers)
del layers[name]
self._layers = layers
self._remove_from_layer_dict(name)
self.call_js_method("removeFlatGeobuf", name=name)
remove_fog(self)
¶
Remove fog atmospheric effects from the map.
Source code in anymap_ts/mapbox.py
def remove_fog(self) -> None:
"""Remove fog atmospheric effects from the map."""
self.call_js_method("removeFog")
remove_layer(self, layer_id)
¶
Remove a layer from the map.
Source code in anymap_ts/mapbox.py
def remove_layer(self, layer_id: str) -> None:
"""Remove a layer from the map."""
if layer_id in self._layers:
layers = dict(self._layers)
del layers[layer_id]
self._layers = layers
self._remove_from_layer_dict(layer_id)
self.call_js_method("removeLayer", layer_id)
remove_legend(self, legend_id=None)
¶
Remove a legend control from the map.
Source code in anymap_ts/mapbox.py
def remove_legend(self, legend_id: Optional[str] = None) -> None:
"""Remove a legend control from the map."""
if legend_id is None:
legend_keys = [k for k in self._controls.keys() if k.startswith("legend")]
for key in legend_keys:
self.call_js_method("removeLegend", key)
self._controls = {
k: v for k, v in self._controls.items() if not k.startswith("legend")
}
else:
self.call_js_method("removeLegend", legend_id)
if legend_id in self._controls:
controls = dict(self._controls)
del controls[legend_id]
self._controls = controls
remove_lidar_layer(self, layer_id=None)
¶
Remove a LiDAR layer.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
layer_id |
Optional[str] |
Layer identifier to remove. If None, removes all LiDAR layers. |
None |
Source code in anymap_ts/mapbox.py
def remove_lidar_layer(self, layer_id: Optional[str] = None) -> None:
"""Remove a LiDAR layer.
Args:
layer_id: Layer identifier to remove. If None, removes all LiDAR layers.
"""
if layer_id:
if layer_id in self._layers:
layers = dict(self._layers)
del layers[layer_id]
self._layers = layers
self.call_js_method("removeLidarLayer", id=layer_id)
else:
# Remove all lidar layers
layers = dict(self._layers)
self._layers = {k: v for k, v in layers.items() if v.get("type") != "lidar"}
self.call_js_method("removeLidarLayer")
remove_marker(self, marker_id)
¶
Remove a marker from the map.
Source code in anymap_ts/mapbox.py
def remove_marker(self, marker_id: str) -> None:
"""Remove a marker from the map."""
self._remove_layer_internal(marker_id, "removeMarker")
remove_measure_control(self)
¶
Remove the measurement control.
Source code in anymap_ts/mapbox.py
def remove_measure_control(self) -> None:
"""Remove the measurement control."""
self.call_js_method("removeMeasureControl")
if "measure-control" in self._controls:
controls = dict(self._controls)
del controls["measure-control"]
self._controls = controls
remove_opacity_slider(self, layer_id)
¶
Remove the opacity slider for a layer.
Source code in anymap_ts/mapbox.py
def remove_opacity_slider(self, layer_id: str) -> None:
"""Remove the opacity slider for a layer."""
self.call_js_method("removeOpacitySlider", layerId=layer_id)
remove_pmtiles_layer(self, layer_id)
¶
Remove a PMTiles layer.
Source code in anymap_ts/mapbox.py
def remove_pmtiles_layer(self, layer_id: str) -> None:
"""Remove a PMTiles layer."""
self._remove_layer_internal(layer_id, "removePMTilesLayer")
remove_point_cloud_layer(self, layer_id)
¶
Remove a point cloud layer.
Source code in anymap_ts/mapbox.py
def remove_point_cloud_layer(self, layer_id: str) -> None:
"""Remove a point cloud layer."""
self._remove_layer_internal(layer_id, "removePointCloudLayer")
remove_print_control(self)
¶
Remove the print/export control.
Source code in anymap_ts/mapbox.py
def remove_print_control(self) -> None:
"""Remove the print/export control."""
self.call_js_method("removePrintControl")
if "print-control" in self._controls:
controls = dict(self._controls)
del controls["print-control"]
self._controls = controls
remove_search_control(self)
¶
Remove the search/geocoder control.
Source code in anymap_ts/mapbox.py
def remove_search_control(self) -> None:
"""Remove the search/geocoder control."""
self.call_js_method("removeSearchControl")
if "search-control" in self._controls:
controls = dict(self._controls)
del controls["search-control"]
self._controls = controls
remove_split_map(self)
¶
Remove the split map comparison view.
Source code in anymap_ts/mapbox.py
def remove_split_map(self) -> None:
"""Remove the split map comparison view."""
self.call_js_method("removeSplitMap")
remove_style_switcher(self)
¶
Remove the style switcher control.
Source code in anymap_ts/mapbox.py
def remove_style_switcher(self) -> None:
"""Remove the style switcher control."""
self.call_js_method("removeStyleSwitcher")
remove_swipe_map(self)
¶
Remove the swipe map comparison control.
Source code in anymap_ts/mapbox.py
def remove_swipe_map(self) -> None:
"""Remove the swipe map comparison control."""
self.call_js_method("removeSwipeMap")
remove_terrain(self)
¶
Remove 3D terrain from the map.
Source code in anymap_ts/mapbox.py
def remove_terrain(self) -> None:
"""Remove 3D terrain from the map."""
self.call_js_method("removeTerrain")
remove_time_slider(self)
¶
Remove the time slider control.
Source code in anymap_ts/mapbox.py
def remove_time_slider(self) -> None:
"""Remove the time slider control."""
self.call_js_method("removeTimeSlider")
remove_tooltip(self, layer_id)
¶
Remove tooltip from a layer.
Source code in anymap_ts/mapbox.py
def remove_tooltip(self, layer_id: str) -> None:
"""Remove tooltip from a layer."""
self.call_js_method("removeTooltip", layerId=layer_id)
remove_video_layer(self, name)
¶
Remove a video layer from the map.
Source code in anymap_ts/mapbox.py
def remove_video_layer(self, name: str) -> None:
"""Remove a video layer from the map."""
if name in self._layers:
layers = dict(self._layers)
del layers[name]
self._layers = layers
self._remove_from_layer_dict(name)
self.call_js_method("removeVideoLayer", id=name)
remove_zarr_layer(self, layer_id)
¶
Remove a Zarr layer.
Source code in anymap_ts/mapbox.py
def remove_zarr_layer(self, layer_id: str) -> None:
"""Remove a Zarr layer."""
self._remove_layer_internal(layer_id, "removeZarrLayer")
resume_animation(self, animation_id)
¶
Resume a paused animation.
Source code in anymap_ts/mapbox.py
def resume_animation(self, animation_id: str) -> None:
"""Resume a paused animation."""
self.call_js_method("resumeAnimation", animation_id)
save_draw_data(self, filepath, driver=None)
¶
Save drawn features to a file.
Source code in anymap_ts/mapbox.py
def save_draw_data(
self,
filepath: Union[str, Path],
driver: Optional[str] = None,
) -> None:
"""Save drawn features to a file."""
try:
import geopandas as gpd
except ImportError:
raise ImportError(
"geopandas is required to save draw data. "
"Install with: pip install anymap-ts[vector]"
)
data = self.get_draw_data()
if not data.get("features"):
print("No features to save")
return
gdf = gpd.GeoDataFrame.from_features(data["features"])
filepath = Path(filepath)
if driver is None:
ext = filepath.suffix.lower()
driver_map = {
".geojson": "GeoJSON",
".json": "GeoJSON",
".shp": "ESRI Shapefile",
".gpkg": "GPKG",
}
driver = driver_map.get(ext, "GeoJSON")
gdf.to_file(filepath, driver=driver)
seek_video(self, name, time)
¶
Seek to a specific time in a video layer.
Source code in anymap_ts/mapbox.py
def seek_video(self, name: str, time: float) -> None:
"""Seek to a specific time in a video layer."""
self.call_js_method("seekVideo", id=name, time=time)
set_access_token(self, token)
¶
Set the Mapbox access token.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
token |
str |
Mapbox access token. |
required |
Source code in anymap_ts/mapbox.py
def set_access_token(self, token: str) -> None:
"""Set the Mapbox access token.
Args:
token: Mapbox access token.
"""
self.access_token = token
set_animation_speed(self, animation_id, speed)
¶
Set animation speed multiplier.
Source code in anymap_ts/mapbox.py
def set_animation_speed(self, animation_id: str, speed: float) -> None:
"""Set animation speed multiplier."""
self.call_js_method("setAnimationSpeed", animation_id, speed)
set_filter(self, layer_id, filter_expression=None)
¶
Set or clear a filter on a map layer.
Source code in anymap_ts/mapbox.py
def set_filter(
self,
layer_id: str,
filter_expression: Optional[List] = None,
) -> None:
"""Set or clear a filter on a map layer."""
self.call_js_method(
"setFilter",
layerId=layer_id,
filter=filter_expression,
)
set_fog(self, color=None, high_color=None, low_color=None, horizon_blend=None, range=None, **kwargs)
¶
Set fog atmospheric effect (Mapbox uses map.setFog() API).
Source code in anymap_ts/mapbox.py
def set_fog(
self,
color: Optional[str] = None,
high_color: Optional[str] = None,
low_color: Optional[str] = None,
horizon_blend: Optional[float] = None,
range: Optional[List[float]] = None,
**kwargs,
) -> None:
"""Set fog atmospheric effect (Mapbox uses map.setFog() API)."""
self.call_js_method(
"setFog",
color=color,
highColor=high_color,
lowColor=low_color,
horizonBlend=horizon_blend,
range=range,
**kwargs,
)
set_layout_property(self, layer_id, property_name, value)
¶
Set a layout property for a layer.
Source code in anymap_ts/mapbox.py
def set_layout_property(
self, layer_id: str, property_name: str, value: Any
) -> None:
"""Set a layout property for a layer."""
self.call_js_method("setLayoutProperty", layer_id, property_name, value)
set_lidar_color_scheme(self, color_scheme)
¶
Set the LiDAR color scheme.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
color_scheme |
str |
Color scheme ('elevation', 'intensity', 'classification', 'rgb'). |
required |
Source code in anymap_ts/mapbox.py
def set_lidar_color_scheme(self, color_scheme: str) -> None:
"""Set the LiDAR color scheme.
Args:
color_scheme: Color scheme ('elevation', 'intensity', 'classification', 'rgb').
"""
self.call_js_method("setLidarColorScheme", colorScheme=color_scheme)
set_lidar_opacity(self, opacity)
¶
Set the LiDAR layer opacity.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
opacity |
float |
Opacity value between 0 and 1. |
required |
Source code in anymap_ts/mapbox.py
def set_lidar_opacity(self, opacity: float) -> None:
"""Set the LiDAR layer opacity.
Args:
opacity: Opacity value between 0 and 1.
"""
self.call_js_method("setLidarOpacity", opacity=opacity)
set_lidar_point_size(self, point_size)
¶
Set the LiDAR point size.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
point_size |
float |
Point size in pixels. |
required |
Source code in anymap_ts/mapbox.py
def set_lidar_point_size(self, point_size: float) -> None:
"""Set the LiDAR point size.
Args:
point_size: Point size in pixels.
"""
self.call_js_method("setLidarPointSize", pointSize=point_size)
set_opacity(self, layer_id, opacity)
¶
Set layer opacity.
Source code in anymap_ts/mapbox.py
def set_opacity(self, layer_id: str, opacity: float) -> None:
"""Set layer opacity."""
self._validate_opacity(opacity)
self.call_js_method("setOpacity", layer_id, opacity)
set_paint_property(self, layer_id, property_name, value)
¶
Set a paint property for a layer.
Source code in anymap_ts/mapbox.py
def set_paint_property(self, layer_id: str, property_name: str, value: Any) -> None:
"""Set a paint property for a layer."""
self.call_js_method("setPaintProperty", layer_id, property_name, value)
set_projection(self, projection='mercator')
¶
Set the map projection (Mapbox supports 'globe' and 'mercator').
Source code in anymap_ts/mapbox.py
def set_projection(self, projection: str = "mercator") -> None:
"""Set the map projection (Mapbox supports 'globe' and 'mercator')."""
self.call_js_method("setProjection", projection=projection)
set_visibility(self, layer_id, visible)
¶
Set layer visibility.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
layer_id |
str |
Layer identifier. |
required |
visible |
bool |
Whether layer should be visible. |
required |
Source code in anymap_ts/mapbox.py
def set_visibility(self, layer_id: str, visible: bool) -> None:
"""Set layer visibility.
Args:
layer_id: Layer identifier.
visible: Whether layer should be visible.
"""
self.call_js_method("setVisibility", layer_id, visible)
stop_animation(self, animation_id)
¶
Stop a running animation.
Source code in anymap_ts/mapbox.py
def stop_animation(self, animation_id: str) -> None:
"""Stop a running animation."""
self.call_js_method("stopAnimation", animation_id)
if animation_id in self._layers:
layers = dict(self._layers)
del layers[animation_id]
self._layers = layers
to_geojson(self, layer_id=None)
¶
Get layer data as GeoJSON.
Source code in anymap_ts/mapbox.py
def to_geojson(self, layer_id: Optional[str] = None) -> Optional[Dict]:
"""Get layer data as GeoJSON."""
if layer_id:
self.call_js_method("getLayerData", sourceId=layer_id)
features = self._queried_features
if features and "data" in features:
return features["data"]
return None
to_geopandas(self, layer_id=None)
¶
Get layer data as a GeoDataFrame.
Source code in anymap_ts/mapbox.py
def to_geopandas(self, layer_id: Optional[str] = None) -> Any:
"""Get layer data as a GeoDataFrame."""
geojson = self.to_geojson(layer_id)
if geojson is None:
return None
try:
import geopandas as gpd
return gpd.GeoDataFrame.from_features(geojson.get("features", []))
except ImportError:
raise ImportError("geopandas is required for to_geopandas()")
update_colorbar(self, colorbar_id=None, **kwargs)
¶
Update an existing colorbar's properties.
Source code in anymap_ts/mapbox.py
def update_colorbar(self, colorbar_id: Optional[str] = None, **kwargs) -> None:
"""Update an existing colorbar's properties."""
if colorbar_id is None:
cbar_keys = [k for k in self._controls.keys() if k.startswith("colorbar")]
if not cbar_keys:
raise ValueError("No colorbar found to update")
colorbar_id = cbar_keys[0]
if colorbar_id not in self._controls:
raise ValueError(f"Colorbar '{colorbar_id}' not found")
js_kwargs: Dict[str, Any] = {"colorbarId": colorbar_id}
key_map = {"bar_thickness": "barThickness", "bar_length": "barLength"}
for key, value in kwargs.items():
js_key = key_map.get(key, key)
js_kwargs[js_key] = value
self.call_js_method("updateColorbar", **js_kwargs)
update_geojson_source(self, source_id, data)
¶
Update the data of an existing GeoJSON source in place.
Source code in anymap_ts/mapbox.py
def update_geojson_source(self, source_id: str, data: Any) -> None:
"""Update the data of an existing GeoJSON source in place."""
processed_data = self._process_deck_data(data)
self.call_js_method(
"updateGeoJSONSource",
sourceId=source_id,
data=processed_data,
)
update_legend(self, legend_id, title=None, labels=None, colors=None, opacity=None, **kwargs)
¶
Update an existing legend's properties.
Source code in anymap_ts/mapbox.py
def update_legend(
self,
legend_id: str,
title: Optional[str] = None,
labels: Optional[List[str]] = None,
colors: Optional[List[str]] = None,
opacity: Optional[float] = None,
**kwargs,
) -> None:
"""Update an existing legend's properties."""
if legend_id not in self._controls:
raise ValueError(f"Legend '{legend_id}' not found")
update_params = {"id": legend_id}
if title is not None:
update_params["title"] = title
self._controls[legend_id]["title"] = title
if labels is not None and colors is not None:
if len(labels) != len(colors):
raise ValueError("Number of labels must match number of colors")
legend_items = [
{"label": label, "color": color} for label, color in zip(labels, colors)
]
update_params["items"] = legend_items
self._controls[legend_id]["labels"] = labels
self._controls[legend_id]["colors"] = colors
elif labels is not None or colors is not None:
raise ValueError("Both labels and colors must be provided together")
if opacity is not None:
update_params["opacity"] = opacity
self._controls[legend_id]["opacity"] = opacity
update_params.update(kwargs)
self.call_js_method("updateLegend", **update_params)
update_zarr_layer(self, layer_id, selector=None, clim=None, colormap=None, opacity=None)
¶
Update a Zarr layer's properties dynamically.
Source code in anymap_ts/mapbox.py
def update_zarr_layer(
self,
layer_id: str,
selector: Optional[Dict[str, Any]] = None,
clim: Optional[Tuple[float, float]] = None,
colormap: Optional[List[str]] = None,
opacity: Optional[float] = None,
) -> None:
"""Update a Zarr layer's properties dynamically."""
update_kwargs: Dict[str, Any] = {"id": layer_id}
if selector is not None:
update_kwargs["selector"] = selector
if clim is not None:
update_kwargs["clim"] = list(clim)
if colormap is not None:
update_kwargs["colormap"] = colormap
if opacity is not None:
update_kwargs["opacity"] = opacity
self.call_js_method("updateZarrLayer", **update_kwargs)
get_mapbox_token()
¶
Get Mapbox access token from environment variable.
Returns:
| Type | Description |
|---|---|
str |
Mapbox access token string, or empty string if not set. |
Source code in anymap_ts/mapbox.py
def get_mapbox_token() -> str:
"""Get Mapbox access token from environment variable.
Returns:
Mapbox access token string, or empty string if not set.
"""
return os.environ.get("MAPBOX_TOKEN", "")