Skip to content

base module

Base class for interactive map widgets using anywidget.

This module provides the core MapWidget class that serves as the foundation for all mapping backends in the anymap library. It handles JavaScript communication, state management, and provides common mapping functionality.

MapWidget (AnyWidget)

Base class for interactive map widgets using anywidget.

This class provides the core functionality for creating interactive maps using different JavaScript mapping libraries. It handles communication between Python and JavaScript, manages map state, and provides common mapping operations.

Attributes:

Name Type Description
center

Map center coordinates as [longitude, latitude].

zoom

Map zoom level.

width

Map container width as CSS string.

height

Map container height as CSS string.

style

Map style configuration.

Source code in anymap/base.py
class MapWidget(anywidget.AnyWidget):
    """Base class for interactive map widgets using anywidget.

    This class provides the core functionality for creating interactive maps
    using different JavaScript mapping libraries. It handles communication
    between Python and JavaScript, manages map state, and provides common
    mapping operations.

    Attributes:
        center: Map center coordinates as [longitude, latitude].
        zoom: Map zoom level.
        width: Map container width as CSS string.
        height: Map container height as CSS string.
        style: Map style configuration.
    """

    # Widget traits for communication with JavaScript
    center = traitlets.List([0.0, 0.0]).tag(sync=True)
    zoom = traitlets.Float(2.0).tag(sync=True)
    width = traitlets.Unicode("100%").tag(sync=True)
    height = traitlets.Unicode("600px").tag(sync=True)
    style = traitlets.Unicode("").tag(sync=True)

    # Communication traits
    _js_calls = traitlets.List([]).tag(sync=True)
    _js_events = traitlets.List([]).tag(sync=True)

    # Internal state
    _layers = traitlets.Dict({}).tag(sync=True)
    _sources = traitlets.Dict({}).tag(sync=True)
    _controls = traitlets.Dict({}).tag(sync=True)
    _projection = traitlets.Dict({}).tag(sync=True)
    _terrain = traitlets.Dict({}).tag(sync=True)

    def __init__(self, **kwargs: Any) -> None:
        """Initialize the map widget.

        Args:
            **kwargs: Additional keyword arguments passed to parent class.
        """
        super().__init__(**kwargs)
        self._event_handlers = {}
        self._js_method_counter = 0

    def call_js_method(self, method_name: str, *args: Any, **kwargs: Any) -> None:
        """Call a JavaScript method on the map instance.

        Args:
            method_name: Name of the JavaScript method to call.
            *args: Positional arguments to pass to the method.
            **kwargs: Keyword arguments to pass to the method.
        """
        call_data = {
            "id": self._js_method_counter,
            "method": method_name,
            "args": args,
            "kwargs": kwargs,
        }
        self._js_method_counter += 1

        # Trigger sync by creating new list
        current_calls = list(self._js_calls)
        current_calls.append(call_data)
        self._js_calls = current_calls

    def on_map_event(
        self, event_type: str, callback: Callable[[Dict[str, Any]], None]
    ) -> None:
        """Register a callback for map events.

        Args:
            event_type: Type of event to listen for (e.g., 'click', 'zoom').
            callback: Function to call when the event occurs. Should accept
                     a dictionary containing event data.
        """
        if event_type not in self._event_handlers:
            self._event_handlers[event_type] = []
        self._event_handlers[event_type].append(callback)

    @traitlets.observe("_js_events")
    def _handle_js_events(self, change: Dict[str, Any]) -> None:
        """Handle events from JavaScript.

        Args:
            change: Dictionary containing the change information from traitlets.
        """
        events = change["new"]
        for event in events:
            event_type = event.get("type")
            if event_type in self._event_handlers:
                for handler in self._event_handlers[event_type]:
                    handler(event)

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

        Args:
            lng: Longitude coordinate.
            lat: Latitude coordinate.
        """
        self.center = [lng, lat]

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

        Args:
            zoom: Zoom level (typically 0-20, where higher values show more detail).
        """
        self.zoom = zoom

    def fly_to(
        self, lat: float, lng: float, zoom: Optional[float] = None, **kwargs
    ) -> None:
        """Animate the map to fly to a specific location.

        Args:
            lat: Target latitude coordinate.
            lng: Target longitude coordinate.
            zoom: Optional target zoom level. If None, keeps current zoom.
        """
        options = {"center": [lat, lng], **kwargs}
        if zoom is not None:
            options["zoom"] = zoom
        self.call_js_method("flyTo", options)

    def add_layer(
        self,
        layer_id: str,
        layer_config: Dict[str, Any],
    ) -> None:
        """Add a layer to the map.

        Args:
            layer_id: Unique identifier for the layer.
            layer_config: Dictionary containing layer configuration.
        """
        # Store layer in local state for persistence
        current_layers = dict(self._layers)
        current_layers[layer_id] = layer_config
        self._layers = current_layers

        self.call_js_method("addLayer", layer_config, layer_id)

    def remove_layer(self, layer_id: str) -> None:
        """Remove a layer from the map.

        Args:
            layer_id: Unique identifier of the layer to remove.
        """
        # Remove from local state
        current_layers = dict(self._layers)
        if layer_id in current_layers:
            del current_layers[layer_id]
            self._layers = current_layers

        self.call_js_method("removeLayer", layer_id)

    def add_source(self, source_id: str, source_config: Dict[str, Any]) -> None:
        """Add a data source to the map.

        Args:
            source_id: Unique identifier for the data source.
            source_config: Dictionary containing source configuration.
        """
        # Store source in local state for persistence
        current_sources = dict(self._sources)
        current_sources[source_id] = source_config
        self._sources = current_sources

        self.call_js_method("addSource", source_id, source_config)

    def remove_source(self, source_id: str) -> None:
        """Remove a data source from the map.

        Args:
            source_id: Unique identifier of the source to remove.
        """
        # Remove from local state
        current_sources = dict(self._sources)
        if source_id in current_sources:
            del current_sources[source_id]
            self._sources = current_sources

        self.call_js_method("removeSource", source_id)

    def get_layers(self) -> Dict[str, Any]:
        """Get all layers currently on the map.

        Returns:
            Dictionary mapping layer IDs to their configurations.
        """
        return dict(self._layers)

    def get_sources(self) -> Dict[str, Any]:
        """Get all sources currently on the map.

        Returns:
            Dictionary mapping source IDs to their configurations.
        """
        return dict(self._sources)

    def clear_layers(self) -> None:
        """Clear all layers from the map.

        Removes all layers that have been added to the map.
        """
        layer_ids = list(self._layers.keys())
        for layer_id in layer_ids:
            self.remove_layer(layer_id)

    def clear_sources(self) -> None:
        """Clear all sources from the map.

        Removes all data sources that have been added to the map.
        """
        source_ids = list(self._sources.keys())
        for source_id in source_ids:
            self.remove_source(source_id)

    def clear_all(self) -> None:
        """Clear all layers and sources from the map.

        Removes all layers and data sources from the map.
        """
        self.clear_layers()
        self.clear_sources()

    def to_html(
        self,
        filename: Optional[str] = None,
        title: str = "Anymap Export",
        width: str = "100%",
        height: str = "600px",
        **kwargs: Any,
    ) -> str:
        """Export the map to a standalone HTML file.

        Args:
            filename: Optional filename to save the HTML. If None, returns HTML string.
            title: Title for the HTML page.
            width: Width of the map container as CSS string.
            height: Height of the map container as CSS string.
            **kwargs: Additional arguments passed to the HTML template.

        Returns:
            HTML string content of the exported map.
        """
        # Get the current map state
        map_state = {
            "center": self.center,
            "zoom": self.zoom,
            "width": width,
            "height": height,
            "style": self.style,
            "_layers": dict(self._layers),
            "_sources": dict(self._sources),
            "_controls": dict(self._controls),
            "_terrain": dict(self._terrain),
        }

        # Add class-specific attributes
        if hasattr(self, "style"):
            map_state["style"] = self.style
        if hasattr(self, "bearing"):
            map_state["bearing"] = self.bearing
        if hasattr(self, "pitch"):
            map_state["pitch"] = self.pitch
        if hasattr(self, "antialias"):
            map_state["antialias"] = self.antialias
        if hasattr(self, "access_token"):
            map_state["access_token"] = self.access_token
        if hasattr(self, "_draw_data"):
            map_state["_draw_data"] = dict(self._draw_data)

        # Generate HTML content
        html_content = self._generate_html_template(map_state, title, **kwargs)

        # Save to file if filename is provided
        if filename:
            with open(filename, "w", encoding="utf-8") as f:
                f.write(html_content)

        return html_content

    def _generate_html_template(
        self, map_state: Dict[str, Any], title: str, **kwargs: Any
    ) -> str:
        """Generate the HTML template with map state.

        This method should be overridden by subclasses to provide library-specific templates.

        Args:
            map_state: Dictionary containing the current map state.
            title: Title for the HTML page.
            **kwargs: Additional arguments for template generation.

        Returns:
            HTML string content.

        Raises:
            NotImplementedError: This method must be implemented by subclasses.
        """
        raise NotImplementedError("Subclasses must implement _generate_html_template")

