Skip to content

openlayers module

OpenLayers map widget implementation.

OpenLayersMap (MapWidget)

Interactive map widget using OpenLayers.

OpenLayers excels at WMS/WMTS support, projection handling, vector tiles, heatmaps, clustering, and advanced GIS operations.

Examples:

>>> from anymap_ts import OpenLayersMap
>>> m = OpenLayersMap(center=[-122.4, 37.8], zoom=10)
>>> m.add_basemap("OpenStreetMap")
>>> m
Source code in anymap_ts/openlayers.py
class OpenLayersMap(MapWidget):
    """Interactive map widget using OpenLayers.

    OpenLayers excels at WMS/WMTS support, projection handling,
    vector tiles, heatmaps, clustering, and advanced GIS operations.

    Example:
        >>> from anymap_ts import OpenLayersMap
        >>> m = OpenLayersMap(center=[-122.4, 37.8], zoom=10)
        >>> m.add_basemap("OpenStreetMap")
        >>> m
    """

    _esm = STATIC_DIR / "openlayers.js"
    _css = STATIC_DIR / "openlayers.css"

    # OpenLayers-specific traits
    projection = traitlets.Unicode("EPSG:3857").tag(sync=True)
    rotation = traitlets.Float(0.0).tag(sync=True)

    # Layer tracking
    _layer_dict = traitlets.Dict({}).tag(sync=True)

    def __init__(
        self,
        center: Tuple[float, float] = (0.0, 0.0),
        zoom: float = 2.0,
        width: str = "100%",
        height: str = "600px",
        projection: str = "EPSG:3857",
        rotation: float = 0.0,
        controls: Optional[Dict[str, Any]] = None,
        **kwargs,
    ):
        """Initialize an OpenLayers map.

        Args:
            center: Map center as (longitude, latitude).
            zoom: Initial zoom level.
            width: Map width as CSS string.
            height: Map height as CSS string.
            projection: Map projection (default EPSG:3857).
            rotation: Map rotation in radians.
            controls: Dict of controls to add. Pass False to disable defaults.
            **kwargs: Additional widget arguments.
        """
        super().__init__(
            center=list(center),
            zoom=zoom,
            width=width,
            height=height,
            projection=projection,
            rotation=rotation,
            **kwargs,
        )

        self._layer_dict = {"Background": []}

        if controls is None:
            controls = {
                "zoom": True,
                "attribution": True,
                "scale": {"units": "metric"},
            }

        if controls is not False:
            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 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],
            }

    # -------------------------------------------------------------------------
    # Tile Layer 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.
            **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"},
        }

    # -------------------------------------------------------------------------
    # Vector Data Methods
    # -------------------------------------------------------------------------

    def add_vector(
        self,
        data: Any,
        name: Optional[str] = None,
        style: Optional[Dict] = None,
        fit_bounds: bool = True,
        popup: Optional[str] = None,
        popup_properties: Optional[List[str]] = None,
        **kwargs,
    ) -> None:
        """Add vector data to the map.

        Args:
            data: GeoJSON dict, GeoDataFrame, URL, or file path.
            name: Layer name.
            style: Style configuration dict with keys like fillColor, strokeColor,
                strokeWidth, radius, lineDash, text, textColor, font.
            fit_bounds: Whether to fit map to data bounds.
            popup: HTML template for popups, with {property} placeholders.
            popup_properties: List of property names to show in popup table.
            **kwargs: Additional layer options.
        """
        geojson = to_geojson(data)

        if geojson.get("type") == "url":
            self.add_geojson_from_url(
                geojson["url"],
                name=name,
                style=style,
                fit_bounds=fit_bounds,
                **kwargs,
            )
            return

        layer_id = name or f"vector-{len(self._layers)}"

        if style is None:
            style = self._get_default_style(geojson)

        self.call_js_method(
            "addGeoJSON",
            data=geojson,
            name=layer_id,
            style=style,
            fitBounds=fit_bounds,
            popup=popup,
            popupProperties=popup_properties,
            **kwargs,
        )

        self._layers = {
            **self._layers,
            layer_id: {"id": layer_id, "type": "vector"},
        }

    def add_geojson(
        self,
        data: Union[str, Dict],
        name: Optional[str] = None,
        style: Optional[Dict] = None,
        fit_bounds: bool = True,
        popup: Optional[str] = None,
        popup_properties: Optional[List[str]] = None,
        **kwargs,
    ) -> None:
        """Add GeoJSON data to the map.

        Args:
            data: GeoJSON dict, URL to GeoJSON, or file path.
            name: Layer name.
            style: Style configuration dict.
            fit_bounds: Whether to fit map to data bounds.
            popup: HTML template for popups.
            popup_properties: List of property names to show in popup.
            **kwargs: Additional layer options.
        """
        self.add_vector(
            data,
            name=name,
            style=style,
            fit_bounds=fit_bounds,
            popup=popup,
            popup_properties=popup_properties,
            **kwargs,
        )

    def add_geojson_from_url(
        self,
        url: str,
        name: Optional[str] = None,
        style: Optional[Dict] = None,
        fit_bounds: bool = True,
        **kwargs,
    ) -> None:
        """Add GeoJSON from a URL (loaded directly by the browser).

        Args:
            url: URL to GeoJSON file.
            name: Layer name.
            style: Style configuration dict.
            fit_bounds: Whether to fit map to data bounds.
            **kwargs: Additional options.
        """
        layer_id = name or f"geojson-url-{len(self._layers)}"

        if style is None:
            style = {
                "fillColor": "rgba(51, 136, 255, 0.5)",
                "strokeColor": "#3388ff",
                "strokeWidth": 2,
                "radius": 6,
            }

        self.call_js_method(
            "addGeoJSONFromURL",
            url=url,
            name=layer_id,
            style=style,
            fitBounds=fit_bounds,
            **kwargs,
        )

        self._layers = {
            **self._layers,
            layer_id: {"id": layer_id, "type": "vector-url"},
        }

    def _get_default_style(self, geojson: Dict) -> Dict:
        """Get default style based on geometry type."""
        geom_type = self._infer_geom_type(geojson)

        if geom_type in ["Point", "MultiPoint"]:
            return {
                "fillColor": "rgba(51, 136, 255, 0.8)",
                "strokeColor": "#ffffff",
                "strokeWidth": 2,
                "radius": 6,
            }
        elif geom_type in ["LineString", "MultiLineString"]:
            return {
                "strokeColor": "#3388ff",
                "strokeWidth": 3,
            }
        else:
            return {
                "fillColor": "rgba(51, 136, 255, 0.5)",
                "strokeColor": "#3388ff",
                "strokeWidth": 2,
            }

    def _infer_geom_type(self, geojson: Dict) -> str:
        """Infer geometry type from GeoJSON."""
        if geojson.get("type") == "FeatureCollection":
            features = geojson.get("features", [])
            if features:
                return features[0].get("geometry", {}).get("type", "Point")
        elif geojson.get("type") == "Feature":
            return geojson.get("geometry", {}).get("type", "Point")
        return "Point"

    # -------------------------------------------------------------------------
    # Heatmap
    # -------------------------------------------------------------------------

    def add_heatmap(
        self,
        data: Any,
        name: Optional[str] = None,
        weight: Optional[str] = None,
        blur: int = 15,
        radius: int = 8,
        opacity: float = 0.8,
        gradient: Optional[List[str]] = None,
        fit_bounds: bool = True,
        **kwargs,
    ) -> None:
        """Add a heatmap layer (native OpenLayers heatmap).

        Args:
            data: GeoJSON (Point features), GeoDataFrame, or file path.
            name: Layer name.
            weight: Feature property to use as weight.
            blur: Blur size in pixels.
            radius: Radius size in pixels.
            opacity: Layer opacity.
            gradient: Color gradient as list of CSS color strings.
            fit_bounds: Whether to fit map to data bounds.
            **kwargs: Additional options.
        """
        geojson = to_geojson(data)
        layer_id = name or f"heatmap-{len(self._layers)}"

        self.call_js_method(
            "addHeatmap",
            data=geojson,
            name=layer_id,
            weight=weight,
            blur=blur,
            radius=radius,
            opacity=opacity,
            gradient=gradient,
            fitBounds=fit_bounds,
            **kwargs,
        )

        self._layers = {
            **self._layers,
            layer_id: {"id": layer_id, "type": "heatmap"},
        }

    # -------------------------------------------------------------------------
    # Clustering
    # -------------------------------------------------------------------------

    def add_cluster_layer(
        self,
        data: Any,
        name: Optional[str] = None,
        distance: int = 40,
        min_distance: int = 20,
        cluster_color: str = "rgba(51, 136, 255, 0.7)",
        point_color: str = "rgba(51, 136, 255, 0.9)",
        text_color: str = "#fff",
        fit_bounds: bool = True,
        **kwargs,
    ) -> None:
        """Add a clustered point layer.

        Args:
            data: GeoJSON (Point features), GeoDataFrame, or file path.
            name: Layer name.
            distance: Distance in pixels within which features are clustered.
            min_distance: Minimum distance between clusters.
            cluster_color: Color of cluster circles.
            point_color: Color of individual point circles.
            text_color: Color of cluster count text.
            fit_bounds: Whether to fit map to data bounds.
            **kwargs: Additional options.
        """
        geojson = to_geojson(data)
        layer_id = name or f"cluster-{len(self._layers)}"

        self.call_js_method(
            "addClusterLayer",
            data=geojson,
            name=layer_id,
            distance=distance,
            minDistance=min_distance,
            clusterColor=cluster_color,
            pointColor=point_color,
            textColor=text_color,
            fitBounds=fit_bounds,
            **kwargs,
        )

        self._layers = {
            **self._layers,
            layer_id: {"id": layer_id, "type": "cluster"},
        }

    def remove_cluster_layer(self, name: str) -> None:
        """Remove a cluster layer.

        Args:
            name: Layer name to remove.
        """
        self.remove_layer(name)

    # -------------------------------------------------------------------------
    # Choropleth
    # -------------------------------------------------------------------------

    def add_choropleth(
        self,
        data: Any,
        column: str,
        cmap: str = "YlOrRd",
        k: int = 5,
        classification: str = "quantile",
        name: Optional[str] = None,
        stroke_color: str = "#333",
        stroke_width: float = 1,
        opacity: float = 0.7,
        fit_bounds: bool = True,
        legend: bool = True,
        manual_breaks: Optional[List[float]] = None,
        **kwargs,
    ) -> None:
        """Add a choropleth (thematic) map layer.

        Args:
            data: GeoJSON or GeoDataFrame with polygon features.
            column: Property/column name to color by.
            cmap: Colormap name (e.g., 'YlOrRd', 'Blues', 'viridis').
            k: Number of classes.
            classification: Classification method ('quantile', 'equal_interval',
                'natural_breaks', 'manual').
            name: Layer name.
            stroke_color: Outline color.
            stroke_width: Outline width.
            opacity: Layer opacity.
            fit_bounds: Whether to fit map to data bounds.
            legend: Whether to show a legend.
            manual_breaks: Custom break values for 'manual' classification.
            **kwargs: Additional options.
        """
        geojson = to_geojson(data)
        layer_id = name or f"choropleth-{len(self._layers)}"

        features = geojson.get("features", [])
        values = []
        for f in features:
            val = f.get("properties", {}).get(column)
            if val is not None:
                try:
                    values.append(float(val))
                except (TypeError, ValueError):
                    pass

        if not values:
            raise ValueError(f"No numeric values found for column '{column}'")

        colors = get_choropleth_colors(cmap, k)
        breaks = compute_breaks(values, classification, k, manual_breaks)

        self.call_js_method(
            "addChoropleth",
            data=geojson,
            name=layer_id,
            column=column,
            breaks=breaks,
            colors=colors,
            strokeColor=stroke_color,
            strokeWidth=stroke_width,
            opacity=opacity,
            fitBounds=fit_bounds,
            legend=legend,
            **kwargs,
        )

        self._layers = {
            **self._layers,
            layer_id: {"id": layer_id, "type": "choropleth"},
        }

    # -------------------------------------------------------------------------
    # WMS/WMTS Methods
    # -------------------------------------------------------------------------

    def add_wms_layer(
        self,
        url: str,
        layers: str,
        name: Optional[str] = None,
        format: str = "image/png",
        transparent: bool = True,
        server_type: Optional[str] = None,
        attribution: str = "",
        **kwargs,
    ) -> None:
        """Add a WMS tile layer.

        Args:
            url: WMS service URL.
            layers: Comma-separated layer names.
            name: Layer name for the map.
            format: Image format (default: image/png).
            transparent: Whether to request transparent images.
            server_type: Server type ('mapserver', 'geoserver', 'qgis').
            attribution: Attribution text.
            **kwargs: Additional WMS parameters.
        """
        layer_id = name or f"wms-{len(self._layers)}"

        self.call_js_method(
            "addWMSLayer",
            url=url,
            layers=layers,
            name=layer_id,
            format=format,
            transparent=transparent,
            serverType=server_type,
            attribution=attribution,
            **kwargs,
        )

        self._layers = {
            **self._layers,
            layer_id: {"id": layer_id, "type": "wms"},
        }

    def add_image_wms_layer(
        self,
        url: str,
        layers: str,
        name: Optional[str] = None,
        format: str = "image/png",
        transparent: bool = True,
        server_type: Optional[str] = None,
        attribution: str = "",
        **kwargs,
    ) -> None:
        """Add a single-image WMS layer (not tiled).

        Args:
            url: WMS service URL.
            layers: Comma-separated layer names.
            name: Layer name for the map.
            format: Image format (default: image/png).
            transparent: Whether to request transparent images.
            server_type: Server type ('mapserver', 'geoserver', 'qgis').
            attribution: Attribution text.
            **kwargs: Additional WMS parameters.
        """
        layer_id = name or f"imagewms-{len(self._layers)}"

        self.call_js_method(
            "addImageWMSLayer",
            url=url,
            layers=layers,
            name=layer_id,
            format=format,
            transparent=transparent,
            serverType=server_type,
            attribution=attribution,
            **kwargs,
        )

        self._layers = {
            **self._layers,
            layer_id: {"id": layer_id, "type": "imagewms"},
        }

    def add_wmts_layer(
        self,
        url: str,
        layer: str,
        name: Optional[str] = None,
        matrix_set: str = "EPSG:3857",
        format: str = "image/png",
        style: str = "default",
        attribution: str = "",
        opacity: float = 1.0,
        **kwargs,
    ) -> None:
        """Add a WMTS tile layer.

        WMTS (Web Map Tile Service) provides pre-rendered tiles
        and is faster than WMS for large-scale maps.

        Args:
            url: WMTS service URL.
            layer: Layer identifier.
            name: Display name for the layer.
            matrix_set: Tile matrix set (projection), e.g., 'EPSG:3857'.
            format: Tile format (default: image/png).
            style: Style identifier (default: 'default').
            attribution: Attribution text.
            opacity: Layer opacity.
            **kwargs: Additional options.
        """
        layer_id = name or f"wmts-{len(self._layers)}"

        self.call_js_method(
            "addWMTSLayer",
            url=url,
            layer=layer,
            name=layer_id,
            matrixSet=matrix_set,
            format=format,
            style=style,
            attribution=attribution,
            opacity=opacity,
            **kwargs,
        )

        self._layers = {
            **self._layers,
            layer_id: {"id": layer_id, "type": "wmts"},
        }

    # -------------------------------------------------------------------------
    # Vector Tiles
    # -------------------------------------------------------------------------

    def add_vector_tile_layer(
        self,
        url: str,
        name: Optional[str] = None,
        style: Optional[Dict] = None,
        attribution: str = "",
        min_zoom: int = 0,
        max_zoom: int = 22,
        **kwargs,
    ) -> None:
        """Add a vector tile layer (MVT/PBF format).

        Args:
            url: Vector tile URL template with {x}, {y}, {z} placeholders.
            name: Layer name.
            style: Style configuration dict.
            attribution: Attribution text.
            min_zoom: Minimum zoom level.
            max_zoom: Maximum zoom level.
            **kwargs: Additional options.
        """
        layer_id = name or f"vectortile-{len(self._layers)}"

        self.call_js_method(
            "addVectorTileLayer",
            url=url,
            name=layer_id,
            style=style or {},
            attribution=attribution,
            minZoom=min_zoom,
            maxZoom=max_zoom,
            **kwargs,
        )

        self._layers = {
            **self._layers,
            layer_id: {"id": layer_id, "type": "vectortile"},
        }

    # -------------------------------------------------------------------------
    # Image Overlay
    # -------------------------------------------------------------------------

    def add_image_layer(
        self,
        url: str,
        bounds: List[float],
        name: Optional[str] = None,
        opacity: float = 1.0,
        **kwargs,
    ) -> None:
        """Add a georeferenced image overlay.

        Args:
            url: URL to the image.
            bounds: Image extent as [west, south, east, north] in EPSG:4326.
            name: Layer name.
            opacity: Layer opacity.
            **kwargs: Additional options.
        """
        layer_id = name or f"image-{len(self._layers)}"

        self.call_js_method(
            "addImageLayer",
            url=url,
            name=layer_id,
            bounds=bounds,
            opacity=opacity,
            **kwargs,
        )

        self._layers = {
            **self._layers,
            layer_id: {"id": layer_id, "type": "image"},
        }

    # -------------------------------------------------------------------------
    # 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)

    def set_layer_style(self, layer_id: str, style: Dict) -> None:
        """Update the style of a vector layer.

        Args:
            layer_id: Layer identifier.
            style: New style configuration dict.
        """
        self.call_js_method("setLayerStyle", layer_id, style=style)

    def set_layer_z_index(self, layer_id: str, z_index: int) -> None:
        """Set the z-index (draw order) of a layer.

        Args:
            layer_id: Layer identifier.
            z_index: Z-index value (higher = drawn on top).
        """
        self.call_js_method("setLayerZIndex", layer_id, z_index)

    # -------------------------------------------------------------------------
    # Controls
    # -------------------------------------------------------------------------

    def add_control(
        self,
        control_type: str,
        position: str = "top-right",
        **kwargs,
    ) -> None:
        """Add a map control.

        Supported control types:
            - 'zoom': Zoom in/out buttons
            - 'scale': Scale bar
            - 'fullscreen': Fullscreen toggle
            - 'attribution': Attribution display
            - 'rotate': Rotation reset button
            - 'mousePosition': Coordinate display at cursor
            - 'overviewMap': Mini overview map
            - 'zoomSlider': Zoom slider
            - 'zoomToExtent': Zoom to full extent button

        Args:
            control_type: Type of control.
            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, collapsed: bool = True) -> None:
        """Add a layer visibility control panel.

        Args:
            collapsed: Whether the panel starts collapsed.
        """
        self.call_js_method("addLayerControl", collapsed=collapsed)

    def remove_layer_control(self) -> None:
        """Remove the layer control panel."""
        self.call_js_method("removeLayerControl")

    # -------------------------------------------------------------------------
    # Navigation
    # -------------------------------------------------------------------------

    def set_center(self, lng: float, lat: float) -> None:
        """Set the map center.

        Args:
            lng: Longitude.
            lat: Latitude.
        """
        self.center = [lng, lat]
        self.call_js_method("setCenter", lng, lat)

    def set_zoom(self, zoom: float) -> None:
        """Set the map zoom level.

        Args:
            zoom: Zoom level.
        """
        self.zoom = zoom
        self.call_js_method("setZoom", zoom)

    def fly_to(
        self,
        lng: float,
        lat: float,
        zoom: Optional[float] = None,
        duration: int = 2000,
    ) -> None:
        """Animate to a new location.

        Args:
            lng: Target longitude.
            lat: Target latitude.
            zoom: Target zoom level (optional).
            duration: Animation duration in milliseconds.
        """
        self.call_js_method(
            "flyTo", lng, lat, zoom=zoom or self.zoom, duration=duration
        )

    def fit_bounds(
        self,
        bounds: List[float],
        padding: int = 50,
        duration: int = 1000,
    ) -> None:
        """Fit the map to bounds.

        Args:
            bounds: Bounds as [minLng, minLat, maxLng, maxLat].
            padding: Padding in pixels.
            duration: Animation duration in milliseconds.
        """
        self.call_js_method("fitBounds", bounds, padding=padding, duration=duration)

    def fit_extent(
        self,
        extent: List[float],
        padding: int = 50,
        duration: int = 1000,
    ) -> None:
        """Fit the map to an extent (in map projection).

        Args:
            extent: Extent as [minX, minY, maxX, maxY] in map projection.
            padding: Padding in pixels.
            duration: Animation duration in milliseconds.
        """
        self.call_js_method("fitExtent", extent, padding=padding, duration=duration)

    def set_rotation(self, rotation: float) -> None:
        """Set the map rotation.

        Args:
            rotation: Rotation in radians.
        """
        self.rotation = rotation
        self.call_js_method("setRotation", rotation)

    # -------------------------------------------------------------------------
    # Markers
    # -------------------------------------------------------------------------

    def add_marker(
        self,
        lng: float,
        lat: float,
        popup: Optional[str] = None,
        color: str = "#3388ff",
        name: Optional[str] = None,
        radius: int = 8,
        draggable: bool = False,
        **kwargs,
    ) -> None:
        """Add a marker to the map.

        Args:
            lng: Marker longitude.
            lat: Marker latitude.
            popup: Popup content (HTML string).
            color: Marker color.
            name: Marker identifier.
            radius: Marker radius in pixels.
            draggable: Whether the marker can be dragged.
            **kwargs: Additional options.
        """
        marker_id = name or f"marker-{len(self._layers)}"
        self.call_js_method(
            "addMarker",
            lng,
            lat,
            popup=popup,
            color=color,
            id=marker_id,
            radius=radius,
            draggable=draggable,
            **kwargs,
        )

    def remove_marker(self, name: str) -> None:
        """Remove a marker from the map.

        Args:
            name: Marker identifier to remove.
        """
        self.call_js_method("removeMarker", name)

    # -------------------------------------------------------------------------
    # Popups / Overlays
    # -------------------------------------------------------------------------

    def show_popup(
        self,
        lng: float,
        lat: float,
        content: str,
    ) -> None:
        """Show a popup at a location.

        Args:
            lng: Longitude.
            lat: Latitude.
            content: HTML content for the popup.
        """
        self.call_js_method("showPopup", lng=lng, lat=lat, content=content)

    def remove_popup(self) -> None:
        """Remove/close the current popup."""
        self.call_js_method("removePopup")

    # -------------------------------------------------------------------------
    # Draw Interaction
    # -------------------------------------------------------------------------

    def add_draw_control(
        self,
        draw_type: str = "Polygon",
        **kwargs,
    ) -> None:
        """Add a drawing interaction to the map.

        Allows users to draw features on the map. Drawn features are
        synced back as GeoJSON in the `_draw_data` trait.

        Args:
            draw_type: Geometry type to draw. One of 'Point', 'LineString',
                'Polygon', 'Circle'.
            **kwargs: Additional options.
        """
        self.call_js_method("addDrawControl", type=draw_type, **kwargs)

    def remove_draw_control(self) -> None:
        """Remove the drawing interaction."""
        self.call_js_method("removeDrawControl")

    def clear_draw_data(self) -> None:
        """Clear all drawn features."""
        self.call_js_method("clearDrawData")

    @property
    def draw_data(self) -> Dict:
        """Get the current drawn features as GeoJSON.

        Returns:
            GeoJSON FeatureCollection of drawn features.
        """
        return self._draw_data

    # -------------------------------------------------------------------------
    # Measure
    # -------------------------------------------------------------------------

    def add_measure_control(
        self,
        measure_type: str = "LineString",
        **kwargs,
    ) -> None:
        """Add a measurement tool.

        Args:
            measure_type: Type of measurement.
                'LineString' for distance, 'Polygon' for area.
            **kwargs: Additional options.
        """
        self.call_js_method("addMeasureControl", type=measure_type, **kwargs)

    def remove_measure_control(self) -> None:
        """Remove the measurement tool."""
        self.call_js_method("removeMeasureControl")

    # -------------------------------------------------------------------------
    # Select Interaction
    # -------------------------------------------------------------------------

    def add_select_interaction(self, multi: bool = False) -> None:
        """Add a click-to-select interaction.

        Selected feature properties are stored in `_queried_features`.

        Args:
            multi: Whether to allow selecting multiple features.
        """
        self.call_js_method("addSelectInteraction", multi=multi)

    def remove_select_interaction(self) -> None:
        """Remove the select interaction."""
        self.call_js_method("removeSelectInteraction")

    # -------------------------------------------------------------------------
    # Graticule
    # -------------------------------------------------------------------------

    def add_graticule(
        self,
        stroke_color: str = "rgba(0, 0, 0, 0.2)",
        stroke_width: float = 1,
        show_labels: bool = True,
    ) -> None:
        """Add a coordinate grid (graticule) overlay.

        Args:
            stroke_color: Grid line color.
            stroke_width: Grid line width.
            show_labels: Whether to show coordinate labels.
        """
        self.call_js_method(
            "addGraticule",
            strokeColor=stroke_color,
            strokeWidth=stroke_width,
            showLabels=show_labels,
        )

    def remove_graticule(self) -> None:
        """Remove the graticule overlay."""
        self.call_js_method("removeGraticule")

    # -------------------------------------------------------------------------
    # HTML Export
    # -------------------------------------------------------------------------

    def _generate_html_template(self) -> str:
        """Generate standalone HTML for the map."""
        template_path = Path(__file__).parent / "templates" / "openlayers.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,
            "projection": self.projection,
            "rotation": self.rotation,
            "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>OpenLayers Map</title>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/ol@v10.0.0/ol.css">
    <script src="https://cdn.jsdelivr.net/npm/ol@v10.0.0/dist/ol.js"></script>
    <style>
        * { margin: 0; padding: 0; box-sizing: border-box; }
        html, body { height: 100%; }
        #map { position: absolute; top: 0; bottom: 0; width: 100%; }
    </style>
</head>
<body>
    <div id="map"></div>
    <script>
        const state = {{state}};

        const map = new ol.Map({
            target: 'map',
            view: new ol.View({
                center: ol.proj.fromLonLat(state.center),
                zoom: state.zoom
            })
        });

        for (const call of state.js_calls || []) {
            executeMethod(call.method, call.args, call.kwargs);
        }

        function executeMethod(method, args, kwargs) {
            console.log('Executing:', method, args, kwargs);
        }
    </script>
</body>
</html>"""

