maplibre module¶
MapLibre GL JS map widget implementation.
MapLibreMap (MapWidget)
¶
Interactive map widget using MapLibre GL JS.
This class provides a Python interface to MapLibre GL JS maps with full bidirectional communication through anywidget.
Examples:
>>> from anymap_ts import Map
>>> m = Map(center=[-122.4, 37.8], zoom=10)
>>> m.add_basemap("OpenStreetMap")
>>> m
Source code in anymap_ts/maplibre.py
class MapLibreMap(MapWidget):
"""Interactive map widget using MapLibre GL JS.
This class provides a Python interface to MapLibre GL JS maps with
full bidirectional communication through anywidget.
Example:
>>> from anymap_ts import Map
>>> m = Map(center=[-122.4, 37.8], zoom=10)
>>> m.add_basemap("OpenStreetMap")
>>> m
"""
# ESM module for frontend
_esm = STATIC_DIR / "maplibre.js"
_css = STATIC_DIR / "maplibre.css"
# MapLibre-specific traits
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 = "700px",
style: Union[
str, Dict
] = "https://basemaps.cartocdn.com/gl/positron-gl-style/style.json",
bearing: float = 0.0,
pitch: float = 0.0,
max_pitch: float = 85.0,
controls: Optional[Dict[str, Any]] = None,
**kwargs,
):
"""Initialize a MapLibre map.
Args:
center: Map center as (longitude, latitude).
zoom: Initial zoom level.
width: Map width as CSS string.
height: Map height as CSS string. Default is "700px".
style: MapLibre style URL or style object. Default is "https://basemaps.cartocdn.com/gl/positron-gl-style/style.json".
bearing: Map bearing in degrees.
pitch: Map pitch in degrees.
max_pitch: Maximum pitch angle in degrees (default: 85).
controls: Dict of controls to add. If None, defaults to
{"layer-control": True, "control-grid": True}.
Use {"layer-control": {"collapsed": True}} for custom options.
**kwargs: Additional widget arguments.
"""
# Handle style shortcuts
if isinstance(style, str) and not style.startswith("http"):
try:
style = get_maplibre_style(style)
except ValueError:
pass # Use as-is
super().__init__(
center=list(center),
zoom=zoom,
width=width,
height=height,
style=style,
bearing=bearing,
pitch=pitch,
max_pitch=max_pitch,
**kwargs,
)
# Initialize layer dictionary
self._layer_dict = {"Background": []}
# Add default controls
if controls is None:
controls = {
"layer-control": True,
"control-grid": True,
}
for control_name, config in controls.items():
if config:
if control_name == "layer-control":
self.add_layer_control(
**(config if isinstance(config, dict) else {})
)
elif control_name == "control-grid":
self.add_control_grid(
**(config if isinstance(config, dict) else {})
)
else:
self.add_control(
control_name, **(config if isinstance(config, dict) else {})
)
# -------------------------------------------------------------------------
# Basemap Methods
# -------------------------------------------------------------------------
def add_basemap(
self,
basemap: str = "OpenStreetMap",
attribution: Optional[str] = None,
**kwargs,
) -> None:
"""Add a basemap layer.
Args:
basemap: Name of basemap provider (e.g., "OpenStreetMap", "CartoDB.Positron")
attribution: Custom attribution text
**kwargs: Additional options
"""
url, default_attribution = get_basemap_url(basemap)
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: MapLibre layer type ('circle', 'line', 'fill', 'symbol')
paint: MapLibre paint properties
name: Layer name
fit_bounds: Whether to fit map to data bounds
**kwargs: Additional layer options
"""
geojson = to_geojson(data)
# Handle URL data
if geojson.get("type") == "url":
self.add_geojson(
geojson["url"],
layer_type=layer_type,
paint=paint,
name=name,
fit_bounds=fit_bounds,
**kwargs,
)
return
layer_id = name or f"vector-{len(self._layers)}"
# 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
bounds = get_bounds(data) 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,
},
}
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: MapLibre layer type
paint: MapLibre 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_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.
Args:
source: Path to local raster file
name: Layer name
attribution: Attribution text
indexes: Band indexes to use
colormap: Colormap name
vmin: Minimum value for colormap
vmax: Maximum value for colormap
nodata: NoData value
fit_bounds: Whether to fit map to raster bounds
**kwargs: Additional options
"""
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)
# Build tile URL with parameters
tile_url = client.get_tile_url()
if indexes:
tile_url = client.get_tile_url(indexes=indexes)
if colormap:
tile_url = client.get_tile_url(colormap=colormap)
if vmin is not None or vmax is not None:
tile_url = client.get_tile_url(
vmin=vmin or client.min, vmax=vmax or client.max
)
if nodata is not None:
tile_url = client.get_tile_url(nodata=nodata)
layer_name = name or Path(source).stem
self.add_tile_layer(
tile_url,
name=layer_name,
attribution=attribution,
**kwargs,
)
# Fit bounds if requested
if fit_bounds:
bounds = client.bounds()
if bounds:
self.fit_bounds([bounds[0], bounds[1], bounds[2], bounds[3]])
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",
},
}
# -------------------------------------------------------------------------
# COG Layer (deck.gl)
# -------------------------------------------------------------------------
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 @developmentseed/deck.gl-geotiff.
This method renders COG files directly in the browser using GPU-accelerated
deck.gl-geotiff 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 Map
>>> m = Map()
>>> 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,
},
}
def remove_cog_layer(self, layer_id: str) -> None:
"""Remove a COG layer.
Args:
layer_id: Layer identifier to remove.
"""
if layer_id in self._layers:
layers = dict(self._layers)
del layers[layer_id]
self._layers = layers
self.call_js_method("removeCOGLayer", layer_id)
# -------------------------------------------------------------------------
# Zarr Layer (@carbonplan/zarr-layer)
# -------------------------------------------------------------------------
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.
This method renders Zarr pyramid datasets directly in the browser using
GPU-accelerated WebGL rendering via @carbonplan/zarr-layer.
Args:
url: URL to the Zarr store (pyramid format recommended).
variable: Variable name in the Zarr dataset to visualize.
name: Layer ID. If None, auto-generated.
colormap: List of hex color strings for visualization.
Example: ['#0000ff', '#ffff00', '#ff0000'] (blue-yellow-red).
Default: ['#000000', '#ffffff'] (black to white).
clim: Color range as (min, max) tuple.
Default: (0, 100).
opacity: Layer opacity (0-1).
selector: Dimension selector for multi-dimensional data.
Example: {"month": 4} to select 4th month.
minzoom: Minimum zoom level for rendering.
maxzoom: Maximum zoom level for rendering.
fill_value: No-data value (auto-detected from metadata if not set).
spatial_dimensions: Custom spatial dimension names.
Example: {"lat": "y", "lon": "x"} for non-standard names.
zarr_version: Zarr format version (2 or 3). Auto-detected if not set.
bounds: Explicit spatial bounds [xMin, yMin, xMax, yMax].
Units depend on CRS: degrees for EPSG:4326, meters for EPSG:3857.
**kwargs: Additional ZarrLayer props.
Example:
>>> from anymap_ts import Map
>>> m = Map()
>>> m.add_zarr_layer(
... "https://example.com/climate.zarr",
... variable="temperature",
... clim=(270, 310),
... colormap=['#0000ff', '#ffff00', '#ff0000'],
... selector={"month": 7}
... )
"""
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,
},
}
def remove_zarr_layer(self, layer_id: str) -> None:
"""Remove a Zarr layer.
Args:
layer_id: Layer identifier to remove.
"""
if layer_id in self._layers:
layers = dict(self._layers)
del layers[layer_id]
self._layers = layers
self.call_js_method("removeZarrLayer", layer_id)
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.
Args:
layer_id: Layer identifier.
selector: New dimension selector.
clim: New color range.
colormap: New colormap.
opacity: New opacity value (0-1).
"""
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)
# -------------------------------------------------------------------------
# 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 MapLibreMap
>>> m = MapLibreMap()
>>> 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",
},
}
def remove_arc_layer(self, layer_id: str) -> None:
"""Remove an arc layer.
Args:
layer_id: Layer identifier to remove.
"""
if layer_id in self._layers:
layers = dict(self._layers)
del layers[layer_id]
self._layers = layers
self.call_js_method("removeArcLayer", layer_id)
# -------------------------------------------------------------------------
# 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 MapLibreMap
>>> import numpy as np
>>> m = MapLibreMap(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",
},
}
def remove_point_cloud_layer(self, layer_id: str) -> None:
"""Remove a point cloud layer.
Args:
layer_id: Layer identifier to remove.
"""
if layer_id in self._layers:
layers = dict(self._layers)
del layers[layer_id]
self._layers = layers
self.call_js_method("removePointCloudLayer", layer_id)
# -------------------------------------------------------------------------
# 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,
panel_max_height: int = 600,
**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.
panel_max_height: Maximum height of the panel in pixels.
**kwargs: Additional control options.
Example:
>>> from anymap_ts import MapLibreMap
>>> m = MapLibreMap(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,
panelMaxHeight=panel_max_height,
**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 MapLibreMap
>>> m = MapLibreMap(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",
... )
"""
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
import 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)
# -------------------------------------------------------------------------
# maplibre-gl-components UI Controls
# -------------------------------------------------------------------------
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 for loading PMTiles files via UI.
This provides an interactive panel for users to enter PMTiles URLs
and visualize vector or raster tile data.
Args:
position: Control position ('top-left', 'top-right', 'bottom-left', 'bottom-right').
collapsed: Whether the panel starts collapsed.
default_url: Default PMTiles URL to pre-fill.
load_default_url: Whether to auto-load the default URL.
default_opacity: Default layer opacity (0-1).
default_fill_color: Default fill color for vector polygons.
default_line_color: Default line color for vector lines.
default_pickable: Whether features are clickable by default.
**kwargs: Additional control options.
Example:
>>> from anymap_ts import MapLibreMap
>>> m = MapLibreMap()
>>> m.add_pmtiles_control(
... default_url="https://pmtiles.io/protomaps(vector)ODbL_firenze.pmtiles",
... load_default_url=True
... )
"""
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 for loading Cloud Optimized GeoTIFFs via UI.
This provides an interactive panel for users to enter COG URLs
and configure visualization parameters like colormap and rescaling.
Args:
position: Control position ('top-left', 'top-right', 'bottom-left', 'bottom-right').
collapsed: Whether the panel starts collapsed.
default_url: Default COG URL to pre-fill.
load_default_url: Whether to auto-load the default URL.
default_opacity: Default layer opacity (0-1).
default_colormap: Default colormap name.
default_bands: Default bands (e.g., '1' or '1,2,3').
default_rescale_min: Default minimum value for rescaling.
default_rescale_max: Default maximum value for rescaling.
**kwargs: Additional control options.
Example:
>>> from anymap_ts import MapLibreMap
>>> m = MapLibreMap()
>>> m.add_cog_control(
... default_url="https://example.com/cog.tif",
... default_colormap="terrain"
... )
"""
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 for loading Zarr datasets via UI.
This provides an interactive panel for users to enter Zarr URLs
and configure visualization parameters.
Args:
position: Control position ('top-left', 'top-right', 'bottom-left', 'bottom-right').
collapsed: Whether the panel starts collapsed.
default_url: Default Zarr URL to pre-fill.
load_default_url: Whether to auto-load the default URL.
default_opacity: Default layer opacity (0-1).
default_variable: Default variable name.
default_clim: Default color limits (min, max).
**kwargs: Additional control options.
Example:
>>> from anymap_ts import MapLibreMap
>>> m = MapLibreMap()
>>> m.add_zarr_control(
... default_url="https://example.com/data.zarr",
... default_variable="temperature"
... )
"""
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 for loading vector datasets from URLs.
This provides an interactive panel for users to enter URLs to
GeoJSON, GeoParquet, or FlatGeobuf datasets.
Args:
position: Control position ('top-left', 'top-right', 'bottom-left', 'bottom-right').
collapsed: Whether the panel starts collapsed.
default_url: Default vector URL to pre-fill.
load_default_url: Whether to auto-load the default URL.
default_opacity: Default layer opacity (0-1).
default_fill_color: Default fill color for polygons.
default_stroke_color: Default stroke color for lines/outlines.
fit_bounds: Whether to fit map to loaded data bounds.
**kwargs: Additional control options.
Example:
>>> from anymap_ts import MapLibreMap
>>> m = MapLibreMap()
>>> m.add_vector_control(
... default_url="https://example.com/data.geojson",
... default_fill_color="#ff0000"
... )
"""
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.
The ControlGrid provides a collapsible toolbar with up to 26 built-in
controls (search, basemap, terrain, measure, draw, etc.) in a
configurable grid layout.
Args:
position: Control position ('top-left', 'top-right', 'bottom-left',
'bottom-right').
default_controls: Explicit list of control names to include. If None,
all 26 default controls are used (minus any in ``exclude``).
Valid names: 'globe', 'fullscreen', 'north', 'terrain', 'search',
'viewState', 'inspect', 'vectorDataset', 'basemap', 'measure',
'geoEditor', 'bookmark', 'print', 'minimap', 'swipe',
'streetView', 'addVector', 'cogLayer', 'zarrLayer',
'pmtilesLayer', 'stacLayer', 'stacSearch', 'planetaryComputer',
'gaussianSplat', 'lidar', 'usgsLidar'.
exclude: Controls to remove from the default set. Ignored when
``default_controls`` is provided.
rows: Number of grid rows (auto-calculated if None).
columns: Number of grid columns (auto-calculated if None).
collapsed: Whether the grid starts collapsed. Default True.
collapsible: Whether the grid can be collapsed. Default True.
title: Optional header title for the grid.
show_row_column_controls: Show row/column input fields. Default True.
gap: Gap between grid cells in pixels. Default 2.
basemap_style_url: Basemap style URL for SwipeControl layer grouping.
If None, the current map style is used automatically.
exclude_layers: Layer ID patterns to exclude from SwipeControl
(e.g., 'measure-*', 'gl-draw-*'). If None, sensible defaults
are applied.
**kwargs: Additional ControlGrid options.
Example:
>>> from anymap_ts import MapLibreMap
>>> m = MapLibreMap()
>>> m.add_control_grid() # All 26 controls
>>> # Or with customization:
>>> m.add_control_grid(
... exclude=["minimap", "streetView"],
... collapsed=True,
... )
"""
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},
}
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
# -------------------------------------------------------------------------
# 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: MapLibre 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)
def remove_layer(self, layer_id: str) -> None:
"""Remove a layer from the map.
Args:
layer_id: Layer identifier to remove
"""
if layer_id in self._layers:
layers = dict(self._layers)
del layers[layer_id]
self._layers = layers
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.
Args:
layer_id: Layer identifier
opacity: Opacity value between 0 and 1
"""
self.call_js_method("setOpacity", layer_id, opacity)
# -------------------------------------------------------------------------
# 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.
Args:
control_type: Type of control to remove
"""
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.
Uses maplibre-gl-layer-control for layer toggling and opacity.
Args:
layers: List of layer IDs to include (None = all layers)
position: Control position
collapsed: Whether control starts collapsed
"""
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},
}
# -------------------------------------------------------------------------
# Drawing
# -------------------------------------------------------------------------
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 using maplibre-gl-geo-editor.
Args:
position: Control position
draw_modes: Drawing modes to enable (e.g., ['polygon', 'line', 'marker'])
edit_modes: Edit modes to enable (e.g., ['select', 'drag', 'delete'])
collapsed: Whether control starts collapsed
**kwargs: Additional geo-editor options
"""
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.
Returns:
GeoJSON FeatureCollection of drawn features
"""
self.call_js_method("getDrawData")
# Small delay to allow JS to update the trait
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.
Args:
geojson: GeoJSON FeatureCollection to load
"""
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.
Args:
filepath: Path to save file
driver: Output driver (auto-detected from extension if not provided)
Raises:
ImportError: If geopandas is not installed
"""
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)
# Infer driver from extension
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)
# -------------------------------------------------------------------------
# HTML Export
# -------------------------------------------------------------------------
def _generate_html_template(self) -> str:
"""Generate standalone HTML for the map."""
template_path = Path(__file__).parent / "templates" / "maplibre.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,
}
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://unpkg.com/maplibre-gl@5/dist/maplibre-gl.js"></script>
<link href="https://unpkg.com/maplibre-gl@5/dist/maplibre-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}};
const map = new maplibregl.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 maplibregl.NavigationControl();
break;
case 'scale':
control = new maplibregl.ScaleControl();
break;
case 'fullscreen':
control = new maplibregl.FullscreenControl();
break;
}
if (control) {
map.addControl(control, position);
}
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;
default:
console.log('Unknown method:', method);
}
}
</script>
</body>
</html>"""
draw_data: Dict
property
readonly
¶
Property to access current draw data.
__init__(self, center=(0.0, 0.0), zoom=2.0, width='100%', height='700px', style='https://basemaps.cartocdn.com/gl/positron-gl-style/style.json', bearing=0.0, pitch=0.0, max_pitch=85.0, controls=None, **kwargs)
special
¶
Initialize a MapLibre 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. Default is "700px". |
'700px' |
style |
Union[str, Dict] |
MapLibre style URL or style object. Default is "https://basemaps.cartocdn.com/gl/positron-gl-style/style.json". |
'https://basemaps.cartocdn.com/gl/positron-gl-style/style.json' |
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 |
controls |
Optional[Dict[str, Any]] |
Dict of controls to add. If None, defaults to {"layer-control": True, "control-grid": True}. Use {"layer-control": {"collapsed": True}} for custom options. |
None |
**kwargs |
Additional widget arguments. |
{} |
Source code in anymap_ts/maplibre.py
def __init__(
self,
center: Tuple[float, float] = (0.0, 0.0),
zoom: float = 2.0,
width: str = "100%",
height: str = "700px",
style: Union[
str, Dict
] = "https://basemaps.cartocdn.com/gl/positron-gl-style/style.json",
bearing: float = 0.0,
pitch: float = 0.0,
max_pitch: float = 85.0,
controls: Optional[Dict[str, Any]] = None,
**kwargs,
):
"""Initialize a MapLibre map.
Args:
center: Map center as (longitude, latitude).
zoom: Initial zoom level.
width: Map width as CSS string.
height: Map height as CSS string. Default is "700px".
style: MapLibre style URL or style object. Default is "https://basemaps.cartocdn.com/gl/positron-gl-style/style.json".
bearing: Map bearing in degrees.
pitch: Map pitch in degrees.
max_pitch: Maximum pitch angle in degrees (default: 85).
controls: Dict of controls to add. If None, defaults to
{"layer-control": True, "control-grid": True}.
Use {"layer-control": {"collapsed": True}} for custom options.
**kwargs: Additional widget arguments.
"""
# Handle style shortcuts
if isinstance(style, str) and not style.startswith("http"):
try:
style = get_maplibre_style(style)
except ValueError:
pass # Use as-is
super().__init__(
center=list(center),
zoom=zoom,
width=width,
height=height,
style=style,
bearing=bearing,
pitch=pitch,
max_pitch=max_pitch,
**kwargs,
)
# Initialize layer dictionary
self._layer_dict = {"Background": []}
# Add default controls
if controls is None:
controls = {
"layer-control": True,
"control-grid": True,
}
for control_name, config in controls.items():
if config:
if control_name == "layer-control":
self.add_layer_control(
**(config if isinstance(config, dict) else {})
)
elif control_name == "control-grid":
self.add_control_grid(
**(config if isinstance(config, dict) else {})
)
else:
self.add_control(
control_name, **(config if isinstance(config, dict) else {})
)
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 MapLibreMap
>>> m = MapLibreMap()
>>> 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/maplibre.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 MapLibreMap
>>> m = MapLibreMap()
>>> 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",
},
}
add_basemap(self, basemap='OpenStreetMap', attribution=None, **kwargs)
¶
Add a basemap layer.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
basemap |
str |
Name of basemap provider (e.g., "OpenStreetMap", "CartoDB.Positron") |
'OpenStreetMap' |
attribution |
Optional[str] |
Custom attribution text |
None |
**kwargs |
Additional options |
{} |
Source code in anymap_ts/maplibre.py
def add_basemap(
self,
basemap: str = "OpenStreetMap",
attribution: Optional[str] = None,
**kwargs,
) -> None:
"""Add a basemap layer.
Args:
basemap: Name of basemap provider (e.g., "OpenStreetMap", "CartoDB.Positron")
attribution: Custom attribution text
**kwargs: Additional options
"""
url, default_attribution = get_basemap_url(basemap)
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_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 for loading Cloud Optimized GeoTIFFs via UI.
This provides an interactive panel for users to enter COG URLs and configure visualization parameters like colormap and rescaling.
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 |
default_url |
Optional[str] |
Default COG URL to pre-fill. |
None |
load_default_url |
bool |
Whether to auto-load the default URL. |
False |
default_opacity |
float |
Default layer opacity (0-1). |
1.0 |
default_colormap |
str |
Default colormap name. |
'viridis' |
default_bands |
str |
Default bands (e.g., '1' or '1,2,3'). |
'1' |
default_rescale_min |
float |
Default minimum value for rescaling. |
0 |
default_rescale_max |
float |
Default maximum value for rescaling. |
255 |
**kwargs |
Additional control options. |
{} |
Examples:
>>> from anymap_ts import MapLibreMap
>>> m = MapLibreMap()
>>> m.add_cog_control(
... default_url="https://example.com/cog.tif",
... default_colormap="terrain"
... )
Source code in anymap_ts/maplibre.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 for loading Cloud Optimized GeoTIFFs via UI.
This provides an interactive panel for users to enter COG URLs
and configure visualization parameters like colormap and rescaling.
Args:
position: Control position ('top-left', 'top-right', 'bottom-left', 'bottom-right').
collapsed: Whether the panel starts collapsed.
default_url: Default COG URL to pre-fill.
load_default_url: Whether to auto-load the default URL.
default_opacity: Default layer opacity (0-1).
default_colormap: Default colormap name.
default_bands: Default bands (e.g., '1' or '1,2,3').
default_rescale_min: Default minimum value for rescaling.
default_rescale_max: Default maximum value for rescaling.
**kwargs: Additional control options.
Example:
>>> from anymap_ts import MapLibreMap
>>> m = MapLibreMap()
>>> m.add_cog_control(
... default_url="https://example.com/cog.tif",
... default_colormap="terrain"
... )
"""
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 @developmentseed/deck.gl-geotiff.
This method renders COG files directly in the browser using GPU-accelerated deck.gl-geotiff 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 Map
>>> m = Map()
>>> m.add_cog_layer(
... "https://example.com/landcover.tif",
... name="landcover",
... opacity=0.8
... )
Source code in anymap_ts/maplibre.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 @developmentseed/deck.gl-geotiff.
This method renders COG files directly in the browser using GPU-accelerated
deck.gl-geotiff 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 Map
>>> m = Map()
>>> 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,
},
}
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/maplibre.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.
The ControlGrid provides a collapsible toolbar with up to 26 built-in controls (search, basemap, terrain, measure, draw, etc.) in a configurable grid layout.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
position |
str |
Control position ('top-left', 'top-right', 'bottom-left', 'bottom-right'). |
'top-right' |
default_controls |
Optional[List[str]] |
Explicit list of control names to include. If None,
all 26 default controls are used (minus any in |
None |
exclude |
Optional[List[str]] |
Controls to remove from the default set. Ignored when
|
None |
rows |
Optional[int] |
Number of grid rows (auto-calculated if None). |
None |
columns |
Optional[int] |
Number of grid columns (auto-calculated if None). |
None |
collapsed |
bool |
Whether the grid starts collapsed. Default True. |
True |
collapsible |
bool |
Whether the grid can be collapsed. Default True. |
True |
title |
str |
Optional header title for the grid. |
'' |
show_row_column_controls |
bool |
Show row/column input fields. Default True. |
True |
gap |
int |
Gap between grid cells in pixels. Default 2. |
2 |
basemap_style_url |
Optional[str] |
Basemap style URL for SwipeControl layer grouping. If None, the current map style is used automatically. |
None |
exclude_layers |
Optional[List[str]] |
Layer ID patterns to exclude from SwipeControl (e.g., 'measure-', 'gl-draw-'). If None, sensible defaults are applied. |
None |
**kwargs |
Additional ControlGrid options. |
{} |
Examples:
>>> from anymap_ts import MapLibreMap
>>> m = MapLibreMap()
>>> m.add_control_grid() # All 26 controls
>>> # Or with customization:
>>> m.add_control_grid(
... exclude=["minimap", "streetView"],
... collapsed=True,
... )
Source code in anymap_ts/maplibre.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.
The ControlGrid provides a collapsible toolbar with up to 26 built-in
controls (search, basemap, terrain, measure, draw, etc.) in a
configurable grid layout.
Args:
position: Control position ('top-left', 'top-right', 'bottom-left',
'bottom-right').
default_controls: Explicit list of control names to include. If None,
all 26 default controls are used (minus any in ``exclude``).
Valid names: 'globe', 'fullscreen', 'north', 'terrain', 'search',
'viewState', 'inspect', 'vectorDataset', 'basemap', 'measure',
'geoEditor', 'bookmark', 'print', 'minimap', 'swipe',
'streetView', 'addVector', 'cogLayer', 'zarrLayer',
'pmtilesLayer', 'stacLayer', 'stacSearch', 'planetaryComputer',
'gaussianSplat', 'lidar', 'usgsLidar'.
exclude: Controls to remove from the default set. Ignored when
``default_controls`` is provided.
rows: Number of grid rows (auto-calculated if None).
columns: Number of grid columns (auto-calculated if None).
collapsed: Whether the grid starts collapsed. Default True.
collapsible: Whether the grid can be collapsed. Default True.
title: Optional header title for the grid.
show_row_column_controls: Show row/column input fields. Default True.
gap: Gap between grid cells in pixels. Default 2.
basemap_style_url: Basemap style URL for SwipeControl layer grouping.
If None, the current map style is used automatically.
exclude_layers: Layer ID patterns to exclude from SwipeControl
(e.g., 'measure-*', 'gl-draw-*'). If None, sensible defaults
are applied.
**kwargs: Additional ControlGrid options.
Example:
>>> from anymap_ts import MapLibreMap
>>> m = MapLibreMap()
>>> m.add_control_grid() # All 26 controls
>>> # Or with customization:
>>> m.add_control_grid(
... exclude=["minimap", "streetView"],
... collapsed=True,
... )
"""
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},
}
add_draw_control(self, position='top-right', draw_modes=None, edit_modes=None, collapsed=False, **kwargs)
¶
Add a drawing control using maplibre-gl-geo-editor.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
position |
str |
Control position |
'top-right' |
draw_modes |
Optional[List[str]] |
Drawing modes to enable (e.g., ['polygon', 'line', 'marker']) |
None |
edit_modes |
Optional[List[str]] |
Edit modes to enable (e.g., ['select', 'drag', 'delete']) |
None |
collapsed |
bool |
Whether control starts collapsed |
False |
**kwargs |
Additional geo-editor options |
{} |
Source code in anymap_ts/maplibre.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 using maplibre-gl-geo-editor.
Args:
position: Control position
draw_modes: Drawing modes to enable (e.g., ['polygon', 'line', 'marker'])
edit_modes: Edit modes to enable (e.g., ['select', 'drag', 'delete'])
collapsed: Whether control starts collapsed
**kwargs: Additional geo-editor options
"""
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_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] |
MapLibre layer type |
None |
paint |
Optional[Dict] |
MapLibre 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/maplibre.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: MapLibre layer type
paint: MapLibre 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_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 |
MapLibre 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/maplibre.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: MapLibre 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)
add_layer_control(self, layers=None, position='top-right', collapsed=True)
¶
Add a layer visibility control.
Uses maplibre-gl-layer-control for layer toggling and opacity.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
layers |
Optional[List[str]] |
List of layer IDs to include (None = all layers) |
None |
position |
str |
Control position |
'top-right' |
collapsed |
bool |
Whether control starts collapsed |
True |
Source code in anymap_ts/maplibre.py
def add_layer_control(
self,
layers: Optional[List[str]] = None,
position: str = "top-right",
collapsed: bool = True,
) -> None:
"""Add a layer visibility control.
Uses maplibre-gl-layer-control for layer toggling and opacity.
Args:
layers: List of layer IDs to include (None = all layers)
position: Control position
collapsed: Whether control starts collapsed
"""
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},
}
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, panel_max_height=600, **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 |
panel_max_height |
int |
Maximum height of the panel in pixels. |
600 |
**kwargs |
Additional control options. |
{} |
Examples:
>>> from anymap_ts import MapLibreMap
>>> m = MapLibreMap(pitch=60)
>>> m.add_lidar_control(color_scheme="classification", pickable=True)
Source code in anymap_ts/maplibre.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,
panel_max_height: int = 600,
**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.
panel_max_height: Maximum height of the panel in pixels.
**kwargs: Additional control options.
Example:
>>> from anymap_ts import MapLibreMap
>>> m = MapLibreMap(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,
panelMaxHeight=panel_max_height,
**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 MapLibreMap
>>> m = MapLibreMap(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/maplibre.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 MapLibreMap
>>> m = MapLibreMap(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",
... )
"""
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
import 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_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 for loading PMTiles files via UI.
This provides an interactive panel for users to enter PMTiles URLs and visualize vector or raster tile data.
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 |
default_url |
Optional[str] |
Default PMTiles URL to pre-fill. |
None |
load_default_url |
bool |
Whether to auto-load the default URL. |
False |
default_opacity |
float |
Default layer opacity (0-1). |
1.0 |
default_fill_color |
str |
Default fill color for vector polygons. |
'steelblue' |
default_line_color |
str |
Default line color for vector lines. |
'#333' |
default_pickable |
bool |
Whether features are clickable by default. |
True |
**kwargs |
Additional control options. |
{} |
Examples:
>>> from anymap_ts import MapLibreMap
>>> m = MapLibreMap()
>>> m.add_pmtiles_control(
... default_url="https://pmtiles.io/protomaps(vector)ODbL_firenze.pmtiles",
... load_default_url=True
... )
Source code in anymap_ts/maplibre.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 for loading PMTiles files via UI.
This provides an interactive panel for users to enter PMTiles URLs
and visualize vector or raster tile data.
Args:
position: Control position ('top-left', 'top-right', 'bottom-left', 'bottom-right').
collapsed: Whether the panel starts collapsed.
default_url: Default PMTiles URL to pre-fill.
load_default_url: Whether to auto-load the default URL.
default_opacity: Default layer opacity (0-1).
default_fill_color: Default fill color for vector polygons.
default_line_color: Default line color for vector lines.
default_pickable: Whether features are clickable by default.
**kwargs: Additional control options.
Example:
>>> from anymap_ts import MapLibreMap
>>> m = MapLibreMap()
>>> m.add_pmtiles_control(
... default_url="https://pmtiles.io/protomaps(vector)ODbL_firenze.pmtiles",
... load_default_url=True
... )
"""
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_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 MapLibreMap
>>> import numpy as np
>>> m = MapLibreMap(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/maplibre.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 MapLibreMap
>>> import numpy as np
>>> m = MapLibreMap(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",
},
}
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.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
source |
str |
Path to local raster file |
required |
name |
Optional[str] |
Layer name |
None |
attribution |
str |
Attribution text |
'' |
indexes |
Optional[List[int]] |
Band indexes to use |
None |
colormap |
Optional[str] |
Colormap name |
None |
vmin |
Optional[float] |
Minimum value for colormap |
None |
vmax |
Optional[float] |
Maximum value for colormap |
None |
nodata |
Optional[float] |
NoData value |
None |
fit_bounds |
bool |
Whether to fit map to raster bounds |
True |
**kwargs |
Additional options |
{} |
Source code in anymap_ts/maplibre.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.
Args:
source: Path to local raster file
name: Layer name
attribution: Attribution text
indexes: Band indexes to use
colormap: Colormap name
vmin: Minimum value for colormap
vmax: Maximum value for colormap
nodata: NoData value
fit_bounds: Whether to fit map to raster bounds
**kwargs: Additional options
"""
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)
# Build tile URL with parameters
tile_url = client.get_tile_url()
if indexes:
tile_url = client.get_tile_url(indexes=indexes)
if colormap:
tile_url = client.get_tile_url(colormap=colormap)
if vmin is not None or vmax is not None:
tile_url = client.get_tile_url(
vmin=vmin or client.min, vmax=vmax or client.max
)
if nodata is not None:
tile_url = client.get_tile_url(nodata=nodata)
layer_name = name or Path(source).stem
self.add_tile_layer(
tile_url,
name=layer_name,
attribution=attribution,
**kwargs,
)
# Fit bounds if requested
if fit_bounds:
bounds = client.bounds()
if bounds:
self.fit_bounds([bounds[0], bounds[1], bounds[2], bounds[3]])
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/maplibre.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",
},
}
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] |
MapLibre layer type ('circle', 'line', 'fill', 'symbol') |
None |
paint |
Optional[Dict] |
MapLibre 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/maplibre.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: MapLibre layer type ('circle', 'line', 'fill', 'symbol')
paint: MapLibre paint properties
name: Layer name
fit_bounds: Whether to fit map to data bounds
**kwargs: Additional layer options
"""
geojson = to_geojson(data)
# Handle URL data
if geojson.get("type") == "url":
self.add_geojson(
geojson["url"],
layer_type=layer_type,
paint=paint,
name=name,
fit_bounds=fit_bounds,
**kwargs,
)
return
layer_id = name or f"vector-{len(self._layers)}"
# 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
bounds = get_bounds(data) 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,
},
}
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 for loading vector datasets from URLs.
This provides an interactive panel for users to enter URLs to GeoJSON, GeoParquet, or FlatGeobuf datasets.
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 |
default_url |
Optional[str] |
Default vector URL to pre-fill. |
None |
load_default_url |
bool |
Whether to auto-load the default URL. |
False |
default_opacity |
float |
Default layer opacity (0-1). |
1.0 |
default_fill_color |
str |
Default fill color for polygons. |
'#3388ff' |
default_stroke_color |
str |
Default stroke color for lines/outlines. |
'#3388ff' |
fit_bounds |
bool |
Whether to fit map to loaded data bounds. |
True |
**kwargs |
Additional control options. |
{} |
Examples:
>>> from anymap_ts import MapLibreMap
>>> m = MapLibreMap()
>>> m.add_vector_control(
... default_url="https://example.com/data.geojson",
... default_fill_color="#ff0000"
... )
Source code in anymap_ts/maplibre.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 for loading vector datasets from URLs.
This provides an interactive panel for users to enter URLs to
GeoJSON, GeoParquet, or FlatGeobuf datasets.
Args:
position: Control position ('top-left', 'top-right', 'bottom-left', 'bottom-right').
collapsed: Whether the panel starts collapsed.
default_url: Default vector URL to pre-fill.
load_default_url: Whether to auto-load the default URL.
default_opacity: Default layer opacity (0-1).
default_fill_color: Default fill color for polygons.
default_stroke_color: Default stroke color for lines/outlines.
fit_bounds: Whether to fit map to loaded data bounds.
**kwargs: Additional control options.
Example:
>>> from anymap_ts import MapLibreMap
>>> m = MapLibreMap()
>>> m.add_vector_control(
... default_url="https://example.com/data.geojson",
... default_fill_color="#ff0000"
... )
"""
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_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 for loading Zarr datasets via UI.
This provides an interactive panel for users to enter Zarr URLs and configure visualization parameters.
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 |
default_url |
Optional[str] |
Default Zarr URL to pre-fill. |
None |
load_default_url |
bool |
Whether to auto-load the default URL. |
False |
default_opacity |
float |
Default layer opacity (0-1). |
1.0 |
default_variable |
str |
Default variable name. |
'' |
default_clim |
Optional[Tuple[float, float]] |
Default color limits (min, max). |
None |
**kwargs |
Additional control options. |
{} |
Examples:
>>> from anymap_ts import MapLibreMap
>>> m = MapLibreMap()
>>> m.add_zarr_control(
... default_url="https://example.com/data.zarr",
... default_variable="temperature"
... )
Source code in anymap_ts/maplibre.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 for loading Zarr datasets via UI.
This provides an interactive panel for users to enter Zarr URLs
and configure visualization parameters.
Args:
position: Control position ('top-left', 'top-right', 'bottom-left', 'bottom-right').
collapsed: Whether the panel starts collapsed.
default_url: Default Zarr URL to pre-fill.
load_default_url: Whether to auto-load the default URL.
default_opacity: Default layer opacity (0-1).
default_variable: Default variable name.
default_clim: Default color limits (min, max).
**kwargs: Additional control options.
Example:
>>> from anymap_ts import MapLibreMap
>>> m = MapLibreMap()
>>> m.add_zarr_control(
... default_url="https://example.com/data.zarr",
... default_variable="temperature"
... )
"""
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.
This method renders Zarr pyramid datasets directly in the browser using GPU-accelerated WebGL rendering via @carbonplan/zarr-layer.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
url |
str |
URL to the Zarr store (pyramid format recommended). |
required |
variable |
str |
Variable name in the Zarr dataset to visualize. |
required |
name |
Optional[str] |
Layer ID. If None, auto-generated. |
None |
colormap |
Optional[List[str]] |
List of hex color strings for visualization. Example: ['#0000ff', '#ffff00', '#ff0000'] (blue-yellow-red). Default: ['#000000', '#ffffff'] (black to white). |
None |
clim |
Optional[Tuple[float, float]] |
Color range as (min, max) tuple. Default: (0, 100). |
None |
opacity |
float |
Layer opacity (0-1). |
1.0 |
selector |
Optional[Dict[str, Any]] |
Dimension selector for multi-dimensional data. Example: {"month": 4} to select 4th month. |
None |
minzoom |
int |
Minimum zoom level for rendering. |
0 |
maxzoom |
int |
Maximum zoom level for rendering. |
22 |
fill_value |
Optional[float] |
No-data value (auto-detected from metadata if not set). |
None |
spatial_dimensions |
Optional[Dict[str, str]] |
Custom spatial dimension names. Example: {"lat": "y", "lon": "x"} for non-standard names. |
None |
zarr_version |
Optional[int] |
Zarr format version (2 or 3). Auto-detected if not set. |
None |
bounds |
Optional[List[float]] |
Explicit spatial bounds [xMin, yMin, xMax, yMax]. Units depend on CRS: degrees for EPSG:4326, meters for EPSG:3857. |
None |
**kwargs |
Additional ZarrLayer props. |
{} |
Examples:
>>> from anymap_ts import Map
>>> m = Map()
>>> m.add_zarr_layer(
... "https://example.com/climate.zarr",
... variable="temperature",
... clim=(270, 310),
... colormap=['#0000ff', '#ffff00', '#ff0000'],
... selector={"month": 7}
... )
Source code in anymap_ts/maplibre.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.
This method renders Zarr pyramid datasets directly in the browser using
GPU-accelerated WebGL rendering via @carbonplan/zarr-layer.
Args:
url: URL to the Zarr store (pyramid format recommended).
variable: Variable name in the Zarr dataset to visualize.
name: Layer ID. If None, auto-generated.
colormap: List of hex color strings for visualization.
Example: ['#0000ff', '#ffff00', '#ff0000'] (blue-yellow-red).
Default: ['#000000', '#ffffff'] (black to white).
clim: Color range as (min, max) tuple.
Default: (0, 100).
opacity: Layer opacity (0-1).
selector: Dimension selector for multi-dimensional data.
Example: {"month": 4} to select 4th month.
minzoom: Minimum zoom level for rendering.
maxzoom: Maximum zoom level for rendering.
fill_value: No-data value (auto-detected from metadata if not set).
spatial_dimensions: Custom spatial dimension names.
Example: {"lat": "y", "lon": "x"} for non-standard names.
zarr_version: Zarr format version (2 or 3). Auto-detected if not set.
bounds: Explicit spatial bounds [xMin, yMin, xMax, yMax].
Units depend on CRS: degrees for EPSG:4326, meters for EPSG:3857.
**kwargs: Additional ZarrLayer props.
Example:
>>> from anymap_ts import Map
>>> m = Map()
>>> m.add_zarr_layer(
... "https://example.com/climate.zarr",
... variable="temperature",
... clim=(270, 310),
... colormap=['#0000ff', '#ffff00', '#ff0000'],
... selector={"month": 7}
... )
"""
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,
},
}
clear_draw_data(self)
¶
Clear all drawn features.
Source code in anymap_ts/maplibre.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.
Returns:
| Type | Description |
|---|---|
Dict |
GeoJSON FeatureCollection of drawn features |
Source code in anymap_ts/maplibre.py
def get_draw_data(self) -> Dict:
"""Get the current drawn features as GeoJSON.
Returns:
GeoJSON FeatureCollection of drawn features
"""
self.call_js_method("getDrawData")
# Small delay to allow JS to update the trait
import time
time.sleep(0.1)
return self._draw_data or {"type": "FeatureCollection", "features": []}
load_draw_data(self, geojson)
¶
Load GeoJSON features into the drawing layer.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
geojson |
Dict |
GeoJSON FeatureCollection to load |
required |
Source code in anymap_ts/maplibre.py
def load_draw_data(self, geojson: Dict) -> None:
"""Load GeoJSON features into the drawing layer.
Args:
geojson: GeoJSON FeatureCollection to load
"""
self._draw_data = geojson
self.call_js_method("loadDrawData", geojson)
remove_arc_layer(self, layer_id)
¶
Remove an arc layer.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
layer_id |
str |
Layer identifier to remove. |
required |
Source code in anymap_ts/maplibre.py
def remove_arc_layer(self, layer_id: str) -> None:
"""Remove an arc layer.
Args:
layer_id: Layer identifier to remove.
"""
if layer_id in self._layers:
layers = dict(self._layers)
del layers[layer_id]
self._layers = layers
self.call_js_method("removeArcLayer", layer_id)
remove_cog_layer(self, layer_id)
¶
Remove a COG layer.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
layer_id |
str |
Layer identifier to remove. |
required |
Source code in anymap_ts/maplibre.py
def remove_cog_layer(self, layer_id: str) -> None:
"""Remove a COG layer.
Args:
layer_id: Layer identifier to remove.
"""
if layer_id in self._layers:
layers = dict(self._layers)
del layers[layer_id]
self._layers = layers
self.call_js_method("removeCOGLayer", layer_id)
remove_control(self, control_type)
¶
Remove a map control.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
control_type |
str |
Type of control to remove |
required |
Source code in anymap_ts/maplibre.py
def remove_control(self, control_type: str) -> None:
"""Remove a map control.
Args:
control_type: Type of control to remove
"""
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_layer(self, layer_id)
¶
Remove a layer from the map.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
layer_id |
str |
Layer identifier to remove |
required |
Source code in anymap_ts/maplibre.py
def remove_layer(self, layer_id: str) -> None:
"""Remove a layer from the map.
Args:
layer_id: Layer identifier to remove
"""
if layer_id in self._layers:
layers = dict(self._layers)
del layers[layer_id]
self._layers = layers
self.call_js_method("removeLayer", layer_id)
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/maplibre.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_point_cloud_layer(self, layer_id)
¶
Remove a point cloud layer.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
layer_id |
str |
Layer identifier to remove. |
required |
Source code in anymap_ts/maplibre.py
def remove_point_cloud_layer(self, layer_id: str) -> None:
"""Remove a point cloud layer.
Args:
layer_id: Layer identifier to remove.
"""
if layer_id in self._layers:
layers = dict(self._layers)
del layers[layer_id]
self._layers = layers
self.call_js_method("removePointCloudLayer", layer_id)
remove_zarr_layer(self, layer_id)
¶
Remove a Zarr layer.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
layer_id |
str |
Layer identifier to remove. |
required |
Source code in anymap_ts/maplibre.py
def remove_zarr_layer(self, layer_id: str) -> None:
"""Remove a Zarr layer.
Args:
layer_id: Layer identifier to remove.
"""
if layer_id in self._layers:
layers = dict(self._layers)
del layers[layer_id]
self._layers = layers
self.call_js_method("removeZarrLayer", layer_id)
save_draw_data(self, filepath, driver=None)
¶
Save drawn features to a file.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
filepath |
Union[str, Path] |
Path to save file |
required |
driver |
Optional[str] |
Output driver (auto-detected from extension if not provided) |
None |
Exceptions:
| Type | Description |
|---|---|
ImportError |
If geopandas is not installed |
Source code in anymap_ts/maplibre.py
def save_draw_data(
self,
filepath: Union[str, Path],
driver: Optional[str] = None,
) -> None:
"""Save drawn features to a file.
Args:
filepath: Path to save file
driver: Output driver (auto-detected from extension if not provided)
Raises:
ImportError: If geopandas is not installed
"""
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)
# Infer driver from extension
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)
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/maplibre.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/maplibre.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/maplibre.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.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
layer_id |
str |
Layer identifier |
required |
opacity |
float |
Opacity value between 0 and 1 |
required |
Source code in anymap_ts/maplibre.py
def set_opacity(self, layer_id: str, opacity: float) -> None:
"""Set layer opacity.
Args:
layer_id: Layer identifier
opacity: Opacity value between 0 and 1
"""
self.call_js_method("setOpacity", layer_id, opacity)
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/maplibre.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)
update_zarr_layer(self, layer_id, selector=None, clim=None, colormap=None, opacity=None)
¶
Update a Zarr layer's properties dynamically.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
layer_id |
str |
Layer identifier. |
required |
selector |
Optional[Dict[str, Any]] |
New dimension selector. |
None |
clim |
Optional[Tuple[float, float]] |
New color range. |
None |
colormap |
Optional[List[str]] |
New colormap. |
None |
opacity |
Optional[float] |
New opacity value (0-1). |
None |
Source code in anymap_ts/maplibre.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.
Args:
layer_id: Layer identifier.
selector: New dimension selector.
clim: New color range.
colormap: New colormap.
opacity: New opacity value (0-1).
"""
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)