leaflet module¶
Leaflet map widget implementation.
LeafletMap (MapWidget)
¶
Interactive map widget using Leaflet.
This class provides a Python interface to Leaflet maps with full bidirectional communication through anywidget.
Note
Leaflet uses [lat, lng] order internally, but this class accepts [lng, lat] for consistency with other map libraries.
Examples:
>>> from anymap_ts import LeafletMap
>>> m = LeafletMap(center=[-122.4, 37.8], zoom=10)
>>> m.add_basemap("OpenStreetMap")
>>> m
Source code in anymap_ts/leaflet.py
class LeafletMap(MapWidget):
"""Interactive map widget using Leaflet.
This class provides a Python interface to Leaflet maps with
full bidirectional communication through anywidget.
Note:
Leaflet uses [lat, lng] order internally, but this class
accepts [lng, lat] for consistency with other map libraries.
Example:
>>> from anymap_ts import LeafletMap
>>> m = LeafletMap(center=[-122.4, 37.8], zoom=10)
>>> m.add_basemap("OpenStreetMap")
>>> m
"""
_esm = STATIC_DIR / "leaflet.js"
_css = STATIC_DIR / "leaflet.css"
_layer_dict = traitlets.Dict({}).tag(sync=True)
def __init__(
self,
center: Tuple[float, float] = (0.0, 0.0),
zoom: float = 2.0,
width: str = "100%",
height: str = "600px",
controls: Optional[Dict[str, Any]] = None,
**kwargs,
):
"""Initialize a Leaflet map.
Args:
center: Map center as (longitude, latitude).
zoom: Initial zoom level.
width: Map width as CSS string.
height: Map height as CSS string.
controls: Dict of controls to add (e.g., {"zoom": True}).
**kwargs: Additional widget arguments.
"""
super().__init__(
center=list(center),
zoom=zoom,
width=width,
height=height,
style="",
**kwargs,
)
self._layer_dict = {"Background": []}
if controls is None:
controls = {
"scale": {"position": "bottom-left"},
"attribution": {"position": "bottom-right"},
"layers": {"position": "top-right"},
}
for control_name, config in controls.items():
if config:
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") or a tile URL.
attribution: Custom attribution text.
**kwargs: Additional options.
"""
try:
url, default_attribution = get_basemap_url(basemap)
except (ValueError, KeyError):
url = basemap
default_attribution = ""
self.call_js_method(
"addBasemap",
url,
attribution=attribution or default_attribution,
name=basemap,
**kwargs,
)
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,
style: Optional[Dict] = None,
name: Optional[str] = None,
fit_bounds: bool = True,
popup_properties: Optional[Union[List[str], bool]] = None,
tooltip_property: Optional[str] = None,
**kwargs,
) -> None:
"""Add vector data to the map.
Supports GeoJSON dicts, GeoDataFrames, or file paths.
Args:
data: GeoJSON dict, GeoDataFrame, or path to vector file.
style: Leaflet style properties.
name: Layer name.
fit_bounds: Whether to fit map to data bounds.
popup_properties: List of property names to show in popups,
or True to show all properties.
tooltip_property: Property name to use as tooltip text.
**kwargs: Additional layer options.
"""
geojson = to_geojson(data)
if geojson.get("type") == "url":
self.add_geojson(
geojson["url"],
style=style,
name=name,
fit_bounds=fit_bounds,
popup_properties=popup_properties,
tooltip_property=tooltip_property,
**kwargs,
)
return
layer_id = name or f"vector-{len(self._layers)}"
if style is None:
layer_type = _infer_leaflet_type(geojson)
style = _get_default_style(layer_type)
bounds = get_bounds(data) if fit_bounds else None
js_kwargs: Dict[str, Any] = {
"data": geojson,
"name": layer_id,
"style": style,
"fitBounds": fit_bounds,
"bounds": bounds,
}
if popup_properties is not None:
js_kwargs["popupProperties"] = popup_properties
if tooltip_property is not None:
js_kwargs["tooltipProperty"] = tooltip_property
self.call_js_method("addGeoJSON", **js_kwargs, **kwargs)
self._layers = {
**self._layers,
layer_id: {"id": layer_id, "type": "geojson", "style": style},
}
def add_geojson(
self,
data: Union[str, Dict],
style: Optional[Dict] = None,
name: Optional[str] = None,
fit_bounds: bool = True,
popup_properties: Optional[Union[List[str], bool]] = None,
tooltip_property: Optional[str] = None,
**kwargs,
) -> None:
"""Add GeoJSON data to the map.
Args:
data: GeoJSON dict or URL to GeoJSON file.
style: Leaflet style properties.
name: Layer name.
fit_bounds: Whether to fit map to data bounds.
popup_properties: List of property names to show in popups,
or True to show all properties.
tooltip_property: Property name to use as tooltip text.
**kwargs: Additional layer options.
"""
self.add_vector(
data,
style=style,
name=name,
fit_bounds=fit_bounds,
popup_properties=popup_properties,
tooltip_property=tooltip_property,
**kwargs,
)
# -------------------------------------------------------------------------
# Raster / Tile Methods
# -------------------------------------------------------------------------
def add_tile_layer(
self,
url: str,
name: Optional[str] = None,
attribution: str = "",
min_zoom: int = 0,
max_zoom: int = 22,
opacity: float = 1.0,
**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.
opacity: Layer opacity (0 to 1).
**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,
opacity=opacity,
**kwargs,
)
self._layers = {
**self._layers,
layer_id: {"id": layer_id, "type": "tile"},
}
def add_wms_layer(
self,
url: str,
layers: str,
name: Optional[str] = None,
format: str = "image/png",
transparent: bool = True,
attribution: str = "",
opacity: float = 1.0,
crs: Optional[str] = None,
styles: str = "",
version: str = "1.1.1",
**kwargs,
) -> None:
"""Add a WMS (Web Map Service) tile layer.
Args:
url: WMS service base URL.
layers: Comma-separated WMS layer names.
name: Layer name for the control.
format: Image format (e.g., "image/png", "image/jpeg").
transparent: Request transparent background.
attribution: Attribution text.
opacity: Layer opacity (0 to 1).
crs: Coordinate reference system (e.g., "EPSG:4326").
styles: WMS styles parameter.
version: WMS version string.
**kwargs: Additional WMS parameters.
"""
layer_id = name or f"wms-{len(self._layers)}"
self.call_js_method(
"addWMSLayer",
url,
name=layer_id,
layers=layers,
format=format,
transparent=transparent,
attribution=attribution,
opacity=opacity,
crs=crs,
styles=styles,
version=version,
**kwargs,
)
self._layers = {
**self._layers,
layer_id: {"id": layer_id, "type": "wms"},
}
def remove_wms_layer(self, layer_id: str) -> None:
"""Remove a WMS layer.
Args:
layer_id: Layer identifier to remove.
"""
self.remove_layer(layer_id)
# -------------------------------------------------------------------------
# Layer Management
# -------------------------------------------------------------------------
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 = "topright",
**kwargs,
) -> None:
"""Add a map control.
Args:
control_type: Type of control ('zoom', 'scale', 'attribution',
'layers').
position: Control position ('topleft', 'topright', 'bottomleft',
'bottomright').
**kwargs: Control-specific options.
"""
position_map = {
"top-left": "topleft",
"top-right": "topright",
"bottom-left": "bottomleft",
"bottom-right": "bottomright",
}
pos = position_map.get(position, position)
self.call_js_method("addControl", control_type, position=pos, **kwargs)
self._controls = {
**self._controls,
control_type: {"type": control_type, "position": pos, **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,
position: str = "topright",
collapsed: bool = True,
) -> None:
"""Add a layer control for toggling layer visibility.
Args:
position: Control position.
collapsed: Whether control starts collapsed.
"""
self.add_control("layers", position=position, collapsed=collapsed)
# -------------------------------------------------------------------------
# Markers
# -------------------------------------------------------------------------
def add_marker(
self,
lng: float,
lat: float,
popup: Optional[str] = None,
tooltip: Optional[str] = None,
marker_id: Optional[str] = None,
draggable: bool = False,
opacity: float = 1.0,
icon_url: Optional[str] = None,
icon_size: Optional[Tuple[int, int]] = None,
icon_anchor: Optional[Tuple[int, int]] = None,
) -> None:
"""Add a marker to the map.
Args:
lng: Longitude.
lat: Latitude.
popup: HTML content for popup.
tooltip: Tooltip text.
marker_id: Unique marker ID.
draggable: Whether marker can be dragged.
opacity: Marker opacity (0 to 1).
icon_url: URL to custom icon image.
icon_size: Icon size as (width, height) in pixels.
icon_anchor: Icon anchor point as (x, y) in pixels.
"""
kw: Dict[str, Any] = {"popup": popup, "id": marker_id}
if tooltip:
kw["tooltip"] = tooltip
if draggable:
kw["draggable"] = True
if opacity != 1.0:
kw["opacity"] = opacity
if icon_url:
kw["iconUrl"] = icon_url
if icon_size:
kw["iconSize"] = list(icon_size)
if icon_anchor:
kw["iconAnchor"] = list(icon_anchor)
self.call_js_method("addMarker", lng, lat, **kw)
def add_markers(
self,
data: List[Dict[str, Any]],
name: Optional[str] = None,
) -> None:
"""Add multiple markers as a layer group.
Each item in *data* should be a dict with at least ``lng`` and
``lat`` keys. Optional keys: ``popup``, ``tooltip``, ``iconUrl``,
``iconSize``.
Args:
data: List of marker dicts.
name: Layer group name.
"""
layer_id = name or f"markers-{len(self._layers)}"
self.call_js_method("addMarkers", data=data, name=layer_id)
self._layers = {
**self._layers,
layer_id: {"id": layer_id, "type": "markers"},
}
def remove_marker(self, marker_id: str) -> None:
"""Remove a marker from the map.
Args:
marker_id: Marker ID to remove.
"""
self.call_js_method("removeMarker", marker_id)
# -------------------------------------------------------------------------
# Shapes (Circle, CircleMarker, Polyline, Polygon, Rectangle)
# -------------------------------------------------------------------------
def add_circle_marker(
self,
lng: float,
lat: float,
radius: int = 10,
name: Optional[str] = None,
color: str = "#3388ff",
fill_color: Optional[str] = None,
fill_opacity: float = 0.5,
weight: int = 2,
opacity: float = 1.0,
popup: Optional[str] = None,
tooltip: Optional[str] = None,
) -> None:
"""Add a circle marker (fixed pixel radius) to the map.
Args:
lng: Longitude.
lat: Latitude.
radius: Radius in pixels.
name: Layer name.
color: Stroke color.
fill_color: Fill color (defaults to stroke color).
fill_opacity: Fill opacity.
weight: Stroke weight in pixels.
opacity: Stroke opacity.
popup: Popup HTML content.
tooltip: Tooltip text.
"""
layer_id = name or f"circle-marker-{len(self._layers)}"
self.call_js_method(
"addCircleMarker",
lng,
lat,
name=layer_id,
radius=radius,
color=color,
fillColor=fill_color or color,
fillOpacity=fill_opacity,
weight=weight,
opacity=opacity,
popup=popup,
tooltip=tooltip,
)
self._layers = {
**self._layers,
layer_id: {"id": layer_id, "type": "circle_marker"},
}
def add_circle(
self,
lng: float,
lat: float,
radius: float = 1000,
name: Optional[str] = None,
color: str = "#3388ff",
fill_color: Optional[str] = None,
fill_opacity: float = 0.2,
weight: int = 2,
opacity: float = 1.0,
popup: Optional[str] = None,
tooltip: Optional[str] = None,
) -> None:
"""Add a geographic circle (radius in meters) to the map.
Args:
lng: Center longitude.
lat: Center latitude.
radius: Radius in meters.
name: Layer name.
color: Stroke color.
fill_color: Fill color (defaults to stroke color).
fill_opacity: Fill opacity.
weight: Stroke weight in pixels.
opacity: Stroke opacity.
popup: Popup HTML content.
tooltip: Tooltip text.
"""
layer_id = name or f"circle-{len(self._layers)}"
self.call_js_method(
"addCircle",
lng,
lat,
name=layer_id,
radius=radius,
color=color,
fillColor=fill_color or color,
fillOpacity=fill_opacity,
weight=weight,
opacity=opacity,
popup=popup,
tooltip=tooltip,
)
self._layers = {
**self._layers,
layer_id: {"id": layer_id, "type": "circle"},
}
def add_polyline(
self,
coordinates: Sequence[Sequence[float]],
name: Optional[str] = None,
color: str = "#3388ff",
weight: int = 3,
opacity: float = 1.0,
dash_array: Optional[str] = None,
popup: Optional[str] = None,
tooltip: Optional[str] = None,
fit_bounds: bool = False,
) -> None:
"""Add a polyline to the map.
Args:
coordinates: List of [lng, lat] pairs.
name: Layer name.
color: Line color.
weight: Line weight in pixels.
opacity: Line opacity.
dash_array: Dash pattern (e.g., "5 10").
popup: Popup HTML content.
tooltip: Tooltip text.
fit_bounds: Whether to fit map to polyline bounds.
"""
layer_id = name or f"polyline-{len(self._layers)}"
kw: Dict[str, Any] = {
"coordinates": [list(c) for c in coordinates],
"name": layer_id,
"color": color,
"weight": weight,
"opacity": opacity,
"fitBounds": fit_bounds,
}
if dash_array:
kw["dashArray"] = dash_array
if popup:
kw["popup"] = popup
if tooltip:
kw["tooltip"] = tooltip
self.call_js_method("addPolyline", **kw)
self._layers = {
**self._layers,
layer_id: {"id": layer_id, "type": "polyline"},
}
def add_polygon(
self,
coordinates: Sequence[Sequence[float]],
name: Optional[str] = None,
color: str = "#3388ff",
fill_color: Optional[str] = None,
fill_opacity: float = 0.5,
weight: int = 2,
opacity: float = 1.0,
popup: Optional[str] = None,
tooltip: Optional[str] = None,
fit_bounds: bool = False,
) -> None:
"""Add a polygon to the map.
Args:
coordinates: List of [lng, lat] vertex pairs.
name: Layer name.
color: Stroke color.
fill_color: Fill color (defaults to stroke color).
fill_opacity: Fill opacity.
weight: Stroke weight in pixels.
opacity: Stroke opacity.
popup: Popup HTML content.
tooltip: Tooltip text.
fit_bounds: Whether to fit map to polygon bounds.
"""
layer_id = name or f"polygon-{len(self._layers)}"
self.call_js_method(
"addPolygon",
coordinates=[list(c) for c in coordinates],
name=layer_id,
color=color,
fillColor=fill_color or color,
fillOpacity=fill_opacity,
weight=weight,
opacity=opacity,
popup=popup,
tooltip=tooltip,
fitBounds=fit_bounds,
)
self._layers = {
**self._layers,
layer_id: {"id": layer_id, "type": "polygon"},
}
def add_rectangle(
self,
bounds: Tuple[float, float, float, float],
name: Optional[str] = None,
color: str = "#3388ff",
fill_color: Optional[str] = None,
fill_opacity: float = 0.2,
weight: int = 2,
opacity: float = 1.0,
popup: Optional[str] = None,
tooltip: Optional[str] = None,
) -> None:
"""Add a rectangle to the map.
Args:
bounds: Bounding box as (west, south, east, north).
name: Layer name.
color: Stroke color.
fill_color: Fill color (defaults to stroke color).
fill_opacity: Fill opacity.
weight: Stroke weight in pixels.
opacity: Stroke opacity.
popup: Popup HTML content.
tooltip: Tooltip text.
"""
layer_id = name or f"rectangle-{len(self._layers)}"
self.call_js_method(
"addRectangle",
bounds=list(bounds),
name=layer_id,
color=color,
fillColor=fill_color or color,
fillOpacity=fill_opacity,
weight=weight,
opacity=opacity,
popup=popup,
tooltip=tooltip,
)
self._layers = {
**self._layers,
layer_id: {"id": layer_id, "type": "rectangle"},
}
# -------------------------------------------------------------------------
# Overlays (Image, Video)
# -------------------------------------------------------------------------
def add_image_overlay(
self,
url: str,
bounds: Tuple[float, float, float, float],
name: Optional[str] = None,
opacity: float = 1.0,
interactive: bool = False,
) -> None:
"""Add an image overlay to the map.
Args:
url: URL of the image to overlay.
bounds: Bounding box as (west, south, east, north).
name: Layer name.
opacity: Image opacity (0 to 1).
interactive: Whether the overlay responds to mouse events.
"""
layer_id = name or f"image-{len(self._layers)}"
self.call_js_method(
"addImageOverlay",
url,
bounds=list(bounds),
name=layer_id,
opacity=opacity,
interactive=interactive,
)
self._layers = {
**self._layers,
layer_id: {"id": layer_id, "type": "image_overlay"},
}
def add_video_overlay(
self,
url: Union[str, List[str]],
bounds: Tuple[float, float, float, float],
name: Optional[str] = None,
opacity: float = 1.0,
autoplay: bool = True,
loop: bool = True,
muted: bool = True,
) -> None:
"""Add a video overlay to the map.
Args:
url: Video URL or list of URLs (for multiple formats).
bounds: Bounding box as (west, south, east, north).
name: Layer name.
opacity: Video opacity (0 to 1).
autoplay: Whether to autoplay the video.
loop: Whether to loop the video.
muted: Whether to mute the video.
"""
layer_id = name or f"video-{len(self._layers)}"
self.call_js_method(
"addVideoOverlay",
url=url if isinstance(url, list) else [url],
bounds=list(bounds),
name=layer_id,
opacity=opacity,
autoplay=autoplay,
loop=loop,
muted=muted,
)
self._layers = {
**self._layers,
layer_id: {"id": layer_id, "type": "video_overlay"},
}
# -------------------------------------------------------------------------
# Heatmap
# -------------------------------------------------------------------------
def add_heatmap(
self,
data: Any,
name: Optional[str] = None,
radius: int = 25,
blur: int = 15,
max_zoom: int = 18,
max_val: float = 1.0,
min_opacity: float = 0.05,
gradient: Optional[Dict[str, str]] = None,
value_column: Optional[str] = None,
lat_column: str = "lat",
lng_column: str = "lng",
) -> None:
"""Add a heatmap layer.
Accepts a list of ``[lng, lat]`` or ``[lng, lat, intensity]``
arrays, a GeoJSON FeatureCollection of Points, or a pandas/
geopandas DataFrame.
Args:
data: Heat data – list of [lng, lat, intensity?] arrays,
GeoJSON FeatureCollection, or DataFrame.
name: Layer name.
radius: Heatmap point radius in pixels.
blur: Blur radius in pixels.
max_zoom: Zoom level at which points reach full intensity.
max_val: Maximum point intensity.
min_opacity: Minimum opacity of heat points.
gradient: Custom color gradient as {stop: color} dict,
e.g. ``{0.4: "blue", 0.65: "lime", 1: "red"}``.
value_column: Column name for intensity (DataFrame input).
lat_column: Column name for latitude (DataFrame input).
lng_column: Column name for longitude (DataFrame input).
"""
layer_id = name or f"heatmap-{len(self._layers)}"
heat_data = self._normalize_heatmap_data(
data, value_column, lat_column, lng_column
)
kw: Dict[str, Any] = {
"data": heat_data,
"name": layer_id,
"radius": radius,
"blur": blur,
"maxZoom": max_zoom,
"max": max_val,
"minOpacity": min_opacity,
}
if gradient:
kw["gradient"] = {str(k): v for k, v in gradient.items()}
self.call_js_method("addHeatmap", **kw)
self._layers = {
**self._layers,
layer_id: {"id": layer_id, "type": "heatmap"},
}
def remove_heatmap(self, name: str) -> None:
"""Remove a heatmap layer.
Args:
name: Heatmap layer name.
"""
self.remove_layer(name)
@staticmethod
def _normalize_heatmap_data(
data: Any,
value_column: Optional[str] = None,
lat_column: str = "lat",
lng_column: str = "lng",
) -> List[List[float]]:
"""Convert various data formats to [[lng, lat, intensity], …]."""
# Already a list of lists/tuples
if isinstance(data, (list, tuple)) and len(data) > 0:
first = data[0]
if isinstance(first, (list, tuple)):
return [list(p) for p in data]
# GeoJSON FeatureCollection
if isinstance(data, dict):
if data.get("type") == "FeatureCollection":
result = []
for f in data.get("features", []):
geom = f.get("geometry", {})
if geom.get("type") == "Point":
coords = geom["coordinates"]
intensity = 1.0
if value_column and f.get("properties"):
val = f["properties"].get(value_column)
if val is not None:
intensity = float(val)
result.append([coords[0], coords[1], intensity])
return result
# DataFrame / GeoDataFrame
try:
import pandas as pd
if isinstance(data, pd.DataFrame):
has_geometry = hasattr(data, "geometry") and hasattr(data.geometry, "x")
result = []
for _, row in data.iterrows():
if has_geometry:
lng_val = row.geometry.x
lat_val = row.geometry.y
else:
lng_val = row[lng_column]
lat_val = row[lat_column]
intensity = float(row[value_column]) if value_column else 1.0
result.append([float(lng_val), float(lat_val), intensity])
return result
except ImportError:
pass
raise ValueError(
"Unsupported data type for heatmap. Use a list of [lng, lat] "
"or [lng, lat, intensity] arrays, a GeoJSON FeatureCollection, "
"or a pandas/geopandas DataFrame."
)
# -------------------------------------------------------------------------
# Choropleth
# -------------------------------------------------------------------------
def add_choropleth(
self,
data: Any,
value_column: str,
name: Optional[str] = None,
colors: Optional[List[str]] = None,
thresholds: Optional[List[float]] = None,
n_classes: int = 7,
fill_opacity: float = 0.7,
line_color: str = "#ffffff",
line_weight: int = 2,
line_opacity: float = 1.0,
popup_properties: Optional[Union[List[str], bool]] = None,
tooltip_property: Optional[str] = None,
fit_bounds: bool = True,
legend_title: Optional[str] = None,
legend_position: str = "bottomright",
) -> None:
"""Add an interactive choropleth layer.
Inspired by the `Leaflet Interactive Choropleth
<https://leafletjs.com/examples/choropleth/>`_ tutorial.
Args:
data: GeoJSON dict, GeoDataFrame, or path to vector file.
value_column: Property/column name containing numeric values.
name: Layer name.
colors: List of colors for the scale. Defaults to a
yellow-green-blue ramp.
thresholds: Breakpoints between colour classes. If *None*,
equal-interval breaks are computed automatically.
n_classes: Number of classes when auto-computing thresholds.
fill_opacity: Polygon fill opacity.
line_color: Polygon border color.
line_weight: Border weight in pixels.
line_opacity: Border opacity.
popup_properties: Properties to show in popups (list or True).
tooltip_property: Property for hover tooltip text.
fit_bounds: Whether to fit map to data bounds.
legend_title: Title for the legend (shows legend if provided).
legend_position: Legend position on the map.
"""
geojson = to_geojson(data)
layer_id = name or f"choropleth-{len(self._layers)}"
if colors is None:
colors = COLORS_YLGNBU
if thresholds is None:
values = []
for feat in geojson.get("features", []):
val = feat.get("properties", {}).get(value_column)
if val is not None:
try:
values.append(float(val))
except (TypeError, ValueError):
pass
# _compute_thresholds(v, n) returns n-1 thresholds → n bins.
# getColor needs len(colors) bins, so n = len(colors).
n = len(colors) if colors else n_classes
thresholds = _compute_thresholds(values, n)
kw: Dict[str, Any] = {
"data": geojson,
"name": layer_id,
"valueProperty": value_column,
"colors": colors,
"thresholds": thresholds,
"fillOpacity": fill_opacity,
"lineColor": line_color,
"lineWeight": line_weight,
"lineOpacity": line_opacity,
"fitBounds": fit_bounds,
}
if popup_properties is not None:
kw["popupProperties"] = popup_properties
if tooltip_property:
kw["tooltipProperty"] = tooltip_property
if legend_title:
kw["legendTitle"] = legend_title
kw["legendPosition"] = legend_position
self.call_js_method("addChoropleth", **kw)
self._layers = {
**self._layers,
layer_id: {"id": layer_id, "type": "choropleth"},
}
# -------------------------------------------------------------------------
# Popups
# -------------------------------------------------------------------------
def add_popup(
self,
lng: float,
lat: float,
content: str,
popup_id: Optional[str] = None,
max_width: int = 300,
close_button: bool = True,
) -> None:
"""Add a standalone popup at a location.
Args:
lng: Longitude.
lat: Latitude.
content: HTML content for the popup.
popup_id: Unique popup ID.
max_width: Maximum popup width in pixels.
close_button: Whether to show a close button.
"""
self.call_js_method(
"addPopup",
lng,
lat,
content=content,
id=popup_id,
maxWidth=max_width,
closeButton=close_button,
)
def remove_popup(self, popup_id: str) -> None:
"""Remove a popup.
Args:
popup_id: Popup ID to remove.
"""
self.call_js_method("removePopup", popup_id)
# -------------------------------------------------------------------------
# Legend
# -------------------------------------------------------------------------
def add_legend(
self,
items: List[Dict[str, str]],
title: Optional[str] = None,
name: str = "legend",
position: str = "bottomright",
) -> None:
"""Add a custom legend to the map.
Args:
items: List of dicts with "color" and "label" keys.
title: Legend title.
name: Legend identifier (for removal).
position: Position on the map.
"""
self.call_js_method(
"addLegend",
name=name,
title=title,
items=items,
position=position,
)
def remove_legend(self, name: str = "legend") -> None:
"""Remove a legend.
Args:
name: Legend identifier.
"""
self.call_js_method("removeLegend", name)
# -------------------------------------------------------------------------
# HTML Export
# -------------------------------------------------------------------------
def _generate_html_template(self) -> str:
"""Generate standalone HTML for the map."""
template_path = Path(__file__).parent / "templates" / "leaflet.html"
if template_path.exists():
template = template_path.read_text(encoding="utf-8")
else:
template = self._get_default_template()
state = {
"center": self.center,
"zoom": self.zoom,
"width": self.width,
"height": self.height,
"layers": self._layers,
"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">
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css" />
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script>
<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 = L.map('map').setView([state.center[1], state.center[0]], state.zoom);
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':
case 'addTileLayer':
L.tileLayer(args[0], {
attribution: kwargs.attribution || '',
maxZoom: kwargs.maxZoom || 22,
minZoom: kwargs.minZoom || 0,
opacity: kwargs.opacity || 1
}).addTo(map);
break;
case 'addGeoJSON': {
const style = kwargs.style || { color: '#3388ff', weight: 2, opacity: 0.8, fillOpacity: 0.5 };
const layer = L.geoJSON(kwargs.data, {
style: style,
pointToLayer: (f, ll) => L.circleMarker(ll, style)
}).addTo(map);
if (kwargs.fitBounds) map.fitBounds(layer.getBounds(), { padding: [50, 50] });
break;
}
case 'addControl': {
const pos = kwargs.position || 'topright';
if (args[0] === 'zoom' || args[0] === 'navigation') L.control.zoom({ position: pos }).addTo(map);
else if (args[0] === 'scale') L.control.scale({ position: pos, imperial: false }).addTo(map);
break;
}
case 'addMarker': {
const mk = L.marker([args[1], args[0]]).addTo(map);
if (kwargs.popup) mk.bindPopup(kwargs.popup);
break;
}
case 'flyTo':
map.flyTo([args[1], args[0]], kwargs.zoom || map.getZoom(), { duration: (kwargs.duration || 2000) / 1000 });
break;
case 'fitBounds': {
const b = args[0];
map.fitBounds([[b[1], b[0]], [b[3], b[2]]], { padding: [kwargs.padding || 50, kwargs.padding || 50] });
break;
}
default:
console.log('Unknown method:', method);
}
}
</script>
</body>
</html>"""
__init__(self, center=(0.0, 0.0), zoom=2.0, width='100%', height='600px', controls=None, **kwargs)
special
¶
Initialize a Leaflet map.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
center |
Tuple[float, float] |
Map center as (longitude, latitude). |
(0.0, 0.0) |
zoom |
float |
Initial zoom level. |
2.0 |
width |
str |
Map width as CSS string. |
'100%' |
height |
str |
Map height as CSS string. |
'600px' |
controls |
Optional[Dict[str, Any]] |
Dict of controls to add (e.g., {"zoom": True}). |
None |
**kwargs |
Additional widget arguments. |
{} |
Source code in anymap_ts/leaflet.py
def __init__(
self,
center: Tuple[float, float] = (0.0, 0.0),
zoom: float = 2.0,
width: str = "100%",
height: str = "600px",
controls: Optional[Dict[str, Any]] = None,
**kwargs,
):
"""Initialize a Leaflet map.
Args:
center: Map center as (longitude, latitude).
zoom: Initial zoom level.
width: Map width as CSS string.
height: Map height as CSS string.
controls: Dict of controls to add (e.g., {"zoom": True}).
**kwargs: Additional widget arguments.
"""
super().__init__(
center=list(center),
zoom=zoom,
width=width,
height=height,
style="",
**kwargs,
)
self._layer_dict = {"Background": []}
if controls is None:
controls = {
"scale": {"position": "bottom-left"},
"attribution": {"position": "bottom-right"},
"layers": {"position": "top-right"},
}
for control_name, config in controls.items():
if config:
self.add_control(
control_name, **(config if isinstance(config, dict) else {})
)
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") or a tile URL. |
'OpenStreetMap' |
attribution |
Optional[str] |
Custom attribution text. |
None |
**kwargs |
Additional options. |
{} |
Source code in anymap_ts/leaflet.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") or a tile URL.
attribution: Custom attribution text.
**kwargs: Additional options.
"""
try:
url, default_attribution = get_basemap_url(basemap)
except (ValueError, KeyError):
url = basemap
default_attribution = ""
self.call_js_method(
"addBasemap",
url,
attribution=attribution or default_attribution,
name=basemap,
**kwargs,
)
basemaps = self._layer_dict.get("Basemaps", [])
if basemap not in basemaps:
self._layer_dict = {
**self._layer_dict,
"Basemaps": basemaps + [basemap],
}
add_choropleth(self, data, value_column, name=None, colors=None, thresholds=None, n_classes=7, fill_opacity=0.7, line_color='#ffffff', line_weight=2, line_opacity=1.0, popup_properties=None, tooltip_property=None, fit_bounds=True, legend_title=None, legend_position='bottomright')
¶
Add an interactive choropleth layer.
Inspired by the Leaflet Interactive Choropleth
<https://leafletjs.com/examples/choropleth/>_ tutorial.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data |
Any |
GeoJSON dict, GeoDataFrame, or path to vector file. |
required |
value_column |
str |
Property/column name containing numeric values. |
required |
name |
Optional[str] |
Layer name. |
None |
colors |
Optional[List[str]] |
List of colors for the scale. Defaults to a yellow-green-blue ramp. |
None |
thresholds |
Optional[List[float]] |
Breakpoints between colour classes. If None, equal-interval breaks are computed automatically. |
None |
n_classes |
int |
Number of classes when auto-computing thresholds. |
7 |
fill_opacity |
float |
Polygon fill opacity. |
0.7 |
line_color |
str |
Polygon border color. |
'#ffffff' |
line_weight |
int |
Border weight in pixels. |
2 |
line_opacity |
float |
Border opacity. |
1.0 |
popup_properties |
Optional[Union[List[str], bool]] |
Properties to show in popups (list or True). |
None |
tooltip_property |
Optional[str] |
Property for hover tooltip text. |
None |
fit_bounds |
bool |
Whether to fit map to data bounds. |
True |
legend_title |
Optional[str] |
Title for the legend (shows legend if provided). |
None |
legend_position |
str |
Legend position on the map. |
'bottomright' |
Source code in anymap_ts/leaflet.py
def add_choropleth(
self,
data: Any,
value_column: str,
name: Optional[str] = None,
colors: Optional[List[str]] = None,
thresholds: Optional[List[float]] = None,
n_classes: int = 7,
fill_opacity: float = 0.7,
line_color: str = "#ffffff",
line_weight: int = 2,
line_opacity: float = 1.0,
popup_properties: Optional[Union[List[str], bool]] = None,
tooltip_property: Optional[str] = None,
fit_bounds: bool = True,
legend_title: Optional[str] = None,
legend_position: str = "bottomright",
) -> None:
"""Add an interactive choropleth layer.
Inspired by the `Leaflet Interactive Choropleth
<https://leafletjs.com/examples/choropleth/>`_ tutorial.
Args:
data: GeoJSON dict, GeoDataFrame, or path to vector file.
value_column: Property/column name containing numeric values.
name: Layer name.
colors: List of colors for the scale. Defaults to a
yellow-green-blue ramp.
thresholds: Breakpoints between colour classes. If *None*,
equal-interval breaks are computed automatically.
n_classes: Number of classes when auto-computing thresholds.
fill_opacity: Polygon fill opacity.
line_color: Polygon border color.
line_weight: Border weight in pixels.
line_opacity: Border opacity.
popup_properties: Properties to show in popups (list or True).
tooltip_property: Property for hover tooltip text.
fit_bounds: Whether to fit map to data bounds.
legend_title: Title for the legend (shows legend if provided).
legend_position: Legend position on the map.
"""
geojson = to_geojson(data)
layer_id = name or f"choropleth-{len(self._layers)}"
if colors is None:
colors = COLORS_YLGNBU
if thresholds is None:
values = []
for feat in geojson.get("features", []):
val = feat.get("properties", {}).get(value_column)
if val is not None:
try:
values.append(float(val))
except (TypeError, ValueError):
pass
# _compute_thresholds(v, n) returns n-1 thresholds → n bins.
# getColor needs len(colors) bins, so n = len(colors).
n = len(colors) if colors else n_classes
thresholds = _compute_thresholds(values, n)
kw: Dict[str, Any] = {
"data": geojson,
"name": layer_id,
"valueProperty": value_column,
"colors": colors,
"thresholds": thresholds,
"fillOpacity": fill_opacity,
"lineColor": line_color,
"lineWeight": line_weight,
"lineOpacity": line_opacity,
"fitBounds": fit_bounds,
}
if popup_properties is not None:
kw["popupProperties"] = popup_properties
if tooltip_property:
kw["tooltipProperty"] = tooltip_property
if legend_title:
kw["legendTitle"] = legend_title
kw["legendPosition"] = legend_position
self.call_js_method("addChoropleth", **kw)
self._layers = {
**self._layers,
layer_id: {"id": layer_id, "type": "choropleth"},
}
add_circle(self, lng, lat, radius=1000, name=None, color='#3388ff', fill_color=None, fill_opacity=0.2, weight=2, opacity=1.0, popup=None, tooltip=None)
¶
Add a geographic circle (radius in meters) to the map.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
lng |
float |
Center longitude. |
required |
lat |
float |
Center latitude. |
required |
radius |
float |
Radius in meters. |
1000 |
name |
Optional[str] |
Layer name. |
None |
color |
str |
Stroke color. |
'#3388ff' |
fill_color |
Optional[str] |
Fill color (defaults to stroke color). |
None |
fill_opacity |
float |
Fill opacity. |
0.2 |
weight |
int |
Stroke weight in pixels. |
2 |
opacity |
float |
Stroke opacity. |
1.0 |
popup |
Optional[str] |
Popup HTML content. |
None |
tooltip |
Optional[str] |
Tooltip text. |
None |
Source code in anymap_ts/leaflet.py
def add_circle(
self,
lng: float,
lat: float,
radius: float = 1000,
name: Optional[str] = None,
color: str = "#3388ff",
fill_color: Optional[str] = None,
fill_opacity: float = 0.2,
weight: int = 2,
opacity: float = 1.0,
popup: Optional[str] = None,
tooltip: Optional[str] = None,
) -> None:
"""Add a geographic circle (radius in meters) to the map.
Args:
lng: Center longitude.
lat: Center latitude.
radius: Radius in meters.
name: Layer name.
color: Stroke color.
fill_color: Fill color (defaults to stroke color).
fill_opacity: Fill opacity.
weight: Stroke weight in pixels.
opacity: Stroke opacity.
popup: Popup HTML content.
tooltip: Tooltip text.
"""
layer_id = name or f"circle-{len(self._layers)}"
self.call_js_method(
"addCircle",
lng,
lat,
name=layer_id,
radius=radius,
color=color,
fillColor=fill_color or color,
fillOpacity=fill_opacity,
weight=weight,
opacity=opacity,
popup=popup,
tooltip=tooltip,
)
self._layers = {
**self._layers,
layer_id: {"id": layer_id, "type": "circle"},
}
add_circle_marker(self, lng, lat, radius=10, name=None, color='#3388ff', fill_color=None, fill_opacity=0.5, weight=2, opacity=1.0, popup=None, tooltip=None)
¶
Add a circle marker (fixed pixel radius) to the map.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
lng |
float |
Longitude. |
required |
lat |
float |
Latitude. |
required |
radius |
int |
Radius in pixels. |
10 |
name |
Optional[str] |
Layer name. |
None |
color |
str |
Stroke color. |
'#3388ff' |
fill_color |
Optional[str] |
Fill color (defaults to stroke color). |
None |
fill_opacity |
float |
Fill opacity. |
0.5 |
weight |
int |
Stroke weight in pixels. |
2 |
opacity |
float |
Stroke opacity. |
1.0 |
popup |
Optional[str] |
Popup HTML content. |
None |
tooltip |
Optional[str] |
Tooltip text. |
None |
Source code in anymap_ts/leaflet.py
def add_circle_marker(
self,
lng: float,
lat: float,
radius: int = 10,
name: Optional[str] = None,
color: str = "#3388ff",
fill_color: Optional[str] = None,
fill_opacity: float = 0.5,
weight: int = 2,
opacity: float = 1.0,
popup: Optional[str] = None,
tooltip: Optional[str] = None,
) -> None:
"""Add a circle marker (fixed pixel radius) to the map.
Args:
lng: Longitude.
lat: Latitude.
radius: Radius in pixels.
name: Layer name.
color: Stroke color.
fill_color: Fill color (defaults to stroke color).
fill_opacity: Fill opacity.
weight: Stroke weight in pixels.
opacity: Stroke opacity.
popup: Popup HTML content.
tooltip: Tooltip text.
"""
layer_id = name or f"circle-marker-{len(self._layers)}"
self.call_js_method(
"addCircleMarker",
lng,
lat,
name=layer_id,
radius=radius,
color=color,
fillColor=fill_color or color,
fillOpacity=fill_opacity,
weight=weight,
opacity=opacity,
popup=popup,
tooltip=tooltip,
)
self._layers = {
**self._layers,
layer_id: {"id": layer_id, "type": "circle_marker"},
}
add_control(self, control_type, position='topright', **kwargs)
¶
Add a map control.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
control_type |
str |
Type of control ('zoom', 'scale', 'attribution', 'layers'). |
required |
position |
str |
Control position ('topleft', 'topright', 'bottomleft', 'bottomright'). |
'topright' |
**kwargs |
Control-specific options. |
{} |
Source code in anymap_ts/leaflet.py
def add_control(
self,
control_type: str,
position: str = "topright",
**kwargs,
) -> None:
"""Add a map control.
Args:
control_type: Type of control ('zoom', 'scale', 'attribution',
'layers').
position: Control position ('topleft', 'topright', 'bottomleft',
'bottomright').
**kwargs: Control-specific options.
"""
position_map = {
"top-left": "topleft",
"top-right": "topright",
"bottom-left": "bottomleft",
"bottom-right": "bottomright",
}
pos = position_map.get(position, position)
self.call_js_method("addControl", control_type, position=pos, **kwargs)
self._controls = {
**self._controls,
control_type: {"type": control_type, "position": pos, **kwargs},
}
add_geojson(self, data, style=None, name=None, fit_bounds=True, popup_properties=None, tooltip_property=None, **kwargs)
¶
Add GeoJSON data to the map.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data |
Union[str, Dict] |
GeoJSON dict or URL to GeoJSON file. |
required |
style |
Optional[Dict] |
Leaflet style properties. |
None |
name |
Optional[str] |
Layer name. |
None |
fit_bounds |
bool |
Whether to fit map to data bounds. |
True |
popup_properties |
Optional[Union[List[str], bool]] |
List of property names to show in popups, or True to show all properties. |
None |
tooltip_property |
Optional[str] |
Property name to use as tooltip text. |
None |
**kwargs |
Additional layer options. |
{} |
Source code in anymap_ts/leaflet.py
def add_geojson(
self,
data: Union[str, Dict],
style: Optional[Dict] = None,
name: Optional[str] = None,
fit_bounds: bool = True,
popup_properties: Optional[Union[List[str], bool]] = None,
tooltip_property: Optional[str] = None,
**kwargs,
) -> None:
"""Add GeoJSON data to the map.
Args:
data: GeoJSON dict or URL to GeoJSON file.
style: Leaflet style properties.
name: Layer name.
fit_bounds: Whether to fit map to data bounds.
popup_properties: List of property names to show in popups,
or True to show all properties.
tooltip_property: Property name to use as tooltip text.
**kwargs: Additional layer options.
"""
self.add_vector(
data,
style=style,
name=name,
fit_bounds=fit_bounds,
popup_properties=popup_properties,
tooltip_property=tooltip_property,
**kwargs,
)
add_heatmap(self, data, name=None, radius=25, blur=15, max_zoom=18, max_val=1.0, min_opacity=0.05, gradient=None, value_column=None, lat_column='lat', lng_column='lng')
¶
Add a heatmap layer.
Accepts a list of [lng, lat] or [lng, lat, intensity]
arrays, a GeoJSON FeatureCollection of Points, or a pandas/
geopandas DataFrame.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data |
Any |
Heat data – list of [lng, lat, intensity?] arrays, GeoJSON FeatureCollection, or DataFrame. |
required |
name |
Optional[str] |
Layer name. |
None |
radius |
int |
Heatmap point radius in pixels. |
25 |
blur |
int |
Blur radius in pixels. |
15 |
max_zoom |
int |
Zoom level at which points reach full intensity. |
18 |
max_val |
float |
Maximum point intensity. |
1.0 |
min_opacity |
float |
Minimum opacity of heat points. |
0.05 |
gradient |
Optional[Dict[str, str]] |
Custom color gradient as {stop: color} dict,
e.g. |
None |
value_column |
Optional[str] |
Column name for intensity (DataFrame input). |
None |
lat_column |
str |
Column name for latitude (DataFrame input). |
'lat' |
lng_column |
str |
Column name for longitude (DataFrame input). |
'lng' |
Source code in anymap_ts/leaflet.py
def add_heatmap(
self,
data: Any,
name: Optional[str] = None,
radius: int = 25,
blur: int = 15,
max_zoom: int = 18,
max_val: float = 1.0,
min_opacity: float = 0.05,
gradient: Optional[Dict[str, str]] = None,
value_column: Optional[str] = None,
lat_column: str = "lat",
lng_column: str = "lng",
) -> None:
"""Add a heatmap layer.
Accepts a list of ``[lng, lat]`` or ``[lng, lat, intensity]``
arrays, a GeoJSON FeatureCollection of Points, or a pandas/
geopandas DataFrame.
Args:
data: Heat data – list of [lng, lat, intensity?] arrays,
GeoJSON FeatureCollection, or DataFrame.
name: Layer name.
radius: Heatmap point radius in pixels.
blur: Blur radius in pixels.
max_zoom: Zoom level at which points reach full intensity.
max_val: Maximum point intensity.
min_opacity: Minimum opacity of heat points.
gradient: Custom color gradient as {stop: color} dict,
e.g. ``{0.4: "blue", 0.65: "lime", 1: "red"}``.
value_column: Column name for intensity (DataFrame input).
lat_column: Column name for latitude (DataFrame input).
lng_column: Column name for longitude (DataFrame input).
"""
layer_id = name or f"heatmap-{len(self._layers)}"
heat_data = self._normalize_heatmap_data(
data, value_column, lat_column, lng_column
)
kw: Dict[str, Any] = {
"data": heat_data,
"name": layer_id,
"radius": radius,
"blur": blur,
"maxZoom": max_zoom,
"max": max_val,
"minOpacity": min_opacity,
}
if gradient:
kw["gradient"] = {str(k): v for k, v in gradient.items()}
self.call_js_method("addHeatmap", **kw)
self._layers = {
**self._layers,
layer_id: {"id": layer_id, "type": "heatmap"},
}
add_image_overlay(self, url, bounds, name=None, opacity=1.0, interactive=False)
¶
Add an image overlay to the map.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
url |
str |
URL of the image to overlay. |
required |
bounds |
Tuple[float, float, float, float] |
Bounding box as (west, south, east, north). |
required |
name |
Optional[str] |
Layer name. |
None |
opacity |
float |
Image opacity (0 to 1). |
1.0 |
interactive |
bool |
Whether the overlay responds to mouse events. |
False |
Source code in anymap_ts/leaflet.py
def add_image_overlay(
self,
url: str,
bounds: Tuple[float, float, float, float],
name: Optional[str] = None,
opacity: float = 1.0,
interactive: bool = False,
) -> None:
"""Add an image overlay to the map.
Args:
url: URL of the image to overlay.
bounds: Bounding box as (west, south, east, north).
name: Layer name.
opacity: Image opacity (0 to 1).
interactive: Whether the overlay responds to mouse events.
"""
layer_id = name or f"image-{len(self._layers)}"
self.call_js_method(
"addImageOverlay",
url,
bounds=list(bounds),
name=layer_id,
opacity=opacity,
interactive=interactive,
)
self._layers = {
**self._layers,
layer_id: {"id": layer_id, "type": "image_overlay"},
}
add_layer_control(self, position='topright', collapsed=True)
¶
Add a layer control for toggling layer visibility.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
position |
str |
Control position. |
'topright' |
collapsed |
bool |
Whether control starts collapsed. |
True |
Source code in anymap_ts/leaflet.py
def add_layer_control(
self,
position: str = "topright",
collapsed: bool = True,
) -> None:
"""Add a layer control for toggling layer visibility.
Args:
position: Control position.
collapsed: Whether control starts collapsed.
"""
self.add_control("layers", position=position, collapsed=collapsed)
add_legend(self, items, title=None, name='legend', position='bottomright')
¶
Add a custom legend to the map.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
items |
List[Dict[str, str]] |
List of dicts with "color" and "label" keys. |
required |
title |
Optional[str] |
Legend title. |
None |
name |
str |
Legend identifier (for removal). |
'legend' |
position |
str |
Position on the map. |
'bottomright' |
Source code in anymap_ts/leaflet.py
def add_legend(
self,
items: List[Dict[str, str]],
title: Optional[str] = None,
name: str = "legend",
position: str = "bottomright",
) -> None:
"""Add a custom legend to the map.
Args:
items: List of dicts with "color" and "label" keys.
title: Legend title.
name: Legend identifier (for removal).
position: Position on the map.
"""
self.call_js_method(
"addLegend",
name=name,
title=title,
items=items,
position=position,
)
add_marker(self, lng, lat, popup=None, tooltip=None, marker_id=None, draggable=False, opacity=1.0, icon_url=None, icon_size=None, icon_anchor=None)
¶
Add a marker to the map.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
lng |
float |
Longitude. |
required |
lat |
float |
Latitude. |
required |
popup |
Optional[str] |
HTML content for popup. |
None |
tooltip |
Optional[str] |
Tooltip text. |
None |
marker_id |
Optional[str] |
Unique marker ID. |
None |
draggable |
bool |
Whether marker can be dragged. |
False |
opacity |
float |
Marker opacity (0 to 1). |
1.0 |
icon_url |
Optional[str] |
URL to custom icon image. |
None |
icon_size |
Optional[Tuple[int, int]] |
Icon size as (width, height) in pixels. |
None |
icon_anchor |
Optional[Tuple[int, int]] |
Icon anchor point as (x, y) in pixels. |
None |
Source code in anymap_ts/leaflet.py
def add_marker(
self,
lng: float,
lat: float,
popup: Optional[str] = None,
tooltip: Optional[str] = None,
marker_id: Optional[str] = None,
draggable: bool = False,
opacity: float = 1.0,
icon_url: Optional[str] = None,
icon_size: Optional[Tuple[int, int]] = None,
icon_anchor: Optional[Tuple[int, int]] = None,
) -> None:
"""Add a marker to the map.
Args:
lng: Longitude.
lat: Latitude.
popup: HTML content for popup.
tooltip: Tooltip text.
marker_id: Unique marker ID.
draggable: Whether marker can be dragged.
opacity: Marker opacity (0 to 1).
icon_url: URL to custom icon image.
icon_size: Icon size as (width, height) in pixels.
icon_anchor: Icon anchor point as (x, y) in pixels.
"""
kw: Dict[str, Any] = {"popup": popup, "id": marker_id}
if tooltip:
kw["tooltip"] = tooltip
if draggable:
kw["draggable"] = True
if opacity != 1.0:
kw["opacity"] = opacity
if icon_url:
kw["iconUrl"] = icon_url
if icon_size:
kw["iconSize"] = list(icon_size)
if icon_anchor:
kw["iconAnchor"] = list(icon_anchor)
self.call_js_method("addMarker", lng, lat, **kw)
add_markers(self, data, name=None)
¶
Add multiple markers as a layer group.
Each item in data should be a dict with at least lng and
lat keys. Optional keys: popup, tooltip, iconUrl,
iconSize.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data |
List[Dict[str, Any]] |
List of marker dicts. |
required |
name |
Optional[str] |
Layer group name. |
None |
Source code in anymap_ts/leaflet.py
def add_markers(
self,
data: List[Dict[str, Any]],
name: Optional[str] = None,
) -> None:
"""Add multiple markers as a layer group.
Each item in *data* should be a dict with at least ``lng`` and
``lat`` keys. Optional keys: ``popup``, ``tooltip``, ``iconUrl``,
``iconSize``.
Args:
data: List of marker dicts.
name: Layer group name.
"""
layer_id = name or f"markers-{len(self._layers)}"
self.call_js_method("addMarkers", data=data, name=layer_id)
self._layers = {
**self._layers,
layer_id: {"id": layer_id, "type": "markers"},
}
add_polygon(self, coordinates, name=None, color='#3388ff', fill_color=None, fill_opacity=0.5, weight=2, opacity=1.0, popup=None, tooltip=None, fit_bounds=False)
¶
Add a polygon to the map.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
coordinates |
Sequence[Sequence[float]] |
List of [lng, lat] vertex pairs. |
required |
name |
Optional[str] |
Layer name. |
None |
color |
str |
Stroke color. |
'#3388ff' |
fill_color |
Optional[str] |
Fill color (defaults to stroke color). |
None |
fill_opacity |
float |
Fill opacity. |
0.5 |
weight |
int |
Stroke weight in pixels. |
2 |
opacity |
float |
Stroke opacity. |
1.0 |
popup |
Optional[str] |
Popup HTML content. |
None |
tooltip |
Optional[str] |
Tooltip text. |
None |
fit_bounds |
bool |
Whether to fit map to polygon bounds. |
False |
Source code in anymap_ts/leaflet.py
def add_polygon(
self,
coordinates: Sequence[Sequence[float]],
name: Optional[str] = None,
color: str = "#3388ff",
fill_color: Optional[str] = None,
fill_opacity: float = 0.5,
weight: int = 2,
opacity: float = 1.0,
popup: Optional[str] = None,
tooltip: Optional[str] = None,
fit_bounds: bool = False,
) -> None:
"""Add a polygon to the map.
Args:
coordinates: List of [lng, lat] vertex pairs.
name: Layer name.
color: Stroke color.
fill_color: Fill color (defaults to stroke color).
fill_opacity: Fill opacity.
weight: Stroke weight in pixels.
opacity: Stroke opacity.
popup: Popup HTML content.
tooltip: Tooltip text.
fit_bounds: Whether to fit map to polygon bounds.
"""
layer_id = name or f"polygon-{len(self._layers)}"
self.call_js_method(
"addPolygon",
coordinates=[list(c) for c in coordinates],
name=layer_id,
color=color,
fillColor=fill_color or color,
fillOpacity=fill_opacity,
weight=weight,
opacity=opacity,
popup=popup,
tooltip=tooltip,
fitBounds=fit_bounds,
)
self._layers = {
**self._layers,
layer_id: {"id": layer_id, "type": "polygon"},
}
add_polyline(self, coordinates, name=None, color='#3388ff', weight=3, opacity=1.0, dash_array=None, popup=None, tooltip=None, fit_bounds=False)
¶
Add a polyline to the map.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
coordinates |
Sequence[Sequence[float]] |
List of [lng, lat] pairs. |
required |
name |
Optional[str] |
Layer name. |
None |
color |
str |
Line color. |
'#3388ff' |
weight |
int |
Line weight in pixels. |
3 |
opacity |
float |
Line opacity. |
1.0 |
dash_array |
Optional[str] |
Dash pattern (e.g., "5 10"). |
None |
popup |
Optional[str] |
Popup HTML content. |
None |
tooltip |
Optional[str] |
Tooltip text. |
None |
fit_bounds |
bool |
Whether to fit map to polyline bounds. |
False |
Source code in anymap_ts/leaflet.py
def add_polyline(
self,
coordinates: Sequence[Sequence[float]],
name: Optional[str] = None,
color: str = "#3388ff",
weight: int = 3,
opacity: float = 1.0,
dash_array: Optional[str] = None,
popup: Optional[str] = None,
tooltip: Optional[str] = None,
fit_bounds: bool = False,
) -> None:
"""Add a polyline to the map.
Args:
coordinates: List of [lng, lat] pairs.
name: Layer name.
color: Line color.
weight: Line weight in pixels.
opacity: Line opacity.
dash_array: Dash pattern (e.g., "5 10").
popup: Popup HTML content.
tooltip: Tooltip text.
fit_bounds: Whether to fit map to polyline bounds.
"""
layer_id = name or f"polyline-{len(self._layers)}"
kw: Dict[str, Any] = {
"coordinates": [list(c) for c in coordinates],
"name": layer_id,
"color": color,
"weight": weight,
"opacity": opacity,
"fitBounds": fit_bounds,
}
if dash_array:
kw["dashArray"] = dash_array
if popup:
kw["popup"] = popup
if tooltip:
kw["tooltip"] = tooltip
self.call_js_method("addPolyline", **kw)
self._layers = {
**self._layers,
layer_id: {"id": layer_id, "type": "polyline"},
}
add_popup(self, lng, lat, content, popup_id=None, max_width=300, close_button=True)
¶
Add a standalone popup at a location.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
lng |
float |
Longitude. |
required |
lat |
float |
Latitude. |
required |
content |
str |
HTML content for the popup. |
required |
popup_id |
Optional[str] |
Unique popup ID. |
None |
max_width |
int |
Maximum popup width in pixels. |
300 |
close_button |
bool |
Whether to show a close button. |
True |
Source code in anymap_ts/leaflet.py
def add_popup(
self,
lng: float,
lat: float,
content: str,
popup_id: Optional[str] = None,
max_width: int = 300,
close_button: bool = True,
) -> None:
"""Add a standalone popup at a location.
Args:
lng: Longitude.
lat: Latitude.
content: HTML content for the popup.
popup_id: Unique popup ID.
max_width: Maximum popup width in pixels.
close_button: Whether to show a close button.
"""
self.call_js_method(
"addPopup",
lng,
lat,
content=content,
id=popup_id,
maxWidth=max_width,
closeButton=close_button,
)
add_rectangle(self, bounds, name=None, color='#3388ff', fill_color=None, fill_opacity=0.2, weight=2, opacity=1.0, popup=None, tooltip=None)
¶
Add a rectangle to the map.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
bounds |
Tuple[float, float, float, float] |
Bounding box as (west, south, east, north). |
required |
name |
Optional[str] |
Layer name. |
None |
color |
str |
Stroke color. |
'#3388ff' |
fill_color |
Optional[str] |
Fill color (defaults to stroke color). |
None |
fill_opacity |
float |
Fill opacity. |
0.2 |
weight |
int |
Stroke weight in pixels. |
2 |
opacity |
float |
Stroke opacity. |
1.0 |
popup |
Optional[str] |
Popup HTML content. |
None |
tooltip |
Optional[str] |
Tooltip text. |
None |
Source code in anymap_ts/leaflet.py
def add_rectangle(
self,
bounds: Tuple[float, float, float, float],
name: Optional[str] = None,
color: str = "#3388ff",
fill_color: Optional[str] = None,
fill_opacity: float = 0.2,
weight: int = 2,
opacity: float = 1.0,
popup: Optional[str] = None,
tooltip: Optional[str] = None,
) -> None:
"""Add a rectangle to the map.
Args:
bounds: Bounding box as (west, south, east, north).
name: Layer name.
color: Stroke color.
fill_color: Fill color (defaults to stroke color).
fill_opacity: Fill opacity.
weight: Stroke weight in pixels.
opacity: Stroke opacity.
popup: Popup HTML content.
tooltip: Tooltip text.
"""
layer_id = name or f"rectangle-{len(self._layers)}"
self.call_js_method(
"addRectangle",
bounds=list(bounds),
name=layer_id,
color=color,
fillColor=fill_color or color,
fillOpacity=fill_opacity,
weight=weight,
opacity=opacity,
popup=popup,
tooltip=tooltip,
)
self._layers = {
**self._layers,
layer_id: {"id": layer_id, "type": "rectangle"},
}
add_tile_layer(self, url, name=None, attribution='', min_zoom=0, max_zoom=22, opacity=1.0, **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 |
opacity |
float |
Layer opacity (0 to 1). |
1.0 |
**kwargs |
Additional options. |
{} |
Source code in anymap_ts/leaflet.py
def add_tile_layer(
self,
url: str,
name: Optional[str] = None,
attribution: str = "",
min_zoom: int = 0,
max_zoom: int = 22,
opacity: float = 1.0,
**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.
opacity: Layer opacity (0 to 1).
**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,
opacity=opacity,
**kwargs,
)
self._layers = {
**self._layers,
layer_id: {"id": layer_id, "type": "tile"},
}
add_vector(self, data, style=None, name=None, fit_bounds=True, popup_properties=None, tooltip_property=None, **kwargs)
¶
Add vector data to the map.
Supports GeoJSON dicts, GeoDataFrames, or file paths.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data |
Any |
GeoJSON dict, GeoDataFrame, or path to vector file. |
required |
style |
Optional[Dict] |
Leaflet style properties. |
None |
name |
Optional[str] |
Layer name. |
None |
fit_bounds |
bool |
Whether to fit map to data bounds. |
True |
popup_properties |
Optional[Union[List[str], bool]] |
List of property names to show in popups, or True to show all properties. |
None |
tooltip_property |
Optional[str] |
Property name to use as tooltip text. |
None |
**kwargs |
Additional layer options. |
{} |
Source code in anymap_ts/leaflet.py
def add_vector(
self,
data: Any,
style: Optional[Dict] = None,
name: Optional[str] = None,
fit_bounds: bool = True,
popup_properties: Optional[Union[List[str], bool]] = None,
tooltip_property: Optional[str] = None,
**kwargs,
) -> None:
"""Add vector data to the map.
Supports GeoJSON dicts, GeoDataFrames, or file paths.
Args:
data: GeoJSON dict, GeoDataFrame, or path to vector file.
style: Leaflet style properties.
name: Layer name.
fit_bounds: Whether to fit map to data bounds.
popup_properties: List of property names to show in popups,
or True to show all properties.
tooltip_property: Property name to use as tooltip text.
**kwargs: Additional layer options.
"""
geojson = to_geojson(data)
if geojson.get("type") == "url":
self.add_geojson(
geojson["url"],
style=style,
name=name,
fit_bounds=fit_bounds,
popup_properties=popup_properties,
tooltip_property=tooltip_property,
**kwargs,
)
return
layer_id = name or f"vector-{len(self._layers)}"
if style is None:
layer_type = _infer_leaflet_type(geojson)
style = _get_default_style(layer_type)
bounds = get_bounds(data) if fit_bounds else None
js_kwargs: Dict[str, Any] = {
"data": geojson,
"name": layer_id,
"style": style,
"fitBounds": fit_bounds,
"bounds": bounds,
}
if popup_properties is not None:
js_kwargs["popupProperties"] = popup_properties
if tooltip_property is not None:
js_kwargs["tooltipProperty"] = tooltip_property
self.call_js_method("addGeoJSON", **js_kwargs, **kwargs)
self._layers = {
**self._layers,
layer_id: {"id": layer_id, "type": "geojson", "style": style},
}
add_video_overlay(self, url, bounds, name=None, opacity=1.0, autoplay=True, loop=True, muted=True)
¶
Add a video overlay to the map.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
url |
Union[str, List[str]] |
Video URL or list of URLs (for multiple formats). |
required |
bounds |
Tuple[float, float, float, float] |
Bounding box as (west, south, east, north). |
required |
name |
Optional[str] |
Layer name. |
None |
opacity |
float |
Video opacity (0 to 1). |
1.0 |
autoplay |
bool |
Whether to autoplay the video. |
True |
loop |
bool |
Whether to loop the video. |
True |
muted |
bool |
Whether to mute the video. |
True |
Source code in anymap_ts/leaflet.py
def add_video_overlay(
self,
url: Union[str, List[str]],
bounds: Tuple[float, float, float, float],
name: Optional[str] = None,
opacity: float = 1.0,
autoplay: bool = True,
loop: bool = True,
muted: bool = True,
) -> None:
"""Add a video overlay to the map.
Args:
url: Video URL or list of URLs (for multiple formats).
bounds: Bounding box as (west, south, east, north).
name: Layer name.
opacity: Video opacity (0 to 1).
autoplay: Whether to autoplay the video.
loop: Whether to loop the video.
muted: Whether to mute the video.
"""
layer_id = name or f"video-{len(self._layers)}"
self.call_js_method(
"addVideoOverlay",
url=url if isinstance(url, list) else [url],
bounds=list(bounds),
name=layer_id,
opacity=opacity,
autoplay=autoplay,
loop=loop,
muted=muted,
)
self._layers = {
**self._layers,
layer_id: {"id": layer_id, "type": "video_overlay"},
}
add_wms_layer(self, url, layers, name=None, format='image/png', transparent=True, attribution='', opacity=1.0, crs=None, styles='', version='1.1.1', **kwargs)
¶
Add a WMS (Web Map Service) tile layer.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
url |
str |
WMS service base URL. |
required |
layers |
str |
Comma-separated WMS layer names. |
required |
name |
Optional[str] |
Layer name for the control. |
None |
format |
str |
Image format (e.g., "image/png", "image/jpeg"). |
'image/png' |
transparent |
bool |
Request transparent background. |
True |
attribution |
str |
Attribution text. |
'' |
opacity |
float |
Layer opacity (0 to 1). |
1.0 |
crs |
Optional[str] |
Coordinate reference system (e.g., "EPSG:4326"). |
None |
styles |
str |
WMS styles parameter. |
'' |
version |
str |
WMS version string. |
'1.1.1' |
**kwargs |
Additional WMS parameters. |
{} |
Source code in anymap_ts/leaflet.py
def add_wms_layer(
self,
url: str,
layers: str,
name: Optional[str] = None,
format: str = "image/png",
transparent: bool = True,
attribution: str = "",
opacity: float = 1.0,
crs: Optional[str] = None,
styles: str = "",
version: str = "1.1.1",
**kwargs,
) -> None:
"""Add a WMS (Web Map Service) tile layer.
Args:
url: WMS service base URL.
layers: Comma-separated WMS layer names.
name: Layer name for the control.
format: Image format (e.g., "image/png", "image/jpeg").
transparent: Request transparent background.
attribution: Attribution text.
opacity: Layer opacity (0 to 1).
crs: Coordinate reference system (e.g., "EPSG:4326").
styles: WMS styles parameter.
version: WMS version string.
**kwargs: Additional WMS parameters.
"""
layer_id = name or f"wms-{len(self._layers)}"
self.call_js_method(
"addWMSLayer",
url,
name=layer_id,
layers=layers,
format=format,
transparent=transparent,
attribution=attribution,
opacity=opacity,
crs=crs,
styles=styles,
version=version,
**kwargs,
)
self._layers = {
**self._layers,
layer_id: {"id": layer_id, "type": "wms"},
}
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/leaflet.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_heatmap(self, name)
¶
Remove a heatmap layer.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
name |
str |
Heatmap layer name. |
required |
Source code in anymap_ts/leaflet.py
def remove_heatmap(self, name: str) -> None:
"""Remove a heatmap layer.
Args:
name: Heatmap layer name.
"""
self.remove_layer(name)
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/leaflet.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_legend(self, name='legend')
¶
Remove a legend.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
name |
str |
Legend identifier. |
'legend' |
Source code in anymap_ts/leaflet.py
def remove_legend(self, name: str = "legend") -> None:
"""Remove a legend.
Args:
name: Legend identifier.
"""
self.call_js_method("removeLegend", name)
remove_marker(self, marker_id)
¶
Remove a marker from the map.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
marker_id |
str |
Marker ID to remove. |
required |
Source code in anymap_ts/leaflet.py
def remove_marker(self, marker_id: str) -> None:
"""Remove a marker from the map.
Args:
marker_id: Marker ID to remove.
"""
self.call_js_method("removeMarker", marker_id)
remove_popup(self, popup_id)
¶
Remove a popup.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
popup_id |
str |
Popup ID to remove. |
required |
Source code in anymap_ts/leaflet.py
def remove_popup(self, popup_id: str) -> None:
"""Remove a popup.
Args:
popup_id: Popup ID to remove.
"""
self.call_js_method("removePopup", popup_id)
remove_wms_layer(self, layer_id)
¶
Remove a WMS layer.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
layer_id |
str |
Layer identifier to remove. |
required |
Source code in anymap_ts/leaflet.py
def remove_wms_layer(self, layer_id: str) -> None:
"""Remove a WMS layer.
Args:
layer_id: Layer identifier to remove.
"""
self.remove_layer(layer_id)
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/leaflet.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/leaflet.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)