draw_data: Dict property readonly

Get the current drawn features as GeoJSON.

Returns:

Type Description
Dict

GeoJSON FeatureCollection of drawn features.

__init__(self, center=(0.0, 0.0), zoom=2.0, width='100%', height='600px', projection='EPSG:3857', rotation=0.0, controls=None, **kwargs) special

Initialize an OpenLayers 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'
projection str

Map projection (default EPSG:3857).

'EPSG:3857'
rotation float

Map rotation in radians.

0.0
controls Optional[Dict[str, Any]]

Dict of controls to add. Pass False to disable defaults.

None
**kwargs

Additional widget arguments.

{}
Source code in anymap_ts/openlayers.py
def __init__(
    self,
    center: Tuple[float, float] = (0.0, 0.0),
    zoom: float = 2.0,
    width: str = "100%",
    height: str = "600px",
    projection: str = "EPSG:3857",
    rotation: float = 0.0,
    controls: Optional[Dict[str, Any]] = None,
    **kwargs,
):
    """Initialize an OpenLayers map.

    Args:
        center: Map center as (longitude, latitude).
        zoom: Initial zoom level.
        width: Map width as CSS string.
        height: Map height as CSS string.
        projection: Map projection (default EPSG:3857).
        rotation: Map rotation in radians.
        controls: Dict of controls to add. Pass False to disable defaults.
        **kwargs: Additional widget arguments.
    """
    super().__init__(
        center=list(center),
        zoom=zoom,
        width=width,
        height=height,
        projection=projection,
        rotation=rotation,
        **kwargs,
    )

    self._layer_dict = {"Background": []}

    if controls is None:
        controls = {
            "zoom": True,
            "attribution": True,
            "scale": {"units": "metric"},
        }

    if controls is not False:
        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 or a tile URL.