__init__(self, **kwargs) special

Initialize the map widget.

Parameters:

Name Type Description Default
**kwargs Any

Additional keyword arguments passed to parent class.

{}
Source code in anymap/base.py
def __init__(self, **kwargs: Any) -> None:
    """Initialize the map widget.

    Args:
        **kwargs: Additional keyword arguments passed to parent class.
    """
    super().__init__(**kwargs)
    self._event_handlers = {}
    self._js_method_counter = 0

add_layer(self, layer_id, layer_config)

Add a layer to the map.

Parameters:

Name Type Description Default
layer_id str

Unique identifier for the layer.

required
layer_config Dict[str, Any]

Dictionary containing layer configuration.

required
Source code in anymap/base.py
def add_layer(
    self,
    layer_id: str,
    layer_config: Dict[str, Any],
) -> None:
    """Add a layer to the map.

    Args:
        layer_id: Unique identifier for the layer.
        layer_config: Dictionary containing layer configuration.
    """
    # Store layer in local state for persistence
    current_layers = dict(self._layers)
    current_layers[layer_id] = layer_config
    self._layers = current_layers

    self.call_js_method("addLayer", layer_config, layer_id)

add_source(self, source_id, source_config)

Add a data source to the map.

Parameters:

Name Type Description Default
source_id str

