Skip to content

base module

Base MapWidget class for all map implementations.

MapWidget (AnyWidget)

Base class for interactive map widgets.

This class provides the core functionality for Python-JavaScript communication using anywidget's traitlet synchronization system.

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

    This class provides the core functionality for Python-JavaScript communication
    using anywidget's traitlet synchronization system.
    """

    # Synchronized traits for map state
    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("400px").tag(sync=True)
    style = traitlets.Union([traitlets.Unicode(), traitlets.Dict()]).tag(sync=True)

    # JavaScript method call queue
    _js_calls = traitlets.List([]).tag(sync=True)
    _js_method_counter = traitlets.Int(0)

    # Events from JavaScript
    _js_events = traitlets.List([]).tag(sync=True)

    # State persistence for layers, sources, and controls
    _layers = traitlets.Dict({}).tag(sync=True)
    _sources = traitlets.Dict({}).tag(sync=True)
    _controls = traitlets.Dict({}).tag(sync=True)

    # Interaction state
    clicked = traitlets.Dict({}).tag(sync=True)
    current_bounds = traitlets.List([]).tag(sync=True)
    current_center = traitlets.List([]).tag(sync=True)
    current_zoom = traitlets.Float(0.0).tag(sync=True)

    # Drawing data
    _draw_data = traitlets.Dict({}).tag(sync=True)

    def __init__(self, **kwargs):
        """Initialize the MapWidget.

        Args:
            **kwargs: Additional widget arguments
        """
        super().__init__(**kwargs)
        self._event_handlers: Dict[str, List[Callable]] = {}
        self.observe(self._handle_js_events, names=["_js_events"])

    def _handle_js_events(self, change: Dict[str, Any]) -> None:
        """Process events received from JavaScript.

        Args:
            change: Traitlet change dict
        """
        events = change.get("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]:
                    try:
                        handler(event.get("data"))
                    except Exception as e:
                        print(f"Error in event handler for {event_type}: {e}")
        # Clear processed events
        self._js_events = []

    def call_js_method(self, method: str, *args, **kwargs) -> None:
        """Queue a JavaScript method call.

        Args:
            method: Name of the JavaScript method to call
            *args: Positional arguments for the method
            **kwargs: Keyword arguments for the method
        """
        self._js_method_counter += 1
        call = {
            "id": self._js_method_counter,
            "method": method,
            "args": list(args),
            "kwargs": kwargs,
        }
        self._js_calls = self._js_calls + [call]

    def on_map_event(self, event_type: str, handler: Callable) -> None:
        """Register an event handler.

        Args:
            event_type: Type of event (e.g., 'click', 'moveend')
            handler: Callback function to handle the event
        """
        if event_type not in self._event_handlers:
            self._event_handlers[event_type] = []
        self._event_handlers[event_type].append(handler)

    def off_map_event(
        self, event_type: str, handler: Optional[Callable] = None
    ) -> None:
        """Unregister an event handler.

        Args:
            event_type: Type of event
            handler: Specific handler to remove. If None, removes all handlers.
        """
        if event_type in self._event_handlers:
            if handler is None:
                del self._event_handlers[event_type]
            else:
                self._event_handlers[event_type] = [
                    h for h in self._event_handlers[event_type] if h != handler
                ]

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

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

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

        Args:
            zoom: Zoom level
        """
        self.zoom = zoom

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

        Args:
            lng: Longitude
            lat: Latitude
            zoom: Optional zoom level
            duration: Animation duration in milliseconds
        """
        self.call_js_method("flyTo", lng, lat, zoom=zoom, duration=duration)

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

        Args:
            bounds: [west, south, east, north] bounds
            padding: Padding in pixels
            duration: Animation duration in milliseconds
        """
        self.call_js_method("fitBounds", bounds, padding=padding, duration=duration)

    @property
    def viewstate(self) -> Dict[str, Any]:
        """Get current view state."""
        return {
            "center": self.current_center or self.center,
            "zoom": self.current_zoom or self.zoom,
            "bounds": self.current_bounds,
        }

    def _generate_html_template(self) -> str:
        """Generate HTML template for standalone export.

        Override in subclasses for library-specific templates.
        """
        raise NotImplementedError("Subclasses must implement _generate_html_template")

    def to_html(
        self,
        filepath: Optional[Union[str, Path]] = None,
        title: str = "Interactive Map",
    ) -> Optional[str]:
        """Export map to standalone HTML file.

        Args:
            filepath: Path to save the HTML file. If None, returns HTML string.
            title: Title for the HTML page.

        Returns:
            HTML string if filepath is None, otherwise None.
        """
        html = self._generate_html_template()
        html = html.replace("{{title}}", title)

        if filepath:
            Path(filepath).write_text(html, encoding="utf-8")
            return None
        return html