'OpenStreetMap'
attribution Optional[str]

Custom attribution text.

None
**kwargs

Additional options.

{}
Source code in anymap_ts/openlayers.py
def add_basemap(
    self,
    basemap: str = "OpenStreetMap",
    attribution: Optional[str] = None,
    **kwargs,
) -> None:
    """Add a basemap layer.

    Args:
        basemap: Name of basemap provider 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, column, cmap='YlOrRd', k=5, classification='quantile', name=None, stroke_color='#333', stroke_width=1, opacity=0.7, fit_bounds=True, legend=True, manual_breaks=None, **kwargs)

Add a choropleth (thematic) map layer.

Parameters:

Name Type Description Default
data Any

GeoJSON or GeoDataFrame with polygon features.

required
column str

Property/column name to color by.

required
cmap str

Colormap name (e.g., 'YlOrRd', 'Blues', 'viridis').

'YlOrRd'
k int

Number of classes.

5
classification str

Classification method ('quantile', 'equal_interval', 'natural_breaks', 'manual').

'quantile'
name Optional[str]

Layer name.

None
stroke_color str

Outline color.

'#333'
stroke_width float

Outline width.

1
opacity float

Layer opacity.

0.7
fit_bounds bool

Whether to fit map to data bounds.

True
legend bool

Whether to show a legend.

True
manual_breaks Optional[List[float]]

Custom break values for 'manual' classification.

None
**kwargs

Additional options.

{}
Source code in anymap_ts/openlayers.py
def add_choropleth(
    self,
    data: Any,
    column: str,
    cmap: str = "YlOrRd",
    k: int = 5,
    classification: str = "quantile",
    name: Optional[str] = None,
    stroke_color: str = "#333",
    stroke_width: float = 1,
    opacity: float = 0.7,
    fit_bounds: bool = True,
    legend: bool = True,
    manual_breaks: Optional[List[float]] = None,
    **kwargs,
) -> None:
    """Add a choropleth (thematic) map layer.

    Args:
        data: GeoJSON or GeoDataFrame with polygon features.
        column: Property/column name to color by.
        cmap: Colormap name (e.g., 'YlOrRd', 'Blues', 'viridis').
        k: Number of classes.
        classification: Classification method ('quantile', 'equal_interval',
            'natural_breaks', 'manual').
        name: Layer name.
        stroke_color: Outline color.
        stroke_width: Outline width.
        opacity: Layer opacity.
        fit_bounds: Whether to fit map to data bounds.
        legend: Whether to show a legend.
        manual_breaks: Custom break values for 'manual' classification.
        **kwargs: Additional options.
    """
    geojson = to_geojson(data)
    layer_id = name or f"choropleth-{len(self._layers)}"

    features = geojson.get("features", [])
    values = []
    for f in features:
        val = f.get("properties", {}).get(column)
        if val is not None:
            try:
                values.append(float(val))
            except (TypeError, ValueError):
                pass

    if not values:
        raise ValueError(f"No numeric values found for column '{column}'")

    colors = get_choropleth_colors(cmap, k)
    breaks = compute_breaks(values, classification, k, manual_breaks)

    self.call_js_method(
        "addChoropleth",
        data=geojson,
        name=layer_id,
        column=column,
        breaks=breaks,
        colors=colors,
        strokeColor=stroke_color,
        strokeWidth=stroke_width,
        opacity=opacity,
        fitBounds=fit_bounds,
        legend=legend,
        **kwargs,
    )

    self._layers = {
        **self._layers,
        layer_id: {"id": layer_id, "type": "choropleth"},
    }

add_cluster_layer(self, data, name=None, distance=40, min_distance=20, cluster_color='rgba(51, 136, 255, 0.7)', point_color='rgba(51, 136, 255, 0.9)', text_color='#fff', fit_bounds=True, **kwargs)

Add a clustered point layer.

Parameters:

Name Type Description Default
data Any

GeoJSON (Point features), GeoDataFrame, or file path.

required
name Optional[str]

Layer name.

None
distance int

Distance in pixels within which features are clustered.

40
min_distance int

Minimum distance between clusters.

20
cluster_color str

Color of cluster circles.

'rgba(51, 136, 255, 0.7)'
point_color str

Color of individual point circles.

'rgba(51, 136, 255, 0.9)'
text_color str

Color of cluster count text.

'#fff'
fit_bounds bool

Whether to fit map to data bounds.

True
**kwargs

Additional options.

{}
Source code in anymap_ts/openlayers.py
def add_cluster_layer(
    self,
    data: Any,
    name: Optional[str] = None,
    distance: int = 40,
    min_distance: int = 20,
    cluster_color: str = "rgba(51, 136, 255, 0.7)",
    point_color: str = "rgba(51, 136, 255, 0.9)",
    text_color: str = "#fff",
    fit_bounds: bool = True,
    **kwargs,
) -> None:
    """Add a clustered point layer.

    Args:
        data: GeoJSON (Point features), GeoDataFrame, or file path.
        name: Layer name.
        distance: Distance in pixels within which features are clustered.
        min_distance: Minimum distance between clusters.
        cluster_color: Color of cluster circles.
        point_color: Color of individual point circles.
        text_color: Color of cluster count text.
        fit_bounds: Whether to fit map to data bounds.
        **kwargs: Additional options.
    """
    geojson = to_geojson(data)
    layer_id = name or f"cluster-{len(self._layers)}"

    self.call_js_method(
        "addClusterLayer",
        data=geojson,
        name=layer_id,
        distance=distance,
        minDistance=min_distance,
        clusterColor=cluster_color,
        pointColor=point_color,
        textColor=text_color,
        fitBounds=fit_bounds,
        **kwargs,
    )

    self._layers = {
        **self._layers,
        layer_id: {"id": layer_id, "type": "cluster"},
    }

add_control(self, control_type, position='top-right', **kwargs)

Add a map control.

Supported control types: - 'zoom': Zoom in/out buttons - 'scale': Scale bar - 'fullscreen': Fullscreen toggle - 'attribution': Attribution display - 'rotate': Rotation reset button - 'mousePosition': Coordinate display at cursor - 'overviewMap': Mini overview map - 'zoomSlider': Zoom slider - 'zoomToExtent': Zoom to full extent button

Parameters:

Name Type Description Default
control_type str

Type of control.

required
position str

Control position.

'top-right'
**kwargs

Control-specific options.

{}
Source code in anymap_ts/openlayers.py
def add_control(
    self,
    control_type: str,
    position: str = "top-right",
    **kwargs,
) -> None:
    """Add a map control.

    Supported control types:
        - 'zoom': Zoom in/out buttons
        - 'scale': Scale bar
        - 'fullscreen': Fullscreen toggle
        - 'attribution': Attribution display
        - 'rotate': Rotation reset button
        - 'mousePosition': Coordinate display at cursor
        - 'overviewMap': Mini overview map
        - 'zoomSlider': Zoom slider
        - 'zoomToExtent': Zoom to full extent button

    Args:
        control_type: Type of control.
        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_draw_control(self, draw_type='Polygon', **kwargs)