Unique identifier for the data source.

required
source_config Dict[str, Any]

Dictionary containing source configuration.

required
Source code in anymap/base.py
def add_source(self, source_id: str, source_config: Dict[str, Any]) -> None:
    """Add a data source to the map.

    Args:
        source_id: Unique identifier for the data source.
        source_config: Dictionary containing source configuration.
    """
    # Store source in local state for persistence
    current_sources = dict(self._sources)
    current_sources[source_id] = source_config
    self._sources = current_sources

    self.call_js_method("addSource", source_id, source_config)

call_js_method(self, method_name, *args, **kwargs)

Call a JavaScript method on the map instance.

Parameters:

Name Type Description Default
method_name str

Name of the JavaScript method to call.

required
*args Any

Positional arguments to pass to the method.

()
**kwargs Any

Keyword arguments to pass to the method.

{}
Source code in anymap/base.py
def call_js_method(self, method_name: str, *args: Any, **kwargs: Any) -> None:
    """Call a JavaScript method on the map instance.

    Args:
        method_name: Name of the JavaScript method to call.
        *args: Positional arguments to pass to the method.
        **kwargs: Keyword arguments to pass to the method.
    """
    call_data = {
        "id": self._js_method_counter,
        "method": method_name,
        "args": args,
        "kwargs": kwargs,
    }
    self._js_method_counter += 1

    # Trigger sync by creating new list
    current_calls = list(self._js_calls)
    current_calls.append(call_data)
    self._js_calls = current_calls

clear_all(self)

Clear all layers and sources from the map.

Removes all layers and data sources from the map.

Source code in anymap/base.py
def clear_all(self) -> None:
    """Clear all layers and sources from the map.

    Removes all layers and data sources from the map.
    """
    self.clear_layers()
    self.clear_sources()

clear_layers(self)

Clear all layers from the map.

Removes all layers that have been added to the map.

Source code in anymap/base.py
def clear_layers(self) -> None:
    """Clear all layers from the map.

    Removes all layers that have been added to the map.
    """
    layer_ids = list(self._layers.keys())
    for layer_id in layer_ids:
        self.remove_layer(layer_id)

clear_sources(self)

Clear all sources from the map.

Removes all data sources that have been added to the map.

Source code in anymap/base.py
def clear_sources(self) -> None:
    """Clear all sources from the map.

    Removes all data sources that have been added to the map.
    """
    source_ids = list(self._sources.keys())
    for source_id in source_ids:
        self.remove_source(source_id)

fly_to(self, lat, lng, zoom=None, **kwargs)

Animate the map to fly to a specific location.

Parameters:

Name Type Description Default
lat float

Target latitude coordinate.

required
lng float

Target longitude coordinate.

required
zoom Optional[float]

Optional target zoom level. If None, keeps current zoom.

None
Source code in anymap/base.py
def fly_to(
    self, lat: float, lng: float, zoom: Optional[float] = None, **kwargs
) -> None:
    """Animate the map to fly to a specific location.

    Args:
        lat: Target latitude coordinate.
        lng: Target longitude coordinate.
        zoom: Optional target zoom level. If None, keeps current zoom.
    """
    options = {"center": [lat, lng], **kwargs}
    if zoom is not None:
        options["zoom"] = zoom
    self.call_js_method("flyTo", options)

get_layers(self)

Get all layers currently on the map.

Returns:

Type Description
Dict[str, Any]

Dictionary mapping layer IDs to their configurations.

Source code in anymap/base.py
def get_layers(self) -> Dict[str, Any]:
    """Get all layers currently on the map.

    Returns:
        Dictionary mapping layer IDs to their configurations.
    """
    return dict(self._layers)

get_sources(self)

Get all sources currently on the map.

Returns:

Type Description
Dict[str, Any]

Dictionary mapping source IDs to their configurations.