viewstate: Dict[str, Any] property readonly

Get current view state.

__init__(self, **kwargs) special

Initialize the MapWidget.

Parameters:

Name Type Description Default
**kwargs

Additional widget arguments

{}
Source code in anymap_ts/base.py
def __init__(self, **kwargs):
    """Initialize the MapWidget.

    Args:
        **kwargs: Additional widget arguments
    """
    super().__init__(**kwargs)
    self._event_handlers: Dict[str, List[Callable]] = {}
    self.observe(self._handle_js_events, names=["_js_events"])

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

Queue a JavaScript method call.

Parameters:

Name Type Description Default
method str

Name of the JavaScript method to call

required
*args

Positional arguments for the method

()
**kwargs

Keyword arguments for the method

{}
Source code in anymap_ts/base.py
def call_js_method(self, method: str, *args, **kwargs) -> None:
    """Queue a JavaScript method call.

    Args:
        method: Name of the JavaScript method to call
        *args: Positional arguments for the method
        **kwargs: Keyword arguments for the method
    """
    self._js_method_counter += 1
    call = {
        "id": self._js_method_counter,
        "method": method,
        "args": list(args),
        "kwargs": kwargs,
    }
    self._js_calls = self._js_calls + [call]

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

Fit the map to the given bounds.

Parameters:

Name Type Description Default
bounds List[float]

[west, south, east, north] bounds

required
padding int

Padding in pixels

50
duration int

Animation duration in milliseconds

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

    Args:
        bounds: [west, south, east, north] bounds
        padding: Padding in pixels
        duration: Animation duration in milliseconds
    """
    self.call_js_method("fitBounds", bounds, padding=padding, duration=duration)

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

Fly to a location with animation.

Parameters:

Name Type Description Default
lng float

Longitude

required
lat float

Latitude

required
zoom Optional[float]

Optional zoom level

None
duration int

Animation duration in milliseconds

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

    Args:
        lng: Longitude
        lat: Latitude
        zoom: Optional zoom level
        duration: Animation duration in milliseconds
    """
    self.call_js_method("flyTo", lng, lat, zoom=zoom, duration=duration)

off_map_event(self, event_type, handler=None)

Unregister an event handler.

Parameters:

Name Type Description Default
event_type str

Type of event

required
handler Optional[Callable]

Specific handler to remove. If None, removes all handlers.

None
Source code in anymap_ts/base.py
def off_map_event(
    self, event_type: str, handler: Optional[Callable] = None
) -> None:
    """Unregister an event handler.

    Args:
        event_type: Type of event
        handler: Specific handler to remove. If None, removes all handlers.
    """
    if event_type in self._event_handlers:
        if handler is None:
            del self._event_handlers[event_type]
        else:
            self._event_handlers[event_type] = [
                h for h in self._event_handlers[event_type] if h != handler
            ]

on_map_event(self, event_type, handler)

Register an event handler.

Parameters:

Name Type Description Default
event_type str

Type of event (e.g., 'click', 'moveend')

required
handler Callable

Callback function to handle the event

required
Source code in anymap_ts/base.py
def on_map_event(self, event_type: str, handler: Callable) -> None:
    """Register an event handler.

    Args:
        event_type: Type of event (e.g., 'click', 'moveend')
        handler: Callback function to handle the event
    """
    if event_type not in self._event_handlers:
        self._event_handlers[event_type] = []
    self._event_handlers[event_type].append(handler)

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/base.py
def set_center(self, lng: float, lat: float) -> None:
    """Set the map center.

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

set_zoom(self, zoom)

Set the map zoom level.

Parameters:

Name Type Description Default
zoom float

Zoom level

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

    Args:
        zoom: Zoom level
    """
    self.zoom = zoom

to_html(self, filepath=None, title='Interactive Map')

Export map to standalone HTML file.

Parameters:

Name Type Description Default
filepath Optional[Union[str, Path]]

Path to save the HTML file. If None, returns HTML string.

None
title str

Title for the HTML page.

'Interactive Map'

Returns:

Type Description
Optional[str]

HTML string if filepath is None, otherwise None.

Source code in anymap_ts/base.py
def to_html(
    self,
    filepath: Optional[Union[str, Path]] = None,
    title: str = "Interactive Map",
) -> Optional[str]:
    """Export map to standalone HTML file.

    Args:
        filepath: Path to save the HTML file. If None, returns HTML string.
        title: Title for the HTML page.

    Returns:
        HTML string if filepath is None, otherwise None.
    """
    html = self._generate_html_template()
    html = html.replace("{{title}}", title)

    if filepath:
        Path(filepath).write_text(html, encoding="utf-8")
        return None
    return html