Add a drawing interaction to the map.

Allows users to draw features on the map. Drawn features are synced back as GeoJSON in the _draw_data trait.

Parameters:

Name Type Description Default
draw_type str

Geometry type to draw. One of 'Point', 'LineString', 'Polygon', 'Circle'.

'Polygon'
**kwargs

Additional options.

{}
Source code in anymap_ts/openlayers.py
def add_draw_control(
    self,
    draw_type: str = "Polygon",
    **kwargs,
) -> None:
    """Add a drawing interaction to the map.

    Allows users to draw features on the map. Drawn features are
    synced back as GeoJSON in the `_draw_data` trait.

    Args:
        draw_type: Geometry type to draw. One of 'Point', 'LineString',
            'Polygon', 'Circle'.
        **kwargs: Additional options.
    """
    self.call_js_method("addDrawControl", type=draw_type, **kwargs)

add_geojson(self, data, name=None, style=None, fit_bounds=True, popup=None, popup_properties=None, **kwargs)

Add GeoJSON data to the map.

Parameters:

Name Type Description Default
data Union[str, Dict]

GeoJSON dict, URL to GeoJSON, or file path.

required
name Optional[str]

Layer name.

None
style Optional[Dict]

Style configuration dict.

None
fit_bounds bool

Whether to fit map to data bounds.