Source code in anymap/base.py
def get_sources(self) -> Dict[str, Any]:
    """Get all sources currently on the map.

    Returns:
        Dictionary mapping source IDs to their configurations.
    """
    return dict(self._sources)

on_map_event(self, event_type, callback)

Register a callback for map events.

Parameters:

Name Type Description Default
event_type str

Type of event to listen for (e.g., 'click', 'zoom').

required
callback Callable[[Dict[str, Any]], NoneType]

Function to call when the event occurs. Should accept a dictionary containing event data.

required
Source code in anymap/base.py
def on_map_event(
    self, event_type: str, callback: Callable[[Dict[str, Any]], None]
) -> None:
    """Register a callback for map events.

    Args:
        event_type: Type of event to listen for (e.g., 'click', 'zoom').
        callback: Function to call when the event occurs. Should accept
                 a dictionary containing event data.
    """
    if event_type not in self._event_handlers:
        self._event_handlers[event_type] = []
    self._event_handlers[event_type].append(callback)

remove_layer(self, layer_id)

Remove a layer from the map.

Parameters:

Name Type Description Default
layer_id str

Unique identifier of the layer to remove.

required
Source code in anymap/base.py
def remove_layer(self, layer_id: str) -> None:
    """Remove a layer from the map.

    Args:
        layer_id: Unique identifier of the layer to remove.
    """
    # Remove from local state
    current_layers = dict(self._layers)
    if layer_id in current_layers:
        del current_layers[layer_id]
        self._layers = current_layers

    self.call_js_method("removeLayer", layer_id)

remove_source(self, source_id)

Remove a data source from the map.

Parameters:

Name Type Description Default
source_id str

Unique identifier of the source to remove.

required
Source code in anymap/base.py
def remove_source(self, source_id: str) -> None:
    """Remove a data source from the map.

    Args:
        source_id: Unique identifier of the source to remove.
    """
    # Remove from local state
    current_sources = dict(self._sources)
    if source_id in current_sources:
        del current_sources[source_id]
        self._sources = current_sources

    self.call_js_method("removeSource", source_id)

set_center(self, lng, lat)

Set the map center coordinates.

Parameters:

Name Type Description Default
lng float

Longitude coordinate.

required
lat float

Latitude coordinate.

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

    Args:
        lng: Longitude coordinate.
        lat: Latitude coordinate.
    """
    self.center = [lng, lat]

set_zoom(self, zoom)

Set the map zoom level.

Parameters:

Name Type Description Default
zoom float

Zoom level (typically 0-20, where higher values show more detail).

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

    Args:
        zoom: Zoom level (typically 0-20, where higher values show more detail).
    """
    self.zoom = zoom

to_html(self, filename=None, title='Anymap Export', width='100%', height='600px', **kwargs)

Export the map to a standalone HTML file.

Parameters:

Name Type Description Default
filename Optional[str]

Optional filename to save the HTML. If None, returns HTML string.

None
title str

Title for the HTML page.

'Anymap Export'
width str

Width of the map container as CSS string.

'100%'
height str

Height of the map container as CSS string.

'600px'
**kwargs Any

Additional arguments passed to the HTML template.

{}

Returns:

Type Description
str

HTML string content of the exported map.

Source code in anymap/base.py
def to_html(
    self,
    filename: Optional[str] = None,
    title: str = "Anymap Export",
    width: str = "100%",
    height: str = "600px",
    **kwargs: Any,
) -> str:
    """Export the map to a standalone HTML file.

    Args:
        filename: Optional filename to save the HTML. If None, returns HTML string.
        title: Title for the HTML page.
        width: Width of the map container as CSS string.
        height: Height of the map container as CSS string.
        **kwargs: Additional arguments passed to the HTML template.

    Returns:
        HTML string content of the exported map.
    """
    # Get the current map state
    map_state = {
        "center": self.center,
        "zoom": self.zoom,
        "width": width,
        "height": height,
        "style": self.style,
        "_layers": dict(self._layers),
        "_sources": dict(self._sources),
        "_controls": dict(self._controls),
        "_terrain": dict(self._terrain),
    }

    # Add class-specific attributes
    if hasattr(self, "style"):
        map_state["style"] = self.style
    if hasattr(self, "bearing"):
        map_state["bearing"] = self.bearing
    if hasattr(self, "pitch"):
        map_state["pitch"] = self.pitch
    if hasattr(self, "antialias"):
        map_state["antialias"] = self.antialias
    if hasattr(self, "access_token"):
        map_state["access_token"] = self.access_token
    if hasattr(self, "_draw_data"):
        map_state["_draw_data"] = dict(self._draw_data)

    # Generate HTML content
    html_content = self._generate_html_template(map_state, title, **kwargs)

    # Save to file if filename is provided
    if filename:
        with open(filename, "w", encoding="utf-8") as f:
            f.write(html_content)

    return html_content