True
popup Optional[str]

HTML template for popups.

None
popup_properties Optional[List[str]]

List of property names to show in popup.

None
**kwargs

Additional layer options.

{}
Source code in anymap_ts/openlayers.py
def add_geojson(
    self,
    data: Union[str, Dict],
    name: Optional[str] = None,
    style: Optional[Dict] = None,
    fit_bounds: bool = True,
    popup: Optional[str] = None,
    popup_properties: Optional[List[str]] = None,
    **kwargs,
) -> None:
    """Add GeoJSON data to the map.

    Args:
        data: GeoJSON dict, URL to GeoJSON, or file path.
        name: Layer name.
        style: Style configuration dict.
        fit_bounds: Whether to fit map to data bounds.
        popup: HTML template for popups.
        popup_properties: List of property names to show in popup.
        **kwargs: Additional layer options.
    """
    self.add_vector(
        data,
        name=name,
        style=style,
        fit_bounds=fit_bounds,
        popup=popup,
        popup_properties=popup_properties,
        **kwargs,
    )

add_geojson_from_url(self, url, name=None, style=None, fit_bounds=True, **kwargs)

Add GeoJSON from a URL (loaded directly by the browser).

Parameters:

Name Type Description Default
url str

URL to GeoJSON file.

required
name Optional[str]

Layer name.

None
style Optional[Dict]

Style configuration dict.

None
fit_bounds bool

Whether to fit map to data bounds.

True
**kwargs

Additional options.

{}
Source code in anymap_ts/openlayers.py
def add_geojson_from_url(
    self,
    url: str,
    name: Optional[str] = None,
    style: Optional[Dict] = None,
    fit_bounds: bool = True,
    **kwargs,
) -> None:
    """Add GeoJSON from a URL (loaded directly by the browser).

    Args:
        url: URL to GeoJSON file.
        name: Layer name.
        style: Style configuration dict.
        fit_bounds: Whether to fit map to data bounds.
        **kwargs: Additional options.
    """
    layer_id = name or f"geojson-url-{len(self._layers)}"

    if style is None:
        style = {
            "fillColor": "rgba(51, 136, 255, 0.5)",
            "strokeColor": "#3388ff",
            "strokeWidth": 2,
            "radius": 6,
        }

    self.call_js_method(
        "addGeoJSONFromURL",
        url=url,
        name=layer_id,
        style=style,
        fitBounds=fit_bounds,
        **kwargs,
    )

    self._layers = {
        **self._layers,
        layer_id: {"id": layer_id, "type": "vector-url"},
    }

add_graticule(self, stroke_color='rgba(0, 0, 0, 0.2)', stroke_width=1, show_labels=True)

Add a coordinate grid (graticule) overlay.

Parameters:

Name Type Description Default
stroke_color str

Grid line color.

'rgba(0, 0, 0, 0.2)'
stroke_width float

Grid line width.

1
show_labels bool

Whether to show coordinate labels.

True
Source code in anymap_ts/openlayers.py
def add_graticule(
    self,
    stroke_color: str = "rgba(0, 0, 0, 0.2)",
    stroke_width: float = 1,
    show_labels: bool = True,
) -> None:
    """Add a coordinate grid (graticule) overlay.

    Args:
        stroke_color: Grid line color.
        stroke_width: Grid line width.
        show_labels: Whether to show coordinate labels.
    """
    self.call_js_method(
        "addGraticule",
        strokeColor=stroke_color,
        strokeWidth=stroke_width,
        showLabels=show_labels,
    )

add_heatmap(self, data, name=None, weight=None, blur=15, radius=8, opacity=0.8, gradient=None, fit_bounds=True, **kwargs)

Add a heatmap layer (native OpenLayers heatmap).

Parameters:

Name Type Description Default
data Any

GeoJSON (Point features), GeoDataFrame, or file path.

required
name Optional[str]

Layer name.

None
weight Optional[str]

Feature property to use as weight.

None
blur int

Blur size in pixels.

15
radius int

Radius size in pixels.

8
opacity float

Layer opacity.

0.8
gradient Optional[List[str]]

Color gradient as list of CSS color strings.

None
fit_bounds bool

Whether to fit map to data bounds.

True
**kwargs

Additional options.

{}
Source code in anymap_ts/openlayers.py
def add_heatmap(
    self,
    data: Any,
    name: Optional[str] = None,
    weight: Optional[str] = None,
    blur: int = 15,
    radius: int = 8,
    opacity: float = 0.8,
    gradient: Optional[List[str]] = None,
    fit_bounds: bool = True,
    **kwargs,
) -> None:
    """Add a heatmap layer (native OpenLayers heatmap).

    Args:
        data: GeoJSON (Point features), GeoDataFrame, or file path.
        name: Layer name.
        weight: Feature property to use as weight.
        blur: Blur size in pixels.
        radius: Radius size in pixels.
        opacity: Layer opacity.
        gradient: Color gradient as list of CSS color strings.
        fit_bounds: Whether to fit map to data bounds.
        **kwargs: Additional options.
    """
    geojson = to_geojson(data)
    layer_id = name or f"heatmap-{len(self._layers)}"

    self.call_js_method(
        "addHeatmap",
        data=geojson,
        name=layer_id,
        weight=weight,
        blur=blur,
        radius=radius,
        opacity=opacity,
        gradient=gradient,
        fitBounds=fit_bounds,
        **kwargs,
    )

    self._layers = {
        **self._layers,
        layer_id: {"id": layer_id, "type": "heatmap"},
    }

add_image_layer(self, url, bounds, name=None, opacity=1.0, **kwargs)

Add a georeferenced image overlay.

Parameters:

Name Type Description Default
url str

URL to the image.

required
bounds List[float]

Image extent as [west, south, east, north] in EPSG:4326.

required
name Optional[str]

Layer name.

None
opacity float

Layer opacity.

1.0
**kwargs

Additional options.

{}
Source code in anymap_ts/openlayers.py
def add_image_layer(
    self,
    url: str,
    bounds: List[float],
    name: Optional[str] = None,
    opacity: float = 1.0,
    **kwargs,
) -> None:
    """Add a georeferenced image overlay.

    Args:
        url: URL to the image.
        bounds: Image extent as [west, south, east, north] in EPSG:4326.
        name: Layer name.
        opacity: Layer opacity.
        **kwargs: Additional options.
    """
    layer_id = name or f"image-{len(self._layers)}"

    self.call_js_method(
        "addImageLayer",
        url=url,
        name=layer_id,
        bounds=bounds,
        opacity=opacity,
        **kwargs,
    )

    self._layers = {
        **self._layers,
        layer_id: {"id": layer_id, "type": "image"},
    }

add_image_wms_layer(self, url, layers, name=None, format='image/png', transparent=True, server_type=None, attribution='', **kwargs)

Add a single-image WMS layer (not tiled).

Parameters:

Name Type Description Default
url str

WMS service URL.

required
layers str

Comma-separated layer names.

required
name Optional[str]

Layer name for the map.

None
format str

Image format (default: image/png).

'image/png'
transparent bool

Whether to request transparent images.

True
server_type Optional[str]

Server type ('mapserver', 'geoserver', 'qgis').

None
attribution str

Attribution text.

''
**kwargs

Additional WMS parameters.

{}
Source code in anymap_ts/openlayers.py
def add_image_wms_layer(
    self,
    url: str,
    layers: str,
    name: Optional[str] = None,
    format: str = "image/png",
    transparent: bool = True,
    server_type: Optional[str] = None,
    attribution: str = "",
    **kwargs,
) -> None:
    """Add a single-image WMS layer (not tiled).

    Args:
        url: WMS service URL.
        layers: Comma-separated layer names.
        name: Layer name for the map.
        format: Image format (default: image/png).
        transparent: Whether to request transparent images.
        server_type: Server type ('mapserver', 'geoserver', 'qgis').
        attribution: Attribution text.
        **kwargs: Additional WMS parameters.
    """
    layer_id = name or f"imagewms-{len(self._layers)}"

    self.call_js_method(
        "addImageWMSLayer",
        url=url,
        layers=layers,
        name=layer_id,
        format=format,
        transparent=transparent,
        serverType=server_type,
        attribution=attribution,
        **kwargs,
    )

    self._layers = {
        **self._layers,
        layer_id: {"id": layer_id, "type": "imagewms"},
    }

add_layer_control(self, collapsed=True)

Add a layer visibility control panel.

Parameters:

Name Type Description Default
collapsed bool

Whether the panel starts collapsed.

True
Source code in anymap_ts/openlayers.py
def add_layer_control(self, collapsed: bool = True) -> None:
    """Add a layer visibility control panel.

    Args:
        collapsed: Whether the panel starts collapsed.
    """
    self.call_js_method("addLayerControl", collapsed=collapsed)

add_marker(self, lng, lat, popup=None, color='#3388ff', name=None, radius=8, draggable=False, **kwargs)

Add a marker to the map.

Parameters:

Name Type Description Default
lng float

Marker longitude.

required
lat float

Marker latitude.

required
popup Optional[str]

Popup content (HTML string).

None
color str

Marker color.

'#3388ff'
name Optional[str]

Marker identifier.

None
radius int

Marker radius in pixels.

8
draggable bool

Whether the marker can be dragged.

False
**kwargs

Additional options.

{}
Source code in anymap_ts/openlayers.py
def add_marker(
    self,
    lng: float,
    lat: float,
    popup: Optional[str] = None,
    color: str = "#3388ff",
    name: Optional[str] = None,
    radius: int = 8,
    draggable: bool = False,
    **kwargs,
) -> None:
    """Add a marker to the map.

    Args:
        lng: Marker longitude.
        lat: Marker latitude.
        popup: Popup content (HTML string).
        color: Marker color.
        name: Marker identifier.
        radius: Marker radius in pixels.
        draggable: Whether the marker can be dragged.
        **kwargs: Additional options.
    """
    marker_id = name or f"marker-{len(self._layers)}"
    self.call_js_method(
        "addMarker",
        lng,
        lat,
        popup=popup,
        color=color,
        id=marker_id,
        radius=radius,
        draggable=draggable,
        **kwargs,
    )

add_measure_control(self, measure_type='LineString', **kwargs)

Add a measurement tool.

Parameters:

Name Type Description Default
measure_type str

Type of measurement. 'LineString' for distance, 'Polygon' for area.

'LineString'
**kwargs

Additional options.

{}
Source code in anymap_ts/openlayers.py
def add_measure_control(
    self,
    measure_type: str = "LineString",
    **kwargs,
) -> None:
    """Add a measurement tool.

    Args:
        measure_type: Type of measurement.
            'LineString' for distance, 'Polygon' for area.
        **kwargs: Additional options.
    """
    self.call_js_method("addMeasureControl", type=measure_type, **kwargs)

add_select_interaction(self, multi=False)

Add a click-to-select interaction.

Selected feature properties are stored in _queried_features.

Parameters:

Name Type Description Default
multi bool

Whether to allow selecting multiple features.

False
Source code in anymap_ts/openlayers.py
def add_select_interaction(self, multi: bool = False) -> None:
    """Add a click-to-select interaction.

    Selected feature properties are stored in `_queried_features`.

    Args:
        multi: Whether to allow selecting multiple features.
    """
    self.call_js_method("addSelectInteraction", multi=multi)

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.

1.0
**kwargs

Additional options.

{}
Source code in anymap_ts/openlayers.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.
        **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, name=None, style=None, fit_bounds=True, popup=None, popup_properties=None, **kwargs)

Add vector data to the map.

Parameters:

Name Type Description Default
data Any

GeoJSON dict, GeoDataFrame, URL, or file path.

required
name Optional[str]

Layer name.

None
style Optional[Dict]

Style configuration dict with keys like fillColor, strokeColor, strokeWidth, radius, lineDash, text, textColor, font.

None
fit_bounds bool

Whether to fit map to data bounds.

True
popup Optional[str]

HTML template for popups, with {property} placeholders.

None
popup_properties Optional[List[str]]

List of property names to show in popup table.

None
**kwargs

Additional layer options.

{}
Source code in anymap_ts/openlayers.py
def add_vector(
    self,
    data: Any,
    name: Optional[str] = None,
    style: Optional[Dict] = None,
    fit_bounds: bool = True,
    popup: Optional[str] = None,
    popup_properties: Optional[List[str]] = None,
    **kwargs,
) -> None:
    """Add vector data to the map.

    Args:
        data: GeoJSON dict, GeoDataFrame, URL, or file path.
        name: Layer name.
        style: Style configuration dict with keys like fillColor, strokeColor,
            strokeWidth, radius, lineDash, text, textColor, font.
        fit_bounds: Whether to fit map to data bounds.
        popup: HTML template for popups, with {property} placeholders.
        popup_properties: List of property names to show in popup table.
        **kwargs: Additional layer options.
    """
    geojson = to_geojson(data)

    if geojson.get("type") == "url":
        self.add_geojson_from_url(
            geojson["url"],
            name=name,
            style=style,
            fit_bounds=fit_bounds,
            **kwargs,
        )
        return

    layer_id = name or f"vector-{len(self._layers)}"

    if style is None:
        style = self._get_default_style(geojson)

    self.call_js_method(
        "addGeoJSON",
        data=geojson,
        name=layer_id,
        style=style,
        fitBounds=fit_bounds,
        popup=popup,
        popupProperties=popup_properties,
        **kwargs,
    )

    self._layers = {
        **self._layers,
        layer_id: {"id": layer_id, "type": "vector"},
    }

add_vector_tile_layer(self, url, name=None, style=None, attribution='', min_zoom=0, max_zoom=22, **kwargs)

Add a vector tile layer (MVT/PBF format).

Parameters:

Name Type Description Default
url str

Vector tile URL template with {x}, {y}, {z} placeholders.

required
name Optional[str]

Layer name.

None
style Optional[Dict]

Style configuration dict.

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/openlayers.py
def add_vector_tile_layer(
    self,
    url: str,
    name: Optional[str] = None,
    style: Optional[Dict] = None,
    attribution: str = "",
    min_zoom: int = 0,
    max_zoom: int = 22,
    **kwargs,
) -> None:
    """Add a vector tile layer (MVT/PBF format).

    Args:
        url: Vector tile URL template with {x}, {y}, {z} placeholders.
        name: Layer name.
        style: Style configuration dict.
        attribution: Attribution text.
        min_zoom: Minimum zoom level.
        max_zoom: Maximum zoom level.
        **kwargs: Additional options.
    """
    layer_id = name or f"vectortile-{len(self._layers)}"

    self.call_js_method(
        "addVectorTileLayer",
        url=url,
        name=layer_id,
        style=style or {},
        attribution=attribution,
        minZoom=min_zoom,
        maxZoom=max_zoom,
        **kwargs,
    )

    self._layers = {
        **self._layers,
        layer_id: {"id": layer_id, "type": "vectortile"},
    }

add_wms_layer(self, url, layers, name=None, format='image/png', transparent=True, server_type=None, attribution='', **kwargs)

Add a WMS tile layer.

Parameters:

Name Type Description Default
url str

WMS service URL.

required
layers str

Comma-separated layer names.

required
name Optional[str]

Layer name for the map.

None
format str

Image format (default: image/png).

'image/png'
transparent bool

Whether to request transparent images.

True
server_type Optional[str]

Server type ('mapserver', 'geoserver', 'qgis').

None
attribution str

Attribution text.

''
**kwargs

Additional WMS parameters.

{}
Source code in anymap_ts/openlayers.py
def add_wms_layer(
    self,
    url: str,
    layers: str,
    name: Optional[str] = None,
    format: str = "image/png",
    transparent: bool = True,
    server_type: Optional[str] = None,
    attribution: str = "",
    **kwargs,
) -> None:
    """Add a WMS tile layer.

    Args:
        url: WMS service URL.
        layers: Comma-separated layer names.
        name: Layer name for the map.
        format: Image format (default: image/png).
        transparent: Whether to request transparent images.
        server_type: Server type ('mapserver', 'geoserver', 'qgis').
        attribution: Attribution text.
        **kwargs: Additional WMS parameters.
    """
    layer_id = name or f"wms-{len(self._layers)}"

    self.call_js_method(
        "addWMSLayer",
        url=url,
        layers=layers,
        name=layer_id,
        format=format,
        transparent=transparent,
        serverType=server_type,
        attribution=attribution,
        **kwargs,
    )

    self._layers = {
        **self._layers,
        layer_id: {"id": layer_id, "type": "wms"},
    }

add_wmts_layer(self, url, layer, name=None, matrix_set='EPSG:3857', format='image/png', style='default', attribution='', opacity=1.0, **kwargs)

Add a WMTS tile layer.

WMTS (Web Map Tile Service) provides pre-rendered tiles and is faster than WMS for large-scale maps.

Parameters:

Name Type Description Default
url str

WMTS service URL.

required
layer str

Layer identifier.

required
name Optional[str]

Display name for the layer.

None
matrix_set str

Tile matrix set (projection), e.g., 'EPSG:3857'.

'EPSG:3857'
format str

Tile format (default: image/png).

'image/png'
style str

Style identifier (default: 'default').

'default'
attribution str

Attribution text.

''
opacity float

Layer opacity.

1.0
**kwargs

Additional options.

{}
Source code in anymap_ts/openlayers.py
def add_wmts_layer(
    self,
    url: str,
    layer: str,
    name: Optional[str] = None,
    matrix_set: str = "EPSG:3857",
    format: str = "image/png",
    style: str = "default",
    attribution: str = "",
    opacity: float = 1.0,
    **kwargs,
) -> None:
    """Add a WMTS tile layer.

    WMTS (Web Map Tile Service) provides pre-rendered tiles
    and is faster than WMS for large-scale maps.

    Args:
        url: WMTS service URL.
        layer: Layer identifier.
        name: Display name for the layer.
        matrix_set: Tile matrix set (projection), e.g., 'EPSG:3857'.
        format: Tile format (default: image/png).
        style: Style identifier (default: 'default').
        attribution: Attribution text.
        opacity: Layer opacity.
        **kwargs: Additional options.
    """
    layer_id = name or f"wmts-{len(self._layers)}"

    self.call_js_method(
        "addWMTSLayer",
        url=url,
        layer=layer,
        name=layer_id,
        matrixSet=matrix_set,
        format=format,
        style=style,
        attribution=attribution,
        opacity=opacity,
        **kwargs,
    )

    self._layers = {
        **self._layers,
        layer_id: {"id": layer_id, "type": "wmts"},
    }

clear_draw_data(self)

Clear all drawn features.

Source code in anymap_ts/openlayers.py
def clear_draw_data(self) -> None:
    """Clear all drawn features."""
    self.call_js_method("clearDrawData")

fit_bounds(self, bounds, padding=50, duration=1000)

Fit the map to bounds.

Parameters:

Name Type Description Default
bounds List[float]

Bounds as [minLng, minLat, maxLng, maxLat].

required
padding int

Padding in pixels.

50
duration int

Animation duration in milliseconds.

1000
Source code in anymap_ts/openlayers.py
def fit_bounds(
    self,
    bounds: List[float],
    padding: int = 50,
    duration: int = 1000,
) -> None:
    """Fit the map to bounds.

    Args:
        bounds: Bounds as [minLng, minLat, maxLng, maxLat].
        padding: Padding in pixels.
        duration: Animation duration in milliseconds.
    """
    self.call_js_method("fitBounds", bounds, padding=padding, duration=duration)

fit_extent(self, extent, padding=50, duration=1000)

Fit the map to an extent (in map projection).

Parameters:

Name Type Description Default
extent List[float]

Extent as [minX, minY, maxX, maxY] in map projection.

required
padding int

Padding in pixels.

50
duration int

Animation duration in milliseconds.

1000
Source code in anymap_ts/openlayers.py
def fit_extent(
    self,
    extent: List[float],
    padding: int = 50,
    duration: int = 1000,
) -> None:
    """Fit the map to an extent (in map projection).

    Args:
        extent: Extent as [minX, minY, maxX, maxY] in map projection.
        padding: Padding in pixels.
        duration: Animation duration in milliseconds.
    """
    self.call_js_method("fitExtent", extent, padding=padding, duration=duration)

fly_to(self, lng, lat, zoom=None, duration=2000)

Animate to a new location.

Parameters:

Name Type Description Default
lng float

Target longitude.

required
lat float

Target latitude.

required
zoom Optional[float]

Target zoom level (optional).

None
duration int

Animation duration in milliseconds.

2000
Source code in anymap_ts/openlayers.py
def fly_to(
    self,
    lng: float,
    lat: float,
    zoom: Optional[float] = None,
    duration: int = 2000,
) -> None:
    """Animate to a new location.

    Args:
        lng: Target longitude.
        lat: Target latitude.
        zoom: Target zoom level (optional).
        duration: Animation duration in milliseconds.
    """
    self.call_js_method(
        "flyTo", lng, lat, zoom=zoom or self.zoom, duration=duration
    )

remove_cluster_layer(self, name)

Remove a cluster layer.

Parameters:

Name Type Description Default
name str

Layer name to remove.

required
Source code in anymap_ts/openlayers.py
def remove_cluster_layer(self, name: str) -> None:
    """Remove a cluster layer.

    Args:
        name: Layer name to remove.
    """
    self.remove_layer(name)

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/openlayers.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_draw_control(self)

Remove the drawing interaction.

Source code in anymap_ts/openlayers.py
def remove_draw_control(self) -> None:
    """Remove the drawing interaction."""
    self.call_js_method("removeDrawControl")

remove_graticule(self)

Remove the graticule overlay.

Source code in anymap_ts/openlayers.py
def remove_graticule(self) -> None:
    """Remove the graticule overlay."""
    self.call_js_method("removeGraticule")

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/openlayers.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_layer_control(self)

Remove the layer control panel.

Source code in anymap_ts/openlayers.py
def remove_layer_control(self) -> None:
    """Remove the layer control panel."""
    self.call_js_method("removeLayerControl")

remove_marker(self, name)

Remove a marker from the map.

Parameters:

Name Type Description Default
name str

Marker identifier to remove.

required
Source code in anymap_ts/openlayers.py
def remove_marker(self, name: str) -> None:
    """Remove a marker from the map.

    Args:
        name: Marker identifier to remove.
    """
    self.call_js_method("removeMarker", name)

remove_measure_control(self)

Remove the measurement tool.

Source code in anymap_ts/openlayers.py
def remove_measure_control(self) -> None:
    """Remove the measurement tool."""
    self.call_js_method("removeMeasureControl")

remove_popup(self)

Remove/close the current popup.

Source code in anymap_ts/openlayers.py
def remove_popup(self) -> None:
    """Remove/close the current popup."""
    self.call_js_method("removePopup")

remove_select_interaction(self)

Remove the select interaction.

Source code in anymap_ts/openlayers.py
def remove_select_interaction(self) -> None:
    """Remove the select interaction."""
    self.call_js_method("removeSelectInteraction")

set_center(self, lng, lat)

Set the map center.

Parameters:

Name Type Description Default
lng float

Longitude.

required
lat float

Latitude.

required
Source code in anymap_ts/openlayers.py
def set_center(self, lng: float, lat: float) -> None:
    """Set the map center.

    Args:
        lng: Longitude.
        lat: Latitude.
    """
    self.center = [lng, lat]
    self.call_js_method("setCenter", lng, lat)

set_layer_style(self, layer_id, style)

Update the style of a vector layer.

Parameters:

Name Type Description Default
layer_id str

Layer identifier.

required
style Dict

New style configuration dict.

required
Source code in anymap_ts/openlayers.py
def set_layer_style(self, layer_id: str, style: Dict) -> None:
    """Update the style of a vector layer.

    Args:
        layer_id: Layer identifier.
        style: New style configuration dict.
    """
    self.call_js_method("setLayerStyle", layer_id, style=style)

set_layer_z_index(self, layer_id, z_index)

Set the z-index (draw order) of a layer.

Parameters:

Name Type Description Default
layer_id str

Layer identifier.

required
z_index int

Z-index value (higher = drawn on top).

required
Source code in anymap_ts/openlayers.py
def set_layer_z_index(self, layer_id: str, z_index: int) -> None:
    """Set the z-index (draw order) of a layer.

    Args:
        layer_id: Layer identifier.
        z_index: Z-index value (higher = drawn on top).
    """
    self.call_js_method("setLayerZIndex", layer_id, z_index)

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/openlayers.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_rotation(self, rotation)

Set the map rotation.

Parameters:

Name Type Description Default
rotation float

Rotation in radians.

required
Source code in anymap_ts/openlayers.py
def set_rotation(self, rotation: float) -> None:
    """Set the map rotation.

    Args:
        rotation: Rotation in radians.
    """
    self.rotation = rotation
    self.call_js_method("setRotation", rotation)

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/openlayers.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)

set_zoom(self, zoom)

Set the map zoom level.

Parameters:

Name Type Description Default
zoom float

Zoom level.

required
Source code in anymap_ts/openlayers.py
def set_zoom(self, zoom: float) -> None:
    """Set the map zoom level.

    Args:
        zoom: Zoom level.
    """
    self.zoom = zoom
    self.call_js_method("setZoom", zoom)

show_popup(self, lng, lat, content)

Show a 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
Source code in anymap_ts/openlayers.py
def show_popup(
    self,
    lng: float,
    lat: float,
    content: str,
) -> None:
    """Show a popup at a location.

    Args:
        lng: Longitude.
        lat: Latitude.
        content: HTML content for the popup.
    """
    self.call_js_method("showPopup", lng=lng, lat=lat, content=content)