Skip to content

map

map

Use Mapyta to generate beatiful interactive maps.

We are standing on the shoulders of giants by using Folium as the underlying mapping library, which provides a Python interface to Leaflet.js and OpenStreetMap tiles.

Provides a high-level API for creating interactive OpenStreetMap visualizations with support for Shapely geometries, GeoPandas DataFrames, emoji/icon markers, text annotations, markdown tooltip text, choropleth coloring, heatmaps, and export to HTML, PNG, and SVG formats.

Examples:

>>> from shapely.geometry import Point, Polygon
>>> from mapyta import Map
>>> m = Map(title="My Map")
>>> m.add_point(Point(4.9, 52.37), marker="πŸ“", tooltip="**Amsterdam**")
>>> m.add_polygon(Polygon([(4.9, 52.3), (5.0, 52.3), (5.0, 52.4), (4.9, 52.4)]))
>>> m.to_html("map.html")

Classes:

  • Map –

    Interactive map builder backed by Folium and OpenStreetMap tiles.

map.Map

Map(
    center: tuple[float, float] | None = None,
    title: str | None = None,
    config: MapConfig | None = None,
    source_crs: str | None = None,
)

Interactive map builder backed by Folium and OpenStreetMap tiles.

Parameters:

  • center (tuple[float, float] | None, default: None ) –

    Map center (latitude, longitude). Auto-fits if None.

  • title (str | None, default: None ) –

    Title rendered at the top of the map.

  • config (MapConfig | None, default: None ) –

    Global map configuration.

  • source_crs (str | None, default: None ) –

    Default source CRS (e.g. "EPSG:28992"). Auto-detects if None.

Examples:

>>> m = Map(title="Demo")
>>> m.add_point(Point(5.0, 52.0), tooltip="**Hello**")
>>> m.to_html("demo.html")
Source code in mapyta/map.py
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
def __init__(
    self,
    center: tuple[float, float] | None = None,
    title: str | None = None,
    config: MapConfig | None = None,
    source_crs: str | None = None,
) -> None:
    self._config = config or MapConfig()
    self._center = center
    self._title = title
    self._source_crs = source_crs
    self._map = self._create_base_map()
    self._bounds: list[tuple[float, float]] = []
    self._feature_groups: dict[str, folium.FeatureGroup] = {}
    self._active_group: folium.FeatureGroup | folium.Map = self._map
    self._colormaps: list[cm.LinearColormap | cm.StepColormap] = []
    self._zoom_controlled_markers: list[dict[str, Any]] = []
    self._zoom_js_injected: bool = False
    self._draw_config: DrawConfig | None = None
    self._draw_injected: bool = False
    self._geojson_features: list[dict] = []
    self._export_button_config: dict[str, Any] | None = None
    self._export_button_injected: bool = False

map.Map.folium_map property

folium_map: Map

Access the underlying Folium Map for advanced customization.

map.Map.add_ant_path

add_ant_path(
    line: LineString | list[Point],
    tooltip: str | RawHTML | None = None,
    popup: str | RawHTML | None = None,
    color: str = "#0000FF",
    pulse_color: str = "#FFFFFF",
    weight: int = 5,
    delay: int = 400,
    dash_array: list[int] | None = None,
    paused: bool = False,
    reverse: bool = False,
    popup_style: PopupStyle | dict[str, Any] | None = None,
) -> Self

Add an animated dashed line (ant path) along a route.

The path is drawn as a marching-ants animation: a dashed line whose gaps appear to travel in the direction of travel.

Parameters:

  • line (LineString | list[Point]) –

    Route geometry. Pass a Shapely LineString or a list of Shapely Point objects as waypoints.

  • tooltip (str | RawHTML | None, default: None ) –

    Markdown tooltip, or RawHTML for pre-formatted HTML.

  • popup (str | RawHTML | None, default: None ) –

    Markdown popup, or RawHTML for pre-formatted HTML.

  • color (str, default: '#0000FF' ) –

    Line colour (CSS hex or name).

  • pulse_color (str, default: '#FFFFFF' ) –

    Colour of the travelling pulse / gap fill.

  • weight (int, default: 5 ) –

    Line width in pixels.

  • delay (int, default: 400 ) –

    Animation step interval in milliseconds. Lower = faster.

  • dash_array (list[int] | None, default: None ) –

    [dash_length, gap_length] pattern in pixels. Defaults to [10, 20] when None.

  • paused (bool, default: False ) –

    Start the animation paused.

  • reverse (bool, default: False ) –

    Reverse the direction of travel.

  • popup_style (PopupStyle | dict[str, Any] | None, default: None ) –

    Popup dimensions.

Returns:

Source code in mapyta/map.py
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
1452
1453
1454
1455
1456
1457
1458
1459
1460
1461
1462
1463
def add_ant_path(  # noqa: PLR0913
    self,
    line: LineString | list[Point],
    tooltip: str | RawHTML | None = None,
    popup: str | RawHTML | None = None,
    color: str = "#0000FF",
    pulse_color: str = "#FFFFFF",
    weight: int = 5,
    delay: int = 400,
    dash_array: list[int] | None = None,
    paused: bool = False,
    reverse: bool = False,
    popup_style: PopupStyle | dict[str, Any] | None = None,
) -> Self:
    """Add an animated dashed line (ant path) along a route.

    The path is drawn as a marching-ants animation: a dashed line
    whose gaps appear to travel in the direction of travel.

    Parameters
    ----------
    line : LineString | list[Point]
        Route geometry.  Pass a Shapely ``LineString`` or a list of
        Shapely ``Point`` objects as waypoints.
    tooltip : str | RawHTML | None
        Markdown tooltip, or ``RawHTML`` for pre-formatted HTML.
    popup : str | RawHTML | None
        Markdown popup, or ``RawHTML`` for pre-formatted HTML.
    color : str
        Line colour (CSS hex or name).
    pulse_color : str
        Colour of the travelling pulse / gap fill.
    weight : int
        Line width in pixels.
    delay : int
        Animation step interval in milliseconds.  Lower = faster.
    dash_array : list[int] | None
        ``[dash_length, gap_length]`` pattern in pixels.
        Defaults to ``[10, 20]`` when ``None``.
    paused : bool
        Start the animation paused.
    reverse : bool
        Reverse the direction of travel.
    popup_style : PopupStyle | dict[str, Any] | None
        Popup dimensions.

    Returns
    -------
    Map
    """
    if isinstance(line, LineString):
        transformed = cast(LineString, self._transform(line))
        self._extend_bounds(transformed)
        locations = [(c[1], c[0]) for c in transformed.coords]
        self._record_feature(
            transformed,
            {"color": color, "tooltip": self._raw_text(tooltip), "popup": self._raw_text(popup)},
        )
    else:
        locations = []
        for pt in line:
            t = cast(Point, self._transform(pt))
            self._extend_bounds(t)
            locations.append((t.y, t.x))
            self._record_feature(t, {"color": color})

    kwargs: dict[str, Any] = {
        "color": color,
        "pulseColor": pulse_color,
        "weight": weight,
        "delay": delay,
        "paused": paused,
        "reverse": reverse,
    }
    if dash_array is not None:
        kwargs["dashArray"] = dash_array

    folium.plugins.AntPath(
        locations=locations,
        tooltip=self._make_tooltip(tooltip),
        popup=self._make_popup(popup, popup_style),
        **kwargs,
    ).add_to(self._target())
    return self

map.Map.add_choropleth

add_choropleth(
    geojson_data: dict | str | Path,
    value_column: str,
    key_on: str,
    values: dict[str, float | str] | None = None,
    vmin: float | None = None,
    vmax: float | None = None,
    legend_name: str | None = None,
    nan_fill_color: str = "#cccccc",
    nan_fill_opacity: float = 0.4,
    line_weight: float = 1.0,
    line_opacity: float = 0.5,
    fill_opacity: float = 0.7,
    hover_fields: list[str] | None = None,
    colors: list[str] | str | None = None,
    categorical: bool | None = None,
) -> Self

Add a choropleth (color-coded) layer.

Parameters:

  • geojson_data (dict | str | Path) –

    GeoJSON FeatureCollection.

  • value_column (str) –

    Property name with numeric or categorical values.

  • key_on (str) –

    Join key, e.g. "feature.properties.id".

  • values (dict[str, float | str] | None, default: None ) –

    Key -> value mapping. Reads from properties if None.

  • vmin (float | None, default: None ) –

    Color scale range for numeric data. Auto-calculated if None.

  • vmax (float | None, default: None ) –

    Color scale range for numeric data. Auto-calculated if None.

  • legend_name (str | None, default: None ) –

    Legend label.

  • nan_fill_color (str, default: '#cccccc' ) –

    Color for missing values.

  • nan_fill_opacity (float, default: 0.4 ) –

    Opacity for missing values.

  • line_weight (float, default: 1.0 ) –

    Styling parameters.

  • line_opacity (float, default: 1.0 ) –

    Styling parameters.

  • fill_opacity (float, default: 1.0 ) –

    Styling parameters.

  • hover_fields (list[str] | None, default: None ) –

    Tooltip property fields.

  • colors (list[str] | str | None, default: None ) –

    Color palette. Pass a palette name (e.g. "blues", "viridis") or a list of hex color strings. See mapyta.PALETTES for available names. Defaults to "ylrd" (yellow β†’ red).

  • categorical (bool | None, default: None ) –

    Force categorical mode (True), numeric mode (False), or auto-detect from values (None). In categorical mode, each unique value gets a distinct color from the palette.

Returns:

Source code in mapyta/map.py
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
def add_choropleth(  # noqa: C901, PLR0913, PLR0912, PLR0915
    self,
    geojson_data: dict | str | Path,
    value_column: str,
    key_on: str,
    values: dict[str, float | str] | None = None,
    vmin: float | None = None,
    vmax: float | None = None,
    legend_name: str | None = None,
    nan_fill_color: str = "#cccccc",
    nan_fill_opacity: float = 0.4,
    line_weight: float = 1.0,
    line_opacity: float = 0.5,
    fill_opacity: float = 0.7,
    hover_fields: list[str] | None = None,
    colors: list[str] | str | None = None,
    categorical: bool | None = None,
) -> Self:
    """Add a choropleth (color-coded) layer.

    Parameters
    ----------
    geojson_data : dict | str | Path
        GeoJSON FeatureCollection.
    value_column : str
        Property name with numeric or categorical values.
    key_on : str
        Join key, e.g. ``"feature.properties.id"``.
    values : dict[str, float | str] | None
        Key -> value mapping. Reads from properties if ``None``.
    vmin, vmax : float | None
        Color scale range for numeric data. Auto-calculated if ``None``.
    legend_name : str | None
        Legend label.
    nan_fill_color : str
        Color for missing values.
    nan_fill_opacity : float
        Opacity for missing values.
    line_weight, line_opacity, fill_opacity : float
        Styling parameters.
    hover_fields : list[str] | None
        Tooltip property fields.
    colors : list[str] | str | None
        Color palette. Pass a palette name (e.g. ``"blues"``, ``"viridis"``) or
        a list of hex color strings. See ``mapyta.PALETTES`` for available names.
        Defaults to ``"ylrd"`` (yellow β†’ red).
    categorical : bool | None
        Force categorical mode (``True``), numeric mode (``False``), or auto-detect
        from values (``None``). In categorical mode, each unique value gets a
        distinct color from the palette.

    Returns
    -------
    Map
    """
    geojson_data = load_geojson_input(geojson_data)
    if geojson_data.get("type") == "FeatureCollection":
        self._geojson_features.extend(geojson_data.get("features", []))
    elif geojson_data.get("type") == "Feature":
        self._geojson_features.append(geojson_data)

    # Extract values if not provided
    if values is None:
        values = {}
        key_parts = key_on.split(".")
        for feat in geojson_data.get("features", []):
            obj = feat
            for part in key_parts[1:]:
                obj = obj.get(part, {})
            key = obj if isinstance(obj, str) else str(obj)
            raw_val = feat.get("properties", {}).get(value_column)
            if raw_val is not None:
                values[key] = raw_val

    # Determine if categorical
    raw_vals = list(values.values())
    is_categorical = categorical if categorical is not None else any(isinstance(v, str) for v in raw_vals)

    caption = legend_name or value_column

    if is_categorical:
        # Build discrete color mapping per unique category
        categories = list(dict.fromkeys(str(v) for v in raw_vals))
        palette_colors: list[str]
        if isinstance(colors, str):
            palette_colors = PALETTES.get(colors) or []
            if not palette_colors:
                valid = ", ".join(f'"{k}"' for k in sorted(PALETTES))
                raise ValueError(f"Unknown palette {colors!r}. Available palettes: {valid}")
        elif colors is not None:
            if not colors:
                raise ValueError("colors list must not be empty")
            palette_colors = colors
        else:
            palette_colors = PALETTES["ylrd"]
        # Cycle colors if more categories than palette entries
        cat_color_map: dict[str, str] = {cat: palette_colors[i % len(palette_colors)] for i, cat in enumerate(categories)}

        # Use the actual cycled colors per category so the legend matches the map
        legend_colors = [cat_color_map[cat] for cat in categories] if categories else palette_colors[:1]
        colormap = cm.StepColormap(
            colors=legend_colors,
            vmin=0,
            vmax=max(len(categories) - 1, 1),
            caption=caption,
        )

        _str_vals = {k: str(v) for k, v in values.items()}
        _cat_map = cat_color_map

        def style_fn(feature: dict) -> dict:
            obj = feature
            for part in key_on.split(".")[1:]:
                obj = obj.get(part, {})
            key = obj if isinstance(obj, str) else str(obj)
            val_str = _str_vals.get(key)
            fill_color = _cat_map.get(val_str, nan_fill_color) if val_str is not None else nan_fill_color
            return {"fillColor": fill_color, "color": "#333", "weight": line_weight, "fillOpacity": fill_opacity, "opacity": line_opacity}

    else:
        # Numeric mode
        num_vals: list[float] = []
        for v in raw_vals:
            if isinstance(v, str):
                continue
            try:
                num_vals.append(float(v))
            except (TypeError, ValueError) as exc:
                raise ValueError(f"Non-numeric value {v!r} in choropleth values cannot be converted to float") from exc
        if num_vals:
            vmin = vmin if vmin is not None else min(num_vals)
            vmax = vmax if vmax is not None else max(num_vals)

        colormap = self._build_colormap(
            colors=colors,
            vmin=vmin if vmin is not None else 0,
            vmax=vmax if vmax is not None else 1,
            caption=caption,
        )

        _num_vals = {k: float(v) for k, v in values.items() if not isinstance(v, str)}
        _cmap = colormap

        def style_fn(feature: dict) -> dict:
            obj = feature
            for part in key_on.split(".")[1:]:
                obj = obj.get(part, {})
            key = obj if isinstance(obj, str) else str(obj)
            val = _num_vals.get(key)
            if val is not None:
                return {"fillColor": _cmap(val), "color": "#333", "weight": line_weight, "fillOpacity": fill_opacity, "opacity": line_opacity}
            return {"fillColor": nan_fill_color, "color": "#333", "weight": line_weight, "fillOpacity": nan_fill_opacity, "opacity": line_opacity}

    tooltip = folium.GeoJsonTooltip(fields=hover_fields, localize=True) if hover_fields else None
    layer = folium.GeoJson(
        geojson_data,
        style_function=style_fn,
        highlight_function=lambda _: {"weight": 3, "fillOpacity": min(fill_opacity + 0.15, 1.0)},
        tooltip=tooltip,
    )
    layer.add_to(self._target())
    colormap.add_to(self._map)
    self._colormaps.append(colormap)

    try:
        layer_bounds = layer.get_bounds()
        if layer_bounds:
            self._bounds.extend(cast(list[tuple[float, float]], layer_bounds))
    except Exception:
        pass
    return self

map.Map.add_circle

add_circle(
    point: Point,
    tooltip: str | RawHTML | None = None,
    popup: str | RawHTML | None = None,
    style: CircleStyle | dict[str, Any] | None = None,
    min_zoom: int | None = None,
    popup_style: PopupStyle | dict[str, Any] | None = None,
    tooltip_style: TooltipStyle | dict[str, Any] | None = None,
) -> Self

Add a circle marker (fixed pixel size).

Parameters:

  • point (Point) –

    Shapely Point.

  • tooltip (str | RawHTML | None, default: None ) –

    Markdown tooltip, or RawHTML for pre-formatted HTML.

  • popup (str | RawHTML | None, default: None ) –

    Markdown popup, or RawHTML for pre-formatted HTML.

  • style (CircleStyle | dict[str, Any] | None, default: None ) –

    Circle appearance.

  • min_zoom (int | None, default: None ) –

    Minimum zoom level at which the marker is visible.

  • popup_style (PopupStyle | dict[str, Any] | None, default: None ) –

    Popup dimensions.

  • tooltip_style (TooltipStyle | dict[str, Any] | None, default: None ) –

    Tooltip appearance.

Returns:

Source code in mapyta/map.py
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
def add_circle(
    self,
    point: Point,
    tooltip: str | RawHTML | None = None,
    popup: str | RawHTML | None = None,
    style: CircleStyle | dict[str, Any] | None = None,
    min_zoom: int | None = None,
    popup_style: PopupStyle | dict[str, Any] | None = None,
    tooltip_style: TooltipStyle | dict[str, Any] | None = None,
) -> Self:
    """Add a circle marker (fixed pixel size).

    Parameters
    ----------
    point : Point
        Shapely Point.
    tooltip : str | RawHTML | None
        Markdown tooltip, or ``RawHTML`` for pre-formatted HTML.
    popup : str | RawHTML | None
        Markdown popup, or ``RawHTML`` for pre-formatted HTML.
    style : CircleStyle | dict[str, Any] | None
        Circle appearance.
    min_zoom : int | None
        Minimum zoom level at which the marker is visible.
    popup_style : PopupStyle | dict[str, Any] | None
        Popup dimensions.
    tooltip_style : TooltipStyle | dict[str, Any] | None
        Tooltip appearance.

    Returns
    -------
    Map
    """
    point = cast(Point, self._transform(point))
    self._extend_bounds(point)
    cs = resolve_style(style, CircleStyle) or CircleStyle()
    marker = folium.CircleMarker(
        location=[point.y, point.x],
        radius=cs.radius,
        color=cs.stroke.color,
        weight=cs.stroke.weight,
        opacity=cs.stroke.opacity,
        fill=True,
        fill_color=cs.fill.color,
        fill_opacity=cs.fill.opacity,
        tooltip=self._make_tooltip(tooltip, tooltip_style),
        popup=self._make_popup(popup, popup_style),
        dash_array=cs.stroke.dash_array,
    )
    marker.add_to(self._target())
    self._record_feature(
        point,
        {
            "radius": cs.radius,
            "stroke_color": cs.stroke.color,
            "stroke_weight": cs.stroke.weight,
            "stroke_opacity": cs.stroke.opacity,
            "stroke_dash_array": cs.stroke.dash_array,
            "fill_color": cs.fill.color,
            "fill_opacity": cs.fill.opacity,
            "tooltip": self._raw_text(tooltip),
            "popup": self._raw_text(popup),
            "min_zoom": min_zoom,
        },
    )
    if min_zoom is not None and min_zoom > 0:
        self._zoom_controlled_markers.append(
            {
                "var_name": marker.get_name(),
                "min_zoom": min_zoom,
            }
        )
    return self

map.Map.add_dataframe

add_dataframe(
    df: Any,
    geometry_col: str = "geometry",
    hover_fields: list[str] | None = None,
    style: dict[str, Any] | None = None,
    highlight: dict[str, Any] | None = None,
) -> Self

Add a Pandas or Polars DataFrame as a GeoJSON layer.

The DataFrame must contain WKT geometry strings in WGS84 (EPSG:4326) in geometry_col. All other columns become GeoJSON Feature properties and are accessible via hover_fields.

Parameters:

  • df (DataFrame | DataFrame) –

    Input DataFrame. GeoPandas GeoDataFrames are not supported; use :meth:from_geodataframe instead.

  • geometry_col (str, default: 'geometry' ) –

    Column name that holds WKT geometry strings. Default "geometry".

  • hover_fields (list[str] | None, default: None ) –

    Property fields to show in the hover tooltip.

  • style (dict[str, Any] | None, default: None ) –

    Style kwargs forwarded to Folium GeoJson (e.g. color, weight).

  • highlight (dict[str, Any] | None, default: None ) –

    Highlight kwargs for mouse-over.

Returns:

Raises:

  • TypeError –

    If df is not a pandas or polars DataFrame.

  • ValueError –

    If geometry_col is missing, the DataFrame is empty, or a WKT string cannot be parsed.

Examples:

>>> import pandas as pd
>>> from mapyta import Map
>>> df = pd.DataFrame({"geometry": ["POINT (4.9 52.37)"], "name": ["Amsterdam"]})
>>> m = Map().add_dataframe(df, hover_fields=["name"])
Source code in mapyta/map.py
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
def add_dataframe(
    self,
    df: Any,  # noqa: ANN401
    geometry_col: str = "geometry",
    hover_fields: list[str] | None = None,
    style: dict[str, Any] | None = None,
    highlight: dict[str, Any] | None = None,
) -> Self:
    """Add a Pandas or Polars DataFrame as a GeoJSON layer.

    The DataFrame must contain WKT geometry strings in WGS84 (EPSG:4326) in ``geometry_col``.
    All other columns become GeoJSON Feature properties and are accessible via ``hover_fields``.

    Parameters
    ----------
    df : pandas.DataFrame | polars.DataFrame
        Input DataFrame. GeoPandas GeoDataFrames are not supported; use
        :meth:`from_geodataframe` instead.
    geometry_col : str
        Column name that holds WKT geometry strings. Default ``"geometry"``.
    hover_fields : list[str] | None
        Property fields to show in the hover tooltip.
    style : dict[str, Any] | None
        Style kwargs forwarded to Folium GeoJson (e.g. ``color``, ``weight``).
    highlight : dict[str, Any] | None
        Highlight kwargs for mouse-over.

    Returns
    -------
    Map

    Raises
    ------
    TypeError
        If ``df`` is not a pandas or polars DataFrame.
    ValueError
        If ``geometry_col`` is missing, the DataFrame is empty, or a WKT string cannot be parsed.

    Examples
    --------
    >>> import pandas as pd
    >>> from mapyta import Map
    >>> df = pd.DataFrame({"geometry": ["POINT (4.9 52.37)"], "name": ["Amsterdam"]})
    >>> m = Map().add_dataframe(df, hover_fields=["name"])
    """
    from mapyta.dataframe import dataframe_to_geojson  # noqa: PLC0415

    fc = dataframe_to_geojson(df, geometry_col=geometry_col)
    return self.add_geojson(fc, hover_fields=hover_fields, style=style, highlight=highlight)

map.Map.add_export_button

add_export_button(
    label: str = "Download GeoJSON",
    filename: str = "export.geojson",
    position: str = "topright",
) -> Self

Add a download button to the map that exports all features as a GeoJSON file.

When clicked, the button triggers a browser download of a GeoJSON FeatureCollection containing all features added to the map.

Parameters:

  • label (str, default: 'Download GeoJSON' ) –

    Button label text.

  • filename (str, default: 'export.geojson' ) –

    Default filename for the downloaded file.

  • position (str, default: 'topright' ) –

    Leaflet control position: "topleft", "topright", "bottomleft", or "bottomright".

Returns:

Source code in mapyta/map.py
2250
2251
2252
2253
2254
2255
2256
2257
2258
2259
2260
2261
2262
2263
2264
2265
2266
2267
2268
2269
2270
2271
2272
2273
2274
2275
2276
def add_export_button(
    self,
    label: str = "Download GeoJSON",
    filename: str = "export.geojson",
    position: str = "topright",
) -> Self:
    """Add a download button to the map that exports all features as a GeoJSON file.

    When clicked, the button triggers a browser download of a GeoJSON
    ``FeatureCollection`` containing all features added to the map.

    Parameters
    ----------
    label : str
        Button label text.
    filename : str
        Default filename for the downloaded file.
    position : str
        Leaflet control position: ``"topleft"``, ``"topright"``,
        ``"bottomleft"``, or ``"bottomright"``.

    Returns
    -------
    Map
    """
    self._export_button_config = {"label": label, "filename": filename, "position": position}
    return self

map.Map.add_geojson

add_geojson(
    data: dict | str | Path,
    hover_fields: list[str] | None = None,
    style: dict[str, Any] | None = None,
    highlight: dict[str, Any] | None = None,
) -> Self

Add a GeoJSON layer.

Parameters:

  • data (dict | str | Path) –

    GeoJSON as dict, JSON string, or file path.

  • hover_fields (list[str] | None, default: None ) –

    Property fields for the tooltip.

  • style (dict[str, Any] | None, default: None ) –

    Style kwargs.

  • highlight (dict[str, Any] | None, default: None ) –

    Highlight kwargs for mouse-over.

Returns:

Source code in mapyta/map.py
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
def add_geojson(
    self,
    data: dict | str | Path,
    hover_fields: list[str] | None = None,
    style: dict[str, Any] | None = None,
    highlight: dict[str, Any] | None = None,
) -> Self:
    """Add a GeoJSON layer.

    Parameters
    ----------
    data : dict | str | Path
        GeoJSON as dict, JSON string, or file path.
    hover_fields : list[str] | None
        Property fields for the tooltip.
    style : dict[str, Any] | None
        Style kwargs.
    highlight : dict[str, Any] | None
        Highlight kwargs for mouse-over.

    Returns
    -------
    Map
    """
    data = load_geojson_input(data)
    if data.get("type") == "FeatureCollection":
        self._geojson_features.extend(data.get("features", []))
    elif data.get("type") == "Feature":
        self._geojson_features.append(data)

    ds = {"color": "#3388ff", "weight": 2, "fillOpacity": 0.2}
    if style:
        ds.update(style)
    dh = {"weight": 5, "fillOpacity": 0.4}
    if highlight:
        dh.update(highlight)

    tooltip = folium.GeoJsonTooltip(fields=hover_fields, localize=True) if hover_fields else None

    layer = folium.GeoJson(
        data,
        style_function=lambda _: ds,
        highlight_function=lambda _: dh,
        tooltip=tooltip,
    )
    layer.add_to(self._target())
    try:
        layer_bounds = layer.get_bounds()
        if layer_bounds:
            self._bounds.extend(cast(list[tuple[float, float]], layer_bounds))
    except Exception:
        pass
    return self

map.Map.add_geometry

add_geometry(
    geom: BaseGeometry,
    tooltip: str | RawHTML | None = None,
    popup: str | RawHTML | None = None,
    label: str | None = None,
    stroke: StrokeStyle | dict[str, Any] | None = None,
    fill: FillStyle | dict[str, Any] | None = None,
    marker_style: dict[str, str] | None = None,
    popup_style: PopupStyle | dict[str, Any] | None = None,
    min_zoom: int | None = None,
    tooltip_style: TooltipStyle | dict[str, Any] | None = None,
) -> Self

Add any Shapely geometry (auto-dispatches by type).

Parameters:

  • geom (BaseGeometry) –

    Any supported Shapely geometry.

  • tooltip (str | RawHTML | None, default: None ) –

    Style and interaction parameters.

  • popup (str | RawHTML | None, default: None ) –

    Style and interaction parameters.

  • label (str | RawHTML | None, default: None ) –

    Style and interaction parameters.

  • stroke (str | RawHTML | None, default: None ) –

    Style and interaction parameters.

  • fill (str | RawHTML | None, default: None ) –

    Style and interaction parameters.

  • marker_style (str | RawHTML | None, default: None ) –

    Style and interaction parameters.

  • popup_style (str | RawHTML | None, default: None ) –

    Style and interaction parameters.

  • tooltip_style (str | RawHTML | None, default: None ) –

    Style and interaction parameters.

  • min_zoom (int | None, default: None ) –

    Minimum zoom level at which the geometry is visible.

Returns:

Raises:

  • TypeError –

    If geometry type is unsupported.

Source code in mapyta/map.py
 940
 941
 942
 943
 944
 945
 946
 947
 948
 949
 950
 951
 952
 953
 954
 955
 956
 957
 958
 959
 960
 961
 962
 963
 964
 965
 966
 967
 968
 969
 970
 971
 972
 973
 974
 975
 976
 977
 978
 979
 980
 981
 982
 983
 984
 985
 986
 987
 988
 989
 990
 991
 992
 993
 994
 995
 996
 997
 998
 999
1000
def add_geometry(
    self,
    geom: BaseGeometry,
    tooltip: str | RawHTML | None = None,
    popup: str | RawHTML | None = None,
    label: str | None = None,
    stroke: StrokeStyle | dict[str, Any] | None = None,
    fill: FillStyle | dict[str, Any] | None = None,
    marker_style: dict[str, str] | None = None,
    popup_style: PopupStyle | dict[str, Any] | None = None,
    min_zoom: int | None = None,
    tooltip_style: TooltipStyle | dict[str, Any] | None = None,
) -> Self:
    """Add any Shapely geometry (auto-dispatches by type).

    Parameters
    ----------
    geom : BaseGeometry
        Any supported Shapely geometry.
    tooltip, popup, label, stroke, fill, marker_style, popup_style, tooltip_style
        Style and interaction parameters.
    min_zoom : int | None
        Minimum zoom level at which the geometry is visible.

    Returns
    -------
    Map

    Raises
    ------
    TypeError
        If geometry type is unsupported.
    """
    tip = tooltip
    ts = tooltip_style
    if isinstance(geom, Point):
        self.add_point(
            geom, tooltip=tip, popup=popup, marker=label, marker_style=marker_style, popup_style=popup_style, min_zoom=min_zoom, tooltip_style=ts
        )
    elif isinstance(geom, LinearRing):
        # LinearRing is a subclass of LineString, check first
        self.add_linestring(
            LineString(geom.coords), tooltip=tip, popup=popup, stroke=stroke, popup_style=popup_style, min_zoom=min_zoom, tooltip_style=ts
        )
    elif isinstance(geom, LineString):
        self.add_linestring(geom, tooltip=tip, popup=popup, stroke=stroke, popup_style=popup_style, min_zoom=min_zoom, tooltip_style=ts)
    elif isinstance(geom, Polygon):
        self.add_polygon(geom, tooltip=tip, popup=popup, stroke=stroke, fill=fill, popup_style=popup_style, min_zoom=min_zoom, tooltip_style=ts)
    elif isinstance(geom, MultiPolygon):
        self.add_multipolygon(
            geom, tooltip=tip, popup=popup, stroke=stroke, fill=fill, popup_style=popup_style, min_zoom=min_zoom, tooltip_style=ts
        )
    elif isinstance(geom, MultiLineString):
        self.add_multilinestring(geom, tooltip=tip, popup=popup, stroke=stroke, popup_style=popup_style, min_zoom=min_zoom, tooltip_style=ts)
    elif isinstance(geom, MultiPoint):
        self.add_multipoint(
            geom, tooltip=tip, popup=popup, label=label, marker_style=marker_style, popup_style=popup_style, min_zoom=min_zoom, tooltip_style=ts
        )
    else:
        raise TypeError(f"Unsupported geometry type: {type(geom).__name__}")
    return self

map.Map.add_heatmap

add_heatmap(
    points: list[Point]
    | list[tuple[float, float]]
    | list[tuple[float, float, float]],
    style: HeatmapStyle | dict[str, Any] | None = None,
    name: str | None = None,
) -> Self

Add a heatmap layer.

Parameters:

  • points (list[Point] | list[tuple]) –

    Shapely Points (lon, lat) or tuples (lat, lon[, intensity]).

  • style (HeatmapStyle | dict[str, Any] | None, default: None ) –

    Heatmap appearance.

  • name (str | None, default: None ) –

    Layer name.

Returns:

Source code in mapyta/map.py
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
def add_heatmap(
    self,
    points: list[Point] | list[tuple[float, float]] | list[tuple[float, float, float]],
    style: HeatmapStyle | dict[str, Any] | None = None,
    name: str | None = None,
) -> Self:
    """Add a heatmap layer.

    Parameters
    ----------
    points : list[Point] | list[tuple]
        Shapely Points ``(lon, lat)`` or tuples ``(lat, lon[, intensity])``.
    style : HeatmapStyle | dict[str, Any] | None
        Heatmap appearance.
    name : str | None
        Layer name.

    Returns
    -------
    Map
    """
    hs = resolve_style(style, HeatmapStyle) or HeatmapStyle()
    heat_data: list[list[float]] = []
    for p in points:
        if isinstance(p, Point):
            pt = cast(Point, self._transform(p))
            heat_data.append([pt.y, pt.x])
            self._extend_bounds(pt)
            self._record_feature(pt, {})
        elif len(p) == 2:
            heat_data.append([p[0], p[1]])
            self._bounds.append((p[0], p[1]))
            self._record_feature(Point(p[1], p[0]), {})
        else:
            heat_data.append(list(p[:3]))
            self._bounds.append((p[0], p[1]))
            self._record_feature(Point(p[1], p[0]), {"intensity": p[2]})  # type: ignore[index]

    kwargs: dict[str, Any] = {
        "radius": hs.radius,
        "blur": hs.blur,
        "min_opacity": hs.min_opacity,
        "max_zoom": hs.max_zoom,
    }
    if hs.gradient:
        kwargs["gradient"] = hs.gradient

    folium.plugins.HeatMap(heat_data, name=name, **kwargs).add_to(self._target())
    return self

map.Map.add_heatmap_with_time

add_heatmap_with_time(
    data: list[
        list[Point]
        | list[tuple[float, float]]
        | list[tuple[float, float, float]]
    ],
    index: list[str],
    radius: int = 15,
    blur: float = 0.8,
    max_opacity: float = 0.6,
    min_opacity: float = 0.0,
    gradient: dict[float, str] | None = None,
    auto_play: bool = False,
    display_index: bool = True,
    position: str = "bottomleft",
) -> Self

Add a heatmap with a time slider.

Displays a heatmap that changes over time. A playback control lets users step or scrub through the time steps.

Parameters:

  • data (list of timestep point lists) –

    Outer list corresponds to time steps (must match index length). Each inner list is a collection of points for that step, in the same format accepted by :meth:add_heatmap: Shapely Point objects or (lat, lon[, intensity]) tuples.

  • index (list[str]) –

    Labels for each time step shown in the slider (e.g. dates: ["2024-01", "2024-02", ...]).

  • radius (int, default: 15 ) –

    Heatmap point radius in pixels.

  • blur (float, default: 0.8 ) –

    Blur amount (0–1).

  • max_opacity (float, default: 0.6 ) –

    Maximum heatmap opacity (0–1).

  • min_opacity (float, default: 0.0 ) –

    Minimum heatmap opacity (0–1).

  • gradient (dict[float, str] | None, default: None ) –

    Colour gradient mapping density values (0–1) to CSS colours, e.g. {0.0: "blue", 0.5: "yellow", 1.0: "red"}.

  • auto_play (bool, default: False ) –

    Start playback automatically when the map loads.

  • display_index (bool, default: True ) –

    Show the current time step label in the control.

  • position (str, default: 'bottomleft' ) –

    Control position: "bottomleft", "bottomright", "topleft", or "topright".

Returns:

Raises:

  • ValueError –

    If the length of data does not match the length of index.

Source code in mapyta/map.py
1465
1466
1467
1468
1469
1470
1471
1472
1473
1474
1475
1476
1477
1478
1479
1480
1481
1482
1483
1484
1485
1486
1487
1488
1489
1490
1491
1492
1493
1494
1495
1496
1497
1498
1499
1500
1501
1502
1503
1504
1505
1506
1507
1508
1509
1510
1511
1512
1513
1514
1515
1516
1517
1518
1519
1520
1521
1522
1523
1524
1525
1526
1527
1528
1529
1530
1531
1532
1533
1534
1535
1536
1537
1538
1539
1540
1541
1542
1543
1544
1545
1546
1547
1548
1549
1550
1551
1552
1553
1554
1555
def add_heatmap_with_time(
    self,
    data: list[list[Point] | list[tuple[float, float]] | list[tuple[float, float, float]]],
    index: list[str],
    radius: int = 15,
    blur: float = 0.8,
    max_opacity: float = 0.6,
    min_opacity: float = 0.0,
    gradient: dict[float, str] | None = None,
    auto_play: bool = False,
    display_index: bool = True,
    position: str = "bottomleft",
) -> Self:
    """Add a heatmap with a time slider.

    Displays a heatmap that changes over time.  A playback control
    lets users step or scrub through the time steps.

    Parameters
    ----------
    data : list of timestep point lists
        Outer list corresponds to time steps (must match *index*
        length).  Each inner list is a collection of points for that
        step, in the same format accepted by :meth:`add_heatmap`:
        Shapely ``Point`` objects or ``(lat, lon[, intensity])``
        tuples.
    index : list[str]
        Labels for each time step shown in the slider (e.g. dates:
        ``["2024-01", "2024-02", ...]``).
    radius : int
        Heatmap point radius in pixels.
    blur : float
        Blur amount (0–1).
    max_opacity : float
        Maximum heatmap opacity (0–1).
    min_opacity : float
        Minimum heatmap opacity (0–1).
    gradient : dict[float, str] | None
        Colour gradient mapping density values (0–1) to CSS colours,
        e.g. ``{0.0: "blue", 0.5: "yellow", 1.0: "red"}``.
    auto_play : bool
        Start playback automatically when the map loads.
    display_index : bool
        Show the current time step label in the control.
    position : str
        Control position: ``"bottomleft"``, ``"bottomright"``,
        ``"topleft"``, or ``"topright"``.

    Returns
    -------
    Map

    Raises
    ------
    ValueError
        If the length of *data* does not match the length of *index*.
    """
    if len(data) != len(index):
        msg = f"add_heatmap_with_time(): data has {len(data)} time step(s) but index has {len(index)} label(s). They must be equal."
        raise ValueError(msg)

    time_data: list[list[list[float]]] = []
    for timestep in data:
        step_points: list[list[float]] = []
        for p in timestep:
            if isinstance(p, Point):
                pt = cast(Point, self._transform(p))
                self._extend_bounds(pt)
                step_points.append([pt.y, pt.x])
            elif len(p) == 3:
                self._bounds.append((p[0], p[1]))
                step_points.append([p[0], p[1], p[2]])  # type: ignore[index]
            else:
                self._bounds.append((p[0], p[1]))
                step_points.append([p[0], p[1]])
        time_data.append(step_points)

    kwargs: dict[str, Any] = {
        "radius": radius,
        "blur": blur,
        "max_opacity": max_opacity,
        "min_opacity": min_opacity,
        "auto_play": auto_play,
        "display_index": display_index,
        "position": position,
    }
    if gradient:
        kwargs["gradient"] = gradient

    folium.plugins.HeatMapWithTime(time_data, index=index, **kwargs).add_to(self._target())
    return self

map.Map.add_layer_control

add_layer_control(collapsed: bool = True, position: str = 'topright') -> Self

Add a layer control toggle.

Parameters:

  • collapsed (bool, default: True ) –

    Start collapsed.

  • position (str, default: 'topright' ) –

    Control position.

Returns:

Source code in mapyta/map.py
1978
1979
1980
1981
1982
1983
1984
1985
1986
1987
1988
1989
1990
1991
1992
1993
def add_layer_control(self, collapsed: bool = True, position: str = "topright") -> Self:
    """Add a layer control toggle.

    Parameters
    ----------
    collapsed : bool
        Start collapsed.
    position : str
        Control position.

    Returns
    -------
    Map
    """
    folium.LayerControl(collapsed=collapsed, position=position).add_to(self._map)
    return self

map.Map.add_linestring

add_linestring(
    line: LineString,
    tooltip: str | RawHTML | None = None,
    popup: str | RawHTML | None = None,
    stroke: StrokeStyle | dict[str, Any] | None = None,
    popup_style: PopupStyle | dict[str, Any] | None = None,
    min_zoom: int | None = None,
    tooltip_style: TooltipStyle | dict[str, Any] | None = None,
) -> Self

Add a LineString.

Parameters:

  • line (LineString) –

    Shapely LineString.

  • tooltip (str | RawHTML | None, default: None ) –

    Markdown tooltip, or RawHTML for pre-formatted HTML.

  • popup (str | RawHTML | None, default: None ) –

    Markdown popup, or RawHTML for pre-formatted HTML.

  • stroke (StrokeStyle | dict[str, Any] | None, default: None ) –

    Line style.

  • popup_style (PopupStyle | dict[str, Any] | None, default: None ) –

    Popup dimensions.

  • min_zoom (int | None, default: None ) –

    Minimum zoom level at which the line is visible.

  • tooltip_style (TooltipStyle | dict[str, Any] | None, default: None ) –

    Tooltip appearance.

Returns:

Source code in mapyta/map.py
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
def add_linestring(
    self,
    line: LineString,
    tooltip: str | RawHTML | None = None,
    popup: str | RawHTML | None = None,
    stroke: StrokeStyle | dict[str, Any] | None = None,
    popup_style: PopupStyle | dict[str, Any] | None = None,
    min_zoom: int | None = None,
    tooltip_style: TooltipStyle | dict[str, Any] | None = None,
) -> Self:
    """Add a LineString.

    Parameters
    ----------
    line : LineString
        Shapely LineString.
    tooltip : str | RawHTML | None
        Markdown tooltip, or ``RawHTML`` for pre-formatted HTML.
    popup : str | RawHTML | None
        Markdown popup, or ``RawHTML`` for pre-formatted HTML.
    stroke : StrokeStyle | dict[str, Any] | None
        Line style.
    popup_style : PopupStyle | dict[str, Any] | None
        Popup dimensions.
    min_zoom : int | None
        Minimum zoom level at which the line is visible.
    tooltip_style : TooltipStyle | dict[str, Any] | None
        Tooltip appearance.

    Returns
    -------
    Map
    """
    line = cast(LineString, self._transform(line))
    self._extend_bounds(line)
    s = resolve_style(stroke, StrokeStyle) or StrokeStyle()
    locations = [(c[1], c[0]) for c in line.coords]
    layer = folium.PolyLine(
        locations=locations,
        color=s.color,
        weight=s.weight,
        opacity=s.opacity,
        dash_array=s.dash_array,
        tooltip=self._make_tooltip(tooltip, tooltip_style),
        popup=self._make_popup(popup, popup_style),
    )
    layer.add_to(self._target())
    self._record_feature(
        line,
        {
            "stroke_color": s.color,
            "stroke_weight": s.weight,
            "stroke_opacity": s.opacity,
            "stroke_dash_array": s.dash_array,
            "tooltip": self._raw_text(tooltip),
            "popup": self._raw_text(popup),
            "min_zoom": min_zoom,
        },
    )
    if min_zoom is not None and min_zoom > 0:
        self._zoom_controlled_markers.append({"var_name": layer.get_name(), "min_zoom": min_zoom})
    return self

map.Map.add_marker_cluster

add_marker_cluster(
    points: list[Point],
    labels: list[str] | None = None,
    tooltips: list[str] | None = None,
    popups: list[str] | None = None,
    marker_style: dict[str, str] | None = None,
    name: str | None = None,
    min_zoom: int | None = None,
    popup_style: PopupStyle | dict[str, Any] | None = None,
    captions: list[str] | None = None,
    caption_style: dict[str, str] | None = None,
    tooltip_style: TooltipStyle | dict[str, Any] | None = None,
) -> Self

Add clustered markers that group at low zoom.

Parameters:

  • points (list[Point]) –

    Shapely Points.

  • labels (list[str] | None, default: None ) –

    Per-location marker content (icon names or emoji/text).

  • tooltips (list[str] | None, default: None ) –

    Per-location markdown tooltips.

  • popups (list[str] | None, default: None ) –

    Per-location markdown popups.

  • marker_style (dict[str, str] | None, default: None ) –

    CSS property overrides for each marker.

  • name (str | None, default: None ) –

    Layer name.

  • min_zoom (int | None, default: None ) –

    Minimum zoom level at which the cluster is visible.

  • popup_style (PopupStyle | dict[str, Any] | None, default: None ) –

    Popup dimensions.

  • captions (list[str] | None, default: None ) –

    Per-location text annotations placed below each marker.

  • caption_style (dict[str, str] | None, default: None ) –

    CSS property overrides for captions.

  • tooltip_style (TooltipStyle | dict[str, Any] | None, default: None ) –

    Tooltip appearance (font size, width, etc.).

Returns:

Source code in mapyta/map.py
1654
1655
1656
1657
1658
1659
1660
1661
1662
1663
1664
1665
1666
1667
1668
1669
1670
1671
1672
1673
1674
1675
1676
1677
1678
1679
1680
1681
1682
1683
1684
1685
1686
1687
1688
1689
1690
1691
1692
1693
1694
1695
1696
1697
1698
1699
1700
1701
1702
1703
1704
1705
1706
1707
1708
1709
1710
1711
1712
1713
1714
1715
1716
1717
1718
1719
1720
1721
1722
1723
1724
1725
1726
1727
1728
1729
1730
1731
1732
1733
1734
1735
1736
1737
def add_marker_cluster(  # noqa: PLR0913
    self,
    points: list[Point],
    labels: list[str] | None = None,
    tooltips: list[str] | None = None,
    popups: list[str] | None = None,
    marker_style: dict[str, str] | None = None,
    name: str | None = None,
    min_zoom: int | None = None,
    popup_style: PopupStyle | dict[str, Any] | None = None,
    captions: list[str] | None = None,
    caption_style: dict[str, str] | None = None,
    tooltip_style: TooltipStyle | dict[str, Any] | None = None,
) -> Self:
    """Add clustered markers that group at low zoom.

    Parameters
    ----------
    points : list[Point]
        Shapely Points.
    labels : list[str] | None
        Per-location marker content (icon names or emoji/text).
    tooltips : list[str] | None
        Per-location markdown tooltips.
    popups : list[str] | None
        Per-location markdown popups.
    marker_style : dict[str, str] | None
        CSS property overrides for each marker.
    name : str | None
        Layer name.
    min_zoom : int | None
        Minimum zoom level at which the cluster is visible.
    popup_style : PopupStyle | dict[str, Any] | None
        Popup dimensions.
    captions : list[str] | None
        Per-location text annotations placed below each marker.
    caption_style : dict[str, str] | None
        CSS property overrides for ``captions``.
    tooltip_style : TooltipStyle | dict[str, Any] | None
        Tooltip appearance (font size, width, etc.).

    Returns
    -------
    Map
    """
    css = marker_style or {}
    cap_css = {**DEFAULT_MARKER_CAPTION_CSS, **(caption_style or {})}
    cluster = folium.plugins.MarkerCluster(name=name)

    for i, point in enumerate(points):
        pt = cast(Point, self._transform(point))
        self._extend_bounds(pt)
        lat, lon = pt.y, pt.x

        label = labels[i] if labels and i < len(labels) else None
        tip = tooltips[i] if tooltips and i < len(tooltips) else None
        popup = popups[i] if popups and i < len(popups) else None
        txt = captions[i] if captions and i < len(captions) else None

        kind = classify_marker(label) if label else "icon_name"
        if kind == "emoji":
            assert label is not None  # guarded by classify_marker above
            icon = build_text_marker(label, css, txt, cap_css)
        else:
            icon_name = label or "arrow-down"
            icon = build_icon_marker(icon_name, css, txt, cap_css)

        folium.Marker(
            location=[lat, lon],
            icon=icon,
            tooltip=self._make_tooltip(tip, tooltip_style),
            popup=self._make_popup(popup, popup_style),
        ).add_to(cluster)
        self._record_feature(pt, {"marker": label, "caption": txt, "tooltip": tip, "popup": popup, "min_zoom": min_zoom})

    cluster.add_to(self._target())
    if min_zoom is not None and min_zoom > 0:
        self._zoom_controlled_markers.append(
            {
                "var_name": cluster.get_name(),
                "min_zoom": min_zoom,
            }
        )
    return self

map.Map.add_multilinestring

add_multilinestring(
    ml: MultiLineString,
    tooltip: str | RawHTML | None = None,
    popup: str | RawHTML | None = None,
    stroke: StrokeStyle | dict[str, Any] | None = None,
    popup_style: PopupStyle | dict[str, Any] | None = None,
    min_zoom: int | None = None,
    tooltip_style: TooltipStyle | dict[str, Any] | None = None,
) -> Self

Add a MultiLineString.

Parameters:

  • ml (MultiLineString) –

    Shapely MultiLineString.

  • tooltip (str | RawHTML | None, default: None ) –

    See add_linestring.

  • popup (str | RawHTML | None, default: None ) –

    See add_linestring.

  • stroke (str | RawHTML | None, default: None ) –

    See add_linestring.

  • popup_style (str | RawHTML | None, default: None ) –

    See add_linestring.

  • tooltip_style (str | RawHTML | None, default: None ) –

    See add_linestring.

  • min_zoom (int | None, default: None ) –

    Minimum zoom level at which each line is visible.

Returns:

Source code in mapyta/map.py
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
def add_multilinestring(
    self,
    ml: MultiLineString,
    tooltip: str | RawHTML | None = None,
    popup: str | RawHTML | None = None,
    stroke: StrokeStyle | dict[str, Any] | None = None,
    popup_style: PopupStyle | dict[str, Any] | None = None,
    min_zoom: int | None = None,
    tooltip_style: TooltipStyle | dict[str, Any] | None = None,
) -> Self:
    """Add a MultiLineString.

    Parameters
    ----------
    ml : MultiLineString
        Shapely MultiLineString.
    tooltip, popup, stroke, popup_style, tooltip_style
        See ``add_linestring``.
    min_zoom : int | None
        Minimum zoom level at which each line is visible.

    Returns
    -------
    Map
    """
    for line in ml.geoms:
        self.add_linestring(
            line, tooltip=tooltip, popup=popup, stroke=stroke, popup_style=popup_style, min_zoom=min_zoom, tooltip_style=tooltip_style
        )
    return self

map.Map.add_multipoint

add_multipoint(
    mp: MultiPoint,
    tooltip: str | RawHTML | None = None,
    popup: str | RawHTML | None = None,
    label: str | None = None,
    marker_style: dict[str, str] | None = None,
    popup_style: PopupStyle | dict[str, Any] | None = None,
    min_zoom: int | None = None,
    tooltip_style: TooltipStyle | dict[str, Any] | None = None,
) -> Self

Add a MultiPoint.

Parameters:

  • mp (MultiPoint) –

    Shapely MultiPoint.

  • tooltip (str | RawHTML | None, default: None ) –

    See add_point.

  • popup (str | RawHTML | None, default: None ) –

    See add_point.

  • label (str | RawHTML | None, default: None ) –

    See add_point.

  • marker_style (str | RawHTML | None, default: None ) –

    See add_point.

  • popup_style (str | RawHTML | None, default: None ) –

    See add_point.

  • tooltip_style (str | RawHTML | None, default: None ) –

    See add_point.

  • min_zoom (int | None, default: None ) –

    Minimum zoom level at which each point is visible.

Returns:

Source code in mapyta/map.py
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
def add_multipoint(
    self,
    mp: MultiPoint,
    tooltip: str | RawHTML | None = None,
    popup: str | RawHTML | None = None,
    label: str | None = None,
    marker_style: dict[str, str] | None = None,
    popup_style: PopupStyle | dict[str, Any] | None = None,
    min_zoom: int | None = None,
    tooltip_style: TooltipStyle | dict[str, Any] | None = None,
) -> Self:
    """Add a MultiPoint.

    Parameters
    ----------
    mp : MultiPoint
        Shapely MultiPoint.
    tooltip, popup, label, marker_style, popup_style, tooltip_style
        See ``add_point``.
    min_zoom : int | None
        Minimum zoom level at which each point is visible.

    Returns
    -------
    Map
    """
    for pt in mp.geoms:
        self.add_point(
            pt,
            tooltip=tooltip,
            popup=popup,
            marker=label,
            marker_style=marker_style,
            popup_style=popup_style,
            min_zoom=min_zoom,
            tooltip_style=tooltip_style,
        )
    return self

map.Map.add_multipolygon

add_multipolygon(
    mp: MultiPolygon,
    tooltip: str | RawHTML | None = None,
    popup: str | RawHTML | None = None,
    stroke: StrokeStyle | dict[str, Any] | None = None,
    fill: FillStyle | dict[str, Any] | None = None,
    popup_style: PopupStyle | dict[str, Any] | None = None,
    min_zoom: int | None = None,
    tooltip_style: TooltipStyle | dict[str, Any] | None = None,
) -> Self

Add a MultiPolygon.

Parameters:

  • mp (MultiPolygon) –

    Shapely MultiPolygon.

  • tooltip (str | RawHTML | None, default: None ) –

    See add_polygon.

  • popup (str | RawHTML | None, default: None ) –

    See add_polygon.

  • stroke (str | RawHTML | None, default: None ) –

    See add_polygon.

  • fill (str | RawHTML | None, default: None ) –

    See add_polygon.

  • popup_style (str | RawHTML | None, default: None ) –

    See add_polygon.

  • tooltip_style (str | RawHTML | None, default: None ) –

    See add_polygon.

  • min_zoom (int | None, default: None ) –

    Minimum zoom level at which each polygon is visible.

Returns:

Source code in mapyta/map.py
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
def add_multipolygon(
    self,
    mp: MultiPolygon,
    tooltip: str | RawHTML | None = None,
    popup: str | RawHTML | None = None,
    stroke: StrokeStyle | dict[str, Any] | None = None,
    fill: FillStyle | dict[str, Any] | None = None,
    popup_style: PopupStyle | dict[str, Any] | None = None,
    min_zoom: int | None = None,
    tooltip_style: TooltipStyle | dict[str, Any] | None = None,
) -> Self:
    """Add a MultiPolygon.

    Parameters
    ----------
    mp : MultiPolygon
        Shapely MultiPolygon.
    tooltip, popup, stroke, fill, popup_style, tooltip_style
        See ``add_polygon``.
    min_zoom : int | None
        Minimum zoom level at which each polygon is visible.

    Returns
    -------
    Map
    """
    for poly in mp.geoms:
        self.add_polygon(
            poly, tooltip=tooltip, popup=popup, stroke=stroke, fill=fill, popup_style=popup_style, min_zoom=min_zoom, tooltip_style=tooltip_style
        )
    return self

map.Map.add_point

add_point(
    point: Point,
    marker: str | None = None,
    caption: str | None = None,
    tooltip: str | RawHTML | None = None,
    popup: str | RawHTML | None = None,
    marker_style: dict[str, str] | None = None,
    caption_style: dict[str, str] | None = None,
    tooltip_style: TooltipStyle | dict[str, Any] | None = None,
    popup_style: PopupStyle | dict[str, Any] | None = None,
    min_zoom: int | None = None,
) -> Self

Add a location marker.

Parameters:

  • point (Point) –

    Shapely Point (x, y) in source CRS.

  • marker (str | None, default: None ) –

    Desired marker symbol. Rendering path is auto-detected:

    • Bare icon name (e.g. "home") β†’ Glyphicon prefix.
    • Bare FA name (e.g. "fa-arrow-right") β†’ fa-solid prefix.
    • Full CSS class (e.g. "fa-solid fa-house") β†’ used as-is.
    • Emoji / unicode text β†’ rendered as text DivIcon.
    • None β†’ default "arrow-down" icon.
  • caption (str | None, default: None ) –

    Text annotation placed below the marker. Works with any marker type (emoji, icon). Can be styled via caption_style.

  • tooltip (str | RawHTML | None, default: None ) –

    Information shown on mouse tooltip. Markdown supported for strings, or use RawHTML for pre-formatted HTML.

  • popup (str | RawHTML | None, default: None ) –

    Information shown on click. Markdown supported for strings, or use RawHTML for pre-formatted HTML.

  • marker_style (dict[str, str] | None, default: None ) –

    CSS property overrides for the marker element.

  • caption_style (dict[str, str] | None, default: None ) –

    CSS property overrides for the caption.

  • tooltip_style (TooltipStyle | dict[str, Any] | None, default: None ) –

    Tooltip appearance.

  • popup_style (PopupStyle | dict[str, Any] | None, default: None ) –

    Popup dimensions.

  • min_zoom (int | None, default: None ) –

    Minimum zoom level at which the marker is visible. None or 0 means always visible.

Returns:

Source code in mapyta/map.py
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
def add_point(
    self,
    point: Point,
    marker: str | None = None,
    caption: str | None = None,
    tooltip: str | RawHTML | None = None,
    popup: str | RawHTML | None = None,
    marker_style: dict[str, str] | None = None,
    caption_style: dict[str, str] | None = None,
    tooltip_style: TooltipStyle | dict[str, Any] | None = None,
    popup_style: PopupStyle | dict[str, Any] | None = None,
    min_zoom: int | None = None,
) -> Self:
    """Add a location marker.

    Parameters
    ----------
    point : Point
        Shapely Point ``(x, y)`` in source CRS.
    marker : str | None
        Desired marker symbol.  Rendering path is auto-detected:

        - Bare icon name (e.g. ``"home"``) β†’ Glyphicon prefix.
        - Bare FA name (e.g. ``"fa-arrow-right"``) β†’ ``fa-solid`` prefix.
        - Full CSS class (e.g. ``"fa-solid fa-house"``) β†’ used as-is.
        - Emoji / unicode text β†’ rendered as text DivIcon.
        - ``None`` β†’ default ``"arrow-down"`` icon.
    caption : str | None
        Text annotation placed below the marker.  Works with any marker
        type (emoji, icon).  Can be styled via ``caption_style``.
    tooltip : str | RawHTML | None
        Information shown on mouse tooltip.  Markdown supported for
        strings, or use ``RawHTML`` for pre-formatted HTML.
    popup : str | RawHTML | None
        Information shown on click.  Markdown supported for strings,
        or use ``RawHTML`` for pre-formatted HTML.
    marker_style : dict[str, str] | None
        CSS property overrides for the marker element.
    caption_style : dict[str, str] | None
        CSS property overrides for the caption.
    tooltip_style : TooltipStyle | dict[str, Any] | None
        Tooltip appearance.
    popup_style : PopupStyle | dict[str, Any] | None
        Popup dimensions.
    min_zoom : int | None
        Minimum zoom level at which the marker is visible.
        ``None`` or ``0`` means always visible.

    Returns
    -------
    Map
    """
    point = cast(Point, self._transform(point))
    self._extend_bounds(point)
    lat, lon = point.y, point.x

    css = marker_style or {}
    cap_css = {**DEFAULT_MARKER_CAPTION_CSS, **(caption_style or {})}

    kind = classify_marker(marker) if marker else "icon_name"
    if kind == "emoji":
        assert marker is not None  # guarded by classify_marker above
        icon = build_text_marker(marker, css, caption, cap_css)
    else:
        icon_name = marker or "arrow-down"
        icon = build_icon_marker(icon_name, css, caption, cap_css)

    m = folium.Marker(
        location=[lat, lon],
        icon=icon,
        tooltip=self._make_tooltip(tooltip, tooltip_style),
        popup=self._make_popup(popup, popup_style),
    )
    m.add_to(self._target())
    self._record_feature(
        point, {"marker": marker, "caption": caption, "tooltip": self._raw_text(tooltip), "popup": self._raw_text(popup), "min_zoom": min_zoom}
    )

    if min_zoom is not None and min_zoom > 0:
        self._zoom_controlled_markers.append(
            {
                "var_name": m.get_name(),
                "min_zoom": min_zoom,
            }
        )

    return self

map.Map.add_polygon

add_polygon(
    polygon: Polygon,
    tooltip: str | RawHTML | None = None,
    popup: str | RawHTML | None = None,
    stroke: StrokeStyle | dict[str, Any] | None = None,
    fill: FillStyle | dict[str, Any] | None = None,
    popup_style: PopupStyle | dict[str, Any] | None = None,
    min_zoom: int | None = None,
    tooltip_style: TooltipStyle | dict[str, Any] | None = None,
) -> Self

Add a Polygon.

Parameters:

  • polygon (Polygon) –

    Shapely Polygon.

  • tooltip (str | RawHTML | None, default: None ) –

    Markdown tooltip, or RawHTML for pre-formatted HTML.

  • popup (str | RawHTML | None, default: None ) –

    Markdown popup, or RawHTML for pre-formatted HTML.

  • stroke (StrokeStyle | dict[str, Any] | None, default: None ) –

    Border style.

  • fill (FillStyle | dict[str, Any] | None, default: None ) –

    Fill style.

  • popup_style (PopupStyle | dict[str, Any] | None, default: None ) –

    Popup dimensions.

  • min_zoom (int | None, default: None ) –

    Minimum zoom level at which the polygon is visible.

  • tooltip_style (TooltipStyle | dict[str, Any] | None, default: None ) –

    Tooltip appearance.

Returns:

Source code in mapyta/map.py
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
def add_polygon(
    self,
    polygon: Polygon,
    tooltip: str | RawHTML | None = None,
    popup: str | RawHTML | None = None,
    stroke: StrokeStyle | dict[str, Any] | None = None,
    fill: FillStyle | dict[str, Any] | None = None,
    popup_style: PopupStyle | dict[str, Any] | None = None,
    min_zoom: int | None = None,
    tooltip_style: TooltipStyle | dict[str, Any] | None = None,
) -> Self:
    """Add a Polygon.

    Parameters
    ----------
    polygon : Polygon
        Shapely Polygon.
    tooltip : str | RawHTML | None
        Markdown tooltip, or ``RawHTML`` for pre-formatted HTML.
    popup : str | RawHTML | None
        Markdown popup, or ``RawHTML`` for pre-formatted HTML.
    stroke : StrokeStyle | dict[str, Any] | None
        Border style.
    fill : FillStyle | dict[str, Any] | None
        Fill style.
    popup_style : PopupStyle | dict[str, Any] | None
        Popup dimensions.
    min_zoom : int | None
        Minimum zoom level at which the polygon is visible.
    tooltip_style : TooltipStyle | dict[str, Any] | None
        Tooltip appearance.

    Returns
    -------
    Map
    """
    polygon = cast(Polygon, self._transform(polygon))
    self._extend_bounds(polygon)
    s = resolve_style(stroke, StrokeStyle) or StrokeStyle()
    f = resolve_style(fill, FillStyle) or FillStyle()
    exterior = [(c[1], c[0]) for c in polygon.exterior.coords]
    locations: list[list[tuple[float, float]]] = [exterior] + [[(c[1], c[0]) for c in interior.coords] for interior in polygon.interiors]
    layer = folium.Polygon(
        locations=locations,
        color=s.color,
        weight=s.weight,
        opacity=s.opacity,
        dash_array=s.dash_array,
        fill=True,
        fill_color=f.color,
        fill_opacity=f.opacity,
        tooltip=self._make_tooltip(tooltip, tooltip_style),
        popup=self._make_popup(popup, popup_style),
    )
    layer.add_to(self._target())
    self._record_feature(
        polygon,
        {
            "stroke_color": s.color,
            "stroke_weight": s.weight,
            "stroke_opacity": s.opacity,
            "stroke_dash_array": s.dash_array,
            "fill_color": f.color,
            "fill_opacity": f.opacity,
            "tooltip": self._raw_text(tooltip),
            "popup": self._raw_text(popup),
            "min_zoom": min_zoom,
        },
    )
    if min_zoom is not None and min_zoom > 0:
        self._zoom_controlled_markers.append({"var_name": layer.get_name(), "min_zoom": min_zoom})
    return self

map.Map.add_search_control

add_search_control(
    layer_name: str,
    property_name: str,
    placeholder: str = "Search...",
    position: str = "topright",
    zoom: int | None = None,
    geom_type: str = "Point",
) -> Self

Add a search control to find features by property value.

Users can type in the search box to locate and zoom to a matching feature. The layer being searched must have been added to a feature group with the given layer_name.

Parameters:

  • layer_name (str) –

    Name of the feature group to search (as passed to create_feature_group).

  • property_name (str) –

    GeoJSON property name to search on (e.g. "name", "gemeente").

  • placeholder (str, default: 'Search...' ) –

    Placeholder text in the search input box.

  • position (str, default: 'topright' ) –

    Control position: "topleft", "topright", "bottomleft", "bottomright".

  • zoom (int | None, default: None ) –

    Zoom level to use when a result is selected. Uses the map's current zoom if None.

  • geom_type (str, default: 'Point' ) –

    Geometry type of the features being searched: "Point" or "Polygon". Use "Polygon" when searching choropleth or polygon layers.

Returns:

Raises:

  • KeyError –

    If layer_name is not found in the map's feature groups.

Source code in mapyta/map.py
1995
1996
1997
1998
1999
2000
2001
2002
2003
2004
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
2026
2027
2028
2029
2030
2031
2032
2033
2034
2035
2036
2037
2038
2039
2040
2041
2042
2043
2044
2045
2046
2047
2048
2049
2050
def add_search_control(
    self,
    layer_name: str,
    property_name: str,
    placeholder: str = "Search...",
    position: str = "topright",
    zoom: int | None = None,
    geom_type: str = "Point",
) -> Self:
    """Add a search control to find features by property value.

    Users can type in the search box to locate and zoom to a matching feature.
    The layer being searched must have been added to a feature group with the
    given ``layer_name``.

    Parameters
    ----------
    layer_name : str
        Name of the feature group to search (as passed to ``create_feature_group``).
    property_name : str
        GeoJSON property name to search on (e.g. ``"name"``, ``"gemeente"``).
    placeholder : str
        Placeholder text in the search input box.
    position : str
        Control position: ``"topleft"``, ``"topright"``, ``"bottomleft"``, ``"bottomright"``.
    zoom : int | None
        Zoom level to use when a result is selected. Uses the map's current zoom if ``None``.
    geom_type : str
        Geometry type of the features being searched: ``"Point"`` or ``"Polygon"``.
        Use ``"Polygon"`` when searching choropleth or polygon layers.

    Returns
    -------
    Map

    Raises
    ------
    KeyError
        If ``layer_name`` is not found in the map's feature groups.
    """
    if layer_name not in self._feature_groups:
        available = ", ".join(f'"{n}"' for n in self._feature_groups) or "(none)"
        raise KeyError(f"Feature group {layer_name!r} not found. Available groups: {available}")
    layer = self._feature_groups[layer_name]
    search_kwargs: dict[str, Any] = {
        "layer": layer,
        "geom_type": geom_type,
        "placeholder": placeholder,
        "collapsed": False,
        "search_label": property_name,
        "position": position,
    }
    if zoom is not None:
        search_kwargs["search_zoom"] = zoom
    folium.plugins.Search(**search_kwargs).add_to(self._map)
    return self

map.Map.add_text

add_text(
    point: tuple[float, float] | Point,
    text: str,
    style: dict[str, str] | None = None,
    tooltip: str | RawHTML | None = None,
    popup: str | RawHTML | None = None,
    popup_style: PopupStyle | dict[str, Any] | None = None,
    min_zoom: int | None = None,
    tooltip_style: TooltipStyle | dict[str, Any] | None = None,
) -> Self

Add a text marker at a location.

Parameters:

  • point (tuple[float, float] | Point) –

    (lat, lon) tuple or Shapely Point (lon, lat).

  • text (str) –

    Label text.

  • style (dict[str, str] | None, default: None ) –

    CSS property overrides.

  • tooltip (str | RawHTML | None, default: None ) –

    Markdown tooltip, or RawHTML for pre-formatted HTML.

  • popup (str | RawHTML | None, default: None ) –

    Markdown popup, or RawHTML for pre-formatted HTML.

  • popup_style (PopupStyle | dict[str, Any] | None, default: None ) –

    Popup dimensions.

  • tooltip_style (TooltipStyle | dict[str, Any] | None, default: None ) –

    Tooltip appearance.

  • min_zoom (int | None, default: None ) –

    Minimum zoom level at which the text is visible.

Returns:

Source code in mapyta/map.py
1743
1744
1745
1746
1747
1748
1749
1750
1751
1752
1753
1754
1755
1756
1757
1758
1759
1760
1761
1762
1763
1764
1765
1766
1767
1768
1769
1770
1771
1772
1773
1774
1775
1776
1777
1778
1779
1780
1781
1782
1783
1784
1785
1786
1787
1788
1789
1790
1791
1792
1793
1794
1795
1796
1797
1798
1799
1800
1801
1802
1803
1804
1805
1806
1807
1808
1809
1810
1811
1812
1813
1814
1815
1816
1817
1818
def add_text(
    self,
    point: tuple[float, float] | Point,
    text: str,
    style: dict[str, str] | None = None,
    tooltip: str | RawHTML | None = None,
    popup: str | RawHTML | None = None,
    popup_style: PopupStyle | dict[str, Any] | None = None,
    min_zoom: int | None = None,
    tooltip_style: TooltipStyle | dict[str, Any] | None = None,
) -> Self:
    """Add a text marker at a location.

    Parameters
    ----------
    point : tuple[float, float] | Point
        ``(lat, lon)`` tuple or Shapely Point ``(lon, lat)``.
    text : str
        Label text.
    style : dict[str, str] | None
        CSS property overrides.
    tooltip : str | RawHTML | None
        Markdown tooltip, or ``RawHTML`` for pre-formatted HTML.
    popup : str | RawHTML | None
        Markdown popup, or ``RawHTML`` for pre-formatted HTML.
    popup_style : PopupStyle | dict[str, Any] | None
        Popup dimensions.
    tooltip_style : TooltipStyle | dict[str, Any] | None
        Tooltip appearance.
    min_zoom : int | None
        Minimum zoom level at which the text is visible.

    Returns
    -------
    Map
    """
    merged = {**DEFAULT_CAPTION_CSS, "border-radius": "3px", "overflow-wrap": "break-word", **(style or {})}
    if isinstance(point, Point):
        loc = cast(Point, self._transform(point))
        lat, lon = loc.y, loc.x
        self._extend_bounds(loc)
    else:
        lat, lon = point
        self._bounds.append((lat, lon))

    css_str = css_to_style(merged)
    # Estimate icon size from text length and font size so the anchor
    # centers the marker on the coordinate and Leaflet doesn't render a
    # phantom shadow from a zero-sized container.
    fs = int(merged.get("font-size", "12px").replace("px", ""))
    est_w = max(len(text) * fs * 0.65 + 16, 20)
    est_h = fs + 12
    icon = folium.DivIcon(
        html=f'<div style="{css_str}">{text}</div>',
        icon_size="100%",  # type: ignore[arg-type]  # Let CSS control sizing
        icon_anchor=(int(est_w // 2), int(est_h // 2)),
        class_name="",
    )
    marker = folium.Marker(
        location=[lat, lon],
        icon=icon,
        tooltip=self._make_tooltip(tooltip, tooltip_style),
        popup=self._make_popup(popup, popup_style),
    )
    marker.add_to(self._target())
    self._record_feature(
        Point(lon, lat), {"text": text, "tooltip": self._raw_text(tooltip), "popup": self._raw_text(popup), "min_zoom": min_zoom}
    )
    if min_zoom is not None and min_zoom > 0:
        self._zoom_controlled_markers.append(
            {
                "var_name": marker.get_name(),
                "min_zoom": min_zoom,
            }
        )
    return self

map.Map.add_tile_layer

add_tile_layer(
    name: str,
    tiles: str | None = None,
    attribution: str | None = None,
    overlay: bool = False,
) -> Self

Add an additional tile layer.

Parameters:

  • name (str) –

    Display name or TILE_PROVIDERS key.

  • tiles (str | None, default: None ) –

    Tile URL. Looks up name in providers if None.

  • attribution (str | None, default: None ) –

    Tile attribution.

  • overlay (bool, default: False ) –

    Add as overlay vs base layer.

Returns:

Source code in mapyta/map.py
2052
2053
2054
2055
2056
2057
2058
2059
2060
2061
2062
2063
2064
2065
2066
2067
2068
2069
2070
2071
2072
2073
2074
2075
2076
2077
2078
2079
2080
2081
2082
2083
2084
2085
2086
2087
2088
def add_tile_layer(
    self,
    name: str,
    tiles: str | None = None,
    attribution: str | None = None,
    overlay: bool = False,
) -> Self:
    """Add an additional tile layer.

    Parameters
    ----------
    name : str
        Display name or ``TILE_PROVIDERS`` key.
    tiles : str | None
        Tile URL. Looks up ``name`` in providers if ``None``.
    attribution : str | None
        Tile attribution.
    overlay : bool
        Add as overlay vs base layer.

    Returns
    -------
    Map
    """
    provider = TILE_PROVIDERS.get(name.lower())
    display_name = name
    if provider and tiles is None:
        tiles = provider["tiles"]
        attribution = attribution or provider.get("attr")
        display_name = provider.get("name", name)
    folium.TileLayer(
        tiles=tiles or name,
        name=display_name,
        attr=attribution,
        overlay=overlay,
    ).add_to(self._map)
    return self

map.Map.add_timestamped_geojson

add_timestamped_geojson(
    data: dict | str | Path,
    auto_play: bool = True,
    loop: bool = True,
    transition_time: int = 200,
    period: str = "P1D",
    date_options: str = "YYYY-MM-DD HH:mm:ss",
    duration: str | None = None,
) -> Self

Add a GeoJSON layer animated over time.

Each feature in data must carry a times property β€” an array of timestamps (ISO 8601 strings or milliseconds since epoch) with one entry per coordinate in the geometry. A playback control is injected into the map automatically.

Parameters:

  • data (dict | str | Path) –

    GeoJSON FeatureCollection as a Python dict, a JSON string, or a file path. Every feature must have a times property whose length matches its coordinate count.

  • auto_play (bool, default: True ) –

    Start playback when the map loads.

  • loop (bool, default: True ) –

    Loop animation continuously.

  • transition_time (int, default: 200 ) –

    Duration of each frame transition in milliseconds.

  • period (str, default: 'P1D' ) –

    ISO 8601 duration string controlling the slider step (e.g. "P1D" = 1 day, "PT1H" = 1 hour, "PT1M" = 1 minute).

  • date_options (str, default: 'YYYY-MM-DD HH:mm:ss' ) –

    moment.js <https://momentjs.com/docs/#/displaying/>_ format string for the displayed timestamp label.

  • duration (str | None, default: None ) –

    ISO 8601 duration for how long each feature remains visible after its timestamp. None means it stays visible forever once shown.

Returns:

Notes

Supported geometry types: LineString, MultiPoint, MultiLineString, Polygon, MultiPolygon.

Example GeoJSON feature with timestamps::

{
    "type": "Feature",
    "geometry": {
        "type": "LineString",
        "coordinates": [
            [4.90, 52.37],
            [4.91, 52.38],
            [4.92, 52.39],
        ],
    },
    "properties": {
        "times": ["2024-01-01", "2024-01-02", "2024-01-03"],
        "tooltip": "Route segment",
    },
}
Source code in mapyta/map.py
1557
1558
1559
1560
1561
1562
1563
1564
1565
1566
1567
1568
1569
1570
1571
1572
1573
1574
1575
1576
1577
1578
1579
1580
1581
1582
1583
1584
1585
1586
1587
1588
1589
1590
1591
1592
1593
1594
1595
1596
1597
1598
1599
1600
1601
1602
1603
1604
1605
1606
1607
1608
1609
1610
1611
1612
1613
1614
1615
1616
1617
1618
1619
1620
1621
1622
1623
1624
1625
1626
1627
1628
1629
1630
1631
1632
1633
1634
1635
1636
1637
1638
1639
1640
1641
1642
1643
1644
1645
1646
1647
1648
def add_timestamped_geojson(
    self,
    data: dict | str | Path,
    auto_play: bool = True,
    loop: bool = True,
    transition_time: int = 200,
    period: str = "P1D",
    date_options: str = "YYYY-MM-DD HH:mm:ss",
    duration: str | None = None,
) -> Self:
    """Add a GeoJSON layer animated over time.

    Each feature in *data* must carry a ``times`` property β€” an array
    of timestamps (ISO 8601 strings or milliseconds since epoch) with
    one entry per coordinate in the geometry.  A playback control is
    injected into the map automatically.

    Parameters
    ----------
    data : dict | str | Path
        GeoJSON ``FeatureCollection`` as a Python dict, a JSON
        string, or a file path.  Every feature must have a ``times``
        property whose length matches its coordinate count.
    auto_play : bool
        Start playback when the map loads.
    loop : bool
        Loop animation continuously.
    transition_time : int
        Duration of each frame transition in milliseconds.
    period : str
        ISO 8601 duration string controlling the slider step
        (e.g. ``"P1D"`` = 1 day, ``"PT1H"`` = 1 hour,
        ``"PT1M"`` = 1 minute).
    date_options : str
        `moment.js <https://momentjs.com/docs/#/displaying/>`_ format
        string for the displayed timestamp label.
    duration : str | None
        ISO 8601 duration for how long each feature remains visible
        after its timestamp.  ``None`` means it stays visible forever
        once shown.

    Returns
    -------
    Map

    Notes
    -----
    Supported geometry types: ``LineString``, ``MultiPoint``,
    ``MultiLineString``, ``Polygon``, ``MultiPolygon``.

    Example GeoJSON feature with timestamps::

        {
            "type": "Feature",
            "geometry": {
                "type": "LineString",
                "coordinates": [
                    [4.90, 52.37],
                    [4.91, 52.38],
                    [4.92, 52.39],
                ],
            },
            "properties": {
                "times": ["2024-01-01", "2024-01-02", "2024-01-03"],
                "tooltip": "Route segment",
            },
        }
    """
    if isinstance(data, Path):
        raw: dict | str = data.read_text(encoding="utf-8")
    else:
        raw = data

    layer = folium.plugins.TimestampedGeoJson(
        data=raw,
        auto_play=auto_play,
        loop=loop,
        transition_time=transition_time,
        period=period,
        date_options=date_options,
        duration=duration,
    )
    layer.add_to(self._target())

    try:
        bounds = layer.get_bounds()
        if bounds:
            self._bounds.extend(cast(list[tuple[float, float]], bounds))
    except Exception:
        pass

    return self

map.Map.create_feature_group

create_feature_group(name: str, show: bool = True) -> Self

Create a named feature group for layer toggling.

Subsequent add_* calls target this group until changed.

To stop targeting a group and return to the base map, call reset_target().

Parameters:

  • name (str) –

    Display name for layer control.

  • show (bool, default: True ) –

    Visible by default.

Returns:

  • Map –

    Self, for chaining.

Source code in mapyta/map.py
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
def create_feature_group(self, name: str, show: bool = True) -> Self:
    """Create a named feature group for layer toggling.

    Subsequent ``add_*`` calls target this group until changed.

    To stop targeting a group and return to the base map, call ``reset_target()``.

    Parameters
    ----------
    name : str
        Display name for layer control.
    show : bool
        Visible by default.

    Returns
    -------
    Map
        Self, for chaining.
    """
    fg = folium.FeatureGroup(name=name, show=show)
    fg.add_to(self._map)
    self._feature_groups[name] = fg
    self._active_group = fg
    return self

map.Map.enable_draw

enable_draw(
    tools: list[DrawTool] | None = None,
    on_submit: str | RawJS | None = None,
    position: str = "topleft",
    submit_label: str = "Submit",
    draw_style: dict[str, Any] | None = None,
    edit: bool = True,
) -> Self

Enable drawing controls on the map.

Parameters:

  • tools (list[DrawTool] | None, default: None ) –

    Active drawing tools. Valid values: "marker", "polyline", "polygon", "rectangle", "circle". Defaults to ["polyline", "polygon", "marker"].

  • on_submit (str | RawJS | None, default: None ) –

    Callback when the user clicks Submit. None downloads a GeoJSON file; a URL string sends a POST request; a plain string calls window["name"](geojson); a :class:RawJS instance is inlined verbatim.

  • position (str, default: 'topleft' ) –

    Leaflet control position.

  • submit_label (str, default: 'Submit' ) –

    Label for the submit button.

  • draw_style (dict[str, Any] | None, default: None ) –

    shapeOptions override for drawn shapes.

  • edit (bool, default: True ) –

    Whether edit/delete controls are active.

Returns:

Raises:

  • ValueError –

    If tools contains an invalid tool name.

Source code in mapyta/map.py
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
def enable_draw(
    self,
    tools: list[DrawTool] | None = None,
    on_submit: str | RawJS | None = None,
    position: str = "topleft",
    submit_label: str = "Submit",
    draw_style: dict[str, Any] | None = None,
    edit: bool = True,
) -> Self:
    """Enable drawing controls on the map.

    Parameters
    ----------
    tools : list[DrawTool] | None
        Active drawing tools.  Valid values: ``"marker"``,
        ``"polyline"``, ``"polygon"``, ``"rectangle"``, ``"circle"``.
        Defaults to ``["polyline", "polygon", "marker"]``.
    on_submit : str | RawJS | None
        Callback when the user clicks Submit.  ``None`` downloads a
        GeoJSON file; a URL string sends a ``POST`` request; a plain
        string calls ``window["name"](geojson)``; a :class:`RawJS`
        instance is inlined verbatim.
    position : str
        Leaflet control position.
    submit_label : str
        Label for the submit button.
    draw_style : dict[str, Any] | None
        ``shapeOptions`` override for drawn shapes.
    edit : bool
        Whether edit/delete controls are active.

    Returns
    -------
    Map

    Raises
    ------
    ValueError
        If *tools* contains an invalid tool name.
    """
    if tools is None:
        tools = ["polyline", "polygon", "marker"]

    invalid = set(tools) - VALID_DRAW_TOOLS
    if invalid:
        msg = f"Invalid draw tool(s): {', '.join(sorted(invalid))}. Valid: {', '.join(sorted(VALID_DRAW_TOOLS))}"
        raise ValueError(msg)

    self._draw_config = DrawConfig(
        tools=tools,
        on_submit=on_submit,
        position=position,
        submit_label=submit_label,
        draw_style=draw_style,
        edit=edit,
    )
    self._draw_injected = False
    return self

map.Map.from_geodataframe classmethod

from_geodataframe(
    gdf: Any,
    hover_columns: list[str] | None = None,
    popup_columns: list[str] | None = None,
    label_column: str | None = None,
    color_column: str | None = None,
    stroke: StrokeStyle | dict[str, Any] | None = None,
    fill: FillStyle | dict[str, Any] | None = None,
    marker_style: dict[str, str] | None = None,
    title: str | None = None,
    config: MapConfig | None = None,
    legend_name: str | None = None,
    colors: list[str] | str | None = None,
) -> Self

Create a GeoMap from a GeoPandas GeoDataFrame.

Parameters:

  • gdf (GeoDataFrame) –

    GeoDataFrame with a geometry column.

  • hover_columns (list[str] | None, default: None ) –

    Columns for tooltip.

  • popup_columns (list[str] | None, default: None ) –

    Columns for click popup.

  • label_column (str | None, default: None ) –

    Column with emoji/text labels for points.

  • color_column (str | None, default: None ) –

    Numeric column for choropleth coloring.

  • stroke (StrokeStyle | None, default: None ) –

    Default border style.

  • fill (FillStyle | None, default: None ) –

    Default fill style.

  • marker_style (dict[str, str] | None, default: None ) –

    CSS property overrides for location markers.

  • title (str | None, default: None ) –

    Map title.

  • config (MapConfig | None, default: None ) –

    Map configuration.

  • legend_name (str | None, default: None ) –

    Color scale label.

  • colors (list[str] | str | None, default: None ) –

    Color palette for color_column. Pass a palette name (e.g. "blues") or a list of hex color strings. See mapyta.PALETTES for available names. Defaults to "ylrd" (yellow β†’ red).

Returns:

Raises:

  • ImportError –

    If geopandas is not installed.

Source code in mapyta/map.py
1824
1825
1826
1827
1828
1829
1830
1831
1832
1833
1834
1835
1836
1837
1838
1839
1840
1841
1842
1843
1844
1845
1846
1847
1848
1849
1850
1851
1852
1853
1854
1855
1856
1857
1858
1859
1860
1861
1862
1863
1864
1865
1866
1867
1868
1869
1870
1871
1872
1873
1874
1875
1876
1877
1878
1879
1880
1881
1882
1883
1884
1885
1886
1887
1888
1889
1890
1891
1892
1893
1894
1895
1896
1897
1898
1899
1900
1901
1902
1903
1904
1905
1906
1907
1908
1909
1910
1911
1912
1913
1914
1915
1916
1917
1918
1919
1920
1921
1922
1923
1924
1925
1926
1927
1928
1929
1930
1931
1932
1933
1934
1935
1936
1937
1938
1939
1940
1941
1942
1943
1944
1945
1946
1947
1948
1949
1950
1951
1952
1953
1954
1955
1956
1957
1958
1959
1960
1961
1962
1963
1964
1965
1966
1967
1968
1969
1970
1971
1972
@classmethod
def from_geodataframe(  # noqa: PLR0913, C901, PLR0912
    cls,
    gdf: Any,  # noqa: ANN401
    hover_columns: list[str] | None = None,
    popup_columns: list[str] | None = None,
    label_column: str | None = None,
    color_column: str | None = None,
    stroke: StrokeStyle | dict[str, Any] | None = None,
    fill: FillStyle | dict[str, Any] | None = None,
    marker_style: dict[str, str] | None = None,
    title: str | None = None,
    config: MapConfig | None = None,
    legend_name: str | None = None,
    colors: list[str] | str | None = None,
) -> Self:
    """Create a GeoMap from a GeoPandas GeoDataFrame.

    Parameters
    ----------
    gdf : geopandas.GeoDataFrame
        GeoDataFrame with a geometry column.
    hover_columns : list[str] | None
        Columns for tooltip.
    popup_columns : list[str] | None
        Columns for click popup.
    label_column : str | None
        Column with emoji/text labels for points.
    color_column : str | None
        Numeric column for choropleth coloring.
    stroke : StrokeStyle | None
        Default border style.
    fill : FillStyle | None
        Default fill style.
    marker_style : dict[str, str] | None
        CSS property overrides for location markers.
    title : str | None
        Map title.
    config : MapConfig | None
        Map configuration.
    legend_name : str | None
        Color scale label.
    colors : list[str] | str | None
        Color palette for ``color_column``. Pass a palette name (e.g. ``"blues"``)
        or a list of hex color strings. See ``mapyta.PALETTES`` for available names.
        Defaults to ``"ylrd"`` (yellow β†’ red).

    Returns
    -------
    Map

    Raises
    ------
    ImportError
        If geopandas is not installed.
    """
    try:
        import geopandas  # noqa: PLC0415, F401
    except ImportError:
        raise ImportError("from_geodataframe() requires geopandas. Install it with:\n  pip install geopandas") from None

    # Reproject to WGS84 if needed
    if gdf.crs and str(gdf.crs) != "EPSG:4326":
        gdf = gdf.to_crs("EPSG:4326")

    # Validate column references
    available = list(gdf.columns)
    for param_name, cols in [("hover_columns", hover_columns or []), ("popup_columns", popup_columns or [])]:
        missing = [c for c in cols if c not in gdf.columns]
        if missing:
            warnings.warn(
                f"from_geodataframe(): {param_name} contains column(s) not found in the GeoDataFrame: {missing}. Available columns: {available}",
                UserWarning,
                stacklevel=2,
            )
    for param_name, col in [("label_column", label_column), ("color_column", color_column)]:
        if col is not None and col not in gdf.columns:
            warnings.warn(
                f"from_geodataframe(): {param_name}={col!r} not found in the GeoDataFrame. Available columns: {available}",
                UserWarning,
                stacklevel=2,
            )

    m = cls(title=title, config=config)

    # Build colormap
    colormap = None
    if color_column and color_column in gdf.columns:
        vals = gdf[color_column].dropna()
        if len(vals) > 0:
            vmin, vmax = float(vals.min()), float(vals.max())
            colormap = m._build_colormap(
                colors=colors,
                vmin=vmin,
                vmax=vmax,
                caption=legend_name or color_column,
            )
            colormap.add_to(m._map)
            m._colormaps.append(colormap)

    # Iterate rows
    for idx, row in gdf.iterrows():
        geom = row.geometry

        if not isinstance(geom, BaseGeometry):
            continue

        if geom is None or geom.is_empty:
            continue

        # Build tooltip/popup text
        tooltip = None
        if hover_columns:
            parts = [f"**{c}**: {row[c]}" for c in hover_columns if c in row.index]
            tooltip = "\n".join(parts) if parts else None

        popup = None
        if popup_columns:
            parts = [f"**{c}**: {row[c]}" for c in popup_columns if c in row.index]
            popup = "\n".join(parts) if parts else None

        lbl = str(row[label_column]) if label_column and label_column in row.index else None

        # Resolve color
        cur_fill, cur_stroke = fill, stroke
        if colormap and color_column and color_column in row.index:
            val = row[color_column]
            if val is not None and not (isinstance(val, float) and math.isnan(val)):
                c = colormap(float(val))
                _fill_base = fill if isinstance(fill, FillStyle) else FillStyle()
                _stroke_base = stroke if isinstance(stroke, StrokeStyle) else StrokeStyle()
                cur_fill = FillStyle(color=c, opacity=_fill_base.opacity)
                cur_stroke = StrokeStyle(
                    color=c,
                    weight=_stroke_base.weight,
                    opacity=_stroke_base.opacity,
                )

        m.add_geometry(
            geom=geom,
            tooltip=tooltip,
            popup=popup,
            label=lbl,
            stroke=cur_stroke,
            fill=cur_fill,
            marker_style=marker_style,
        )

    return m

map.Map.reset_target

reset_target() -> Self

Reset target to base map (no feature group).

Returns:

Source code in mapyta/map.py
328
329
330
331
332
333
334
335
336
def reset_target(self) -> Self:
    """Reset target to base map (no feature group).

    Returns
    -------
    Map
    """
    self._active_group = self._map
    return self

map.Map.set_bounds

set_bounds(padding: float = 0.0, restrict: bool = False) -> Self

Fit map view to tracked bounds with optional padding and restriction.

Parameters:

  • padding (float, default: 0.0 ) –

    Padding in degrees around the data bounds.

  • restrict (bool, default: False ) –

    If True, prevent the user from panning/zooming beyond the data bounds (sets maxBounds and maxBoundsViscosity).

Returns:

  • Map –

    Self, for chaining.

Source code in mapyta/map.py
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
def set_bounds(self, padding: float = 0.0, restrict: bool = False) -> Self:
    """Fit map view to tracked bounds with optional padding and restriction.

    Parameters
    ----------
    padding : float
        Padding in degrees around the data bounds.
    restrict : bool
        If ``True``, prevent the user from panning/zooming beyond the
        data bounds (sets ``maxBounds`` and ``maxBoundsViscosity``).

    Returns
    -------
    Map
        Self, for chaining.
    """
    if not self._bounds:
        return self

    lats = [b[0] for b in self._bounds]
    lons = [b[1] for b in self._bounds]
    bounds = [
        [min(lats) - padding, min(lons) - padding],
        [max(lats) + padding, max(lons) + padding],
    ]
    self._map.fit_bounds(bounds)

    if restrict:
        self._map.options["maxBounds"] = bounds
        self._map.options["maxBoundsViscosity"] = 1.0

    return self

map.Map.set_feature_group

set_feature_group(name: str) -> Self

Activate an existing feature group.

Parameters:

  • name (str) –

    Feature group name.

Returns:

Raises:

  • KeyError –

    If group does not exist.

Source code in mapyta/map.py
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
def set_feature_group(self, name: str) -> Self:
    """Activate an existing feature group.

    Parameters
    ----------
    name : str
        Feature group name.

    Returns
    -------
    Map

    Raises
    ------
    KeyError
        If group does not exist.
    """
    if name not in self._feature_groups:
        raise KeyError(f"Feature group '{name}' not found. Available: {list(self._feature_groups.keys())}")
    self._active_group = self._feature_groups[name]
    return self

map.Map.to_bytesio

to_bytesio(
    width: int = 1200,
    height: int = 800,
    delay: float = 2.0,
    hide_controls: bool = True,
    scale: float = 1.0,
) -> BytesIO

Export as PNG in a BytesIO buffer.

Parameters:

  • width (int, default: 1200 ) –

    Viewport width.

  • height (int, default: 800 ) –

    Viewport height.

  • delay (float, default: 2.0 ) –

    Tile loading wait time.

  • hide_controls (bool, default: True ) –

    If True, hide Leaflet UI controls.

  • scale (float, default: 1.0 ) –

    Output resolution multiplier. scale=2.0 produces a 2Γ— (high-DPI) image.

Returns:

  • BytesIO –

    Buffer at position 0.

Source code in mapyta/map.py
2397
2398
2399
2400
2401
2402
2403
2404
2405
2406
2407
2408
2409
2410
2411
2412
2413
2414
2415
2416
2417
2418
2419
2420
2421
2422
2423
2424
2425
2426
2427
2428
def to_bytesio(
    self,
    width: int = 1200,
    height: int = 800,
    delay: float = 2.0,
    hide_controls: bool = True,
    scale: float = 1.0,
) -> io.BytesIO:
    """Export as PNG in a BytesIO buffer.

    Parameters
    ----------
    width : int
        Viewport width.
    height : int
        Viewport height.
    delay : float
        Tile loading wait time.
    hide_controls : bool
        If ``True``, hide Leaflet UI controls.
    scale : float
        Output resolution multiplier. ``scale=2.0`` produces a 2Γ— (high-DPI) image.

    Returns
    -------
    io.BytesIO
        Buffer at position 0.
    """
    png = self.to_image(path=None, width=width, height=height, delay=delay, hide_controls=hide_controls, scale=scale)
    buf = io.BytesIO(png)
    buf.seek(0)
    return buf

map.Map.to_geojson

to_geojson(path: None = None) -> dict
to_geojson(path: str | Path) -> Path
to_geojson(path: str | Path | None = None) -> dict | Path

Export tracked features as a GeoJSON FeatureCollection.

Parameters:

  • path (str | Path | None, default: None ) –

    Output file path. When None, the FeatureCollection dict is returned instead of being written to disk.

Returns:

  • dict | Path –

    FeatureCollection dict when path is None, otherwise the resolved output :class:~pathlib.Path.

Source code in mapyta/map.py
2228
2229
2230
2231
2232
2233
2234
2235
2236
2237
2238
2239
2240
2241
2242
2243
2244
2245
2246
2247
2248
def to_geojson(self, path: str | Path | None = None) -> dict | Path:
    """Export tracked features as a GeoJSON FeatureCollection.

    Parameters
    ----------
    path : str | Path | None
        Output file path.  When ``None``, the FeatureCollection dict is
        returned instead of being written to disk.

    Returns
    -------
    dict | Path
        FeatureCollection dict when *path* is ``None``, otherwise the
        resolved output :class:`~pathlib.Path`.
    """
    fc = self._build_geojson_collection()
    if path is None:
        return fc
    out = Path(path)
    out.write_text(json.dumps(fc, indent=2, ensure_ascii=False), encoding="utf-8")
    return out

map.Map.to_html

to_html(path: None = None, open_in_browser: bool = False) -> str
to_html(path: str | Path, open_in_browser: bool = False) -> Path
to_html(
    path: str | Path | None = None, open_in_browser: bool = False
) -> str | Path

Export as standalone HTML.

Parameters:

  • path (str | Path | None, default: None ) –

    Output file path. When None, the full HTML document is returned as a string instead of being written to disk.

  • open_in_browser (bool, default: False ) –

    If True and path is given, open the file in the default browser after saving. Ignored when path is None.

Returns:

  • str | Path –

    HTML string when path is None, otherwise the resolved output :class:~pathlib.Path.

Source code in mapyta/map.py
2196
2197
2198
2199
2200
2201
2202
2203
2204
2205
2206
2207
2208
2209
2210
2211
2212
2213
2214
2215
2216
2217
2218
2219
2220
def to_html(self, path: str | Path | None = None, open_in_browser: bool = False) -> str | Path:
    """Export as standalone HTML.

    Parameters
    ----------
    path : str | Path | None
        Output file path.  When ``None``, the full HTML document is
        returned as a string instead of being written to disk.
    open_in_browser : bool
        If ``True`` and *path* is given, open the file in the default
        browser after saving.  Ignored when *path* is ``None``.

    Returns
    -------
    str | Path
        HTML string when *path* is ``None``, otherwise the resolved
        output :class:`~pathlib.Path`.
    """
    if path is None:
        return self._get_html()
    out = Path(path)
    out.write_text(self._get_standalone_html(), encoding="utf-8")
    if open_in_browser:
        webbrowser.open(out.resolve().as_uri())
    return out

map.Map.to_image

to_image(
    path: None = None,
    width: int = 1200,
    height: int = 800,
    delay: float = 0.5,
    hide_controls: bool = True,
    scale: float = 1.0,
) -> bytes
to_image(
    path: str | Path,
    width: int = 1200,
    height: int = 800,
    delay: float = 0.5,
    hide_controls: bool = True,
    scale: float = 1.0,
) -> Path
to_image(
    path: str | Path | None = None,
    width: int = 1200,
    height: int = 800,
    delay: float = 0.5,
    hide_controls: bool = True,
    scale: float = 1.0,
) -> bytes | Path

Save the map as a PNG image.

Parameters:

  • path (str | Path | None, default: None ) –

    Output path. Returns bytes if None.

  • width (int, default: 1200 ) –

    Viewport width in px.

  • height (int, default: 800 ) –

    Viewport height in px.

  • delay (float, default: 0.5 ) –

    Seconds to wait for tiles.

  • hide_controls (bool, default: True ) –

    If True, inject CSS to hide Leaflet UI controls in the exported image.

  • scale (float, default: 1.0 ) –

    Output resolution multiplier. scale=2.0 produces a 2Γ— (high-DPI) image at width * 2 Γ— height * 2 pixels. Defaults to 1.0.

Returns:

  • bytes | Path –

Raises:

  • ImportError –

    If selenium not installed.

  • RuntimeError –

    If Chrome/chromedriver not found.

Source code in mapyta/map.py
2333
2334
2335
2336
2337
2338
2339
2340
2341
2342
2343
2344
2345
2346
2347
2348
2349
2350
2351
2352
2353
2354
2355
2356
2357
2358
2359
2360
2361
2362
2363
2364
2365
2366
2367
2368
2369
2370
2371
2372
2373
2374
2375
2376
2377
2378
2379
2380
2381
2382
2383
2384
2385
2386
2387
2388
2389
2390
2391
2392
2393
2394
2395
def to_image(
    self,
    path: str | Path | None = None,
    width: int = 1200,
    height: int = 800,
    delay: float = 0.50,
    hide_controls: bool = True,
    scale: float = 1.0,
) -> bytes | Path:
    """Save the map as a PNG image.

    Parameters
    ----------
    path : str | Path | None
        Output path. Returns bytes if ``None``.
    width : int
        Viewport width in px.
    height : int
        Viewport height in px.
    delay : float
        Seconds to wait for tiles.
    hide_controls : bool
        If ``True``, inject CSS to hide Leaflet UI controls in the
        exported image.
    scale : float
        Output resolution multiplier. ``scale=2.0`` produces a 2Γ— (high-DPI)
        image at ``width * 2`` Γ— ``height * 2`` pixels. Defaults to ``1.0``.

    Returns
    -------
    bytes | Path

    Raises
    ------
    ImportError
        If selenium not installed.
    RuntimeError
        If Chrome/chromedriver not found.
    """
    with tempfile.NamedTemporaryFile(suffix=".html", delete=False) as tmp:
        self.to_html(tmp.name)
        tmp_path = tmp.name
    try:
        if hide_controls:
            content = Path(tmp_path).read_text(encoding="utf-8")
            hide_css = "<style>.leaflet-control{display:none !important;}</style>"
            content = content.replace("</head>", f"{hide_css}\n</head>", 1)
            Path(tmp_path).write_text(content, encoding="utf-8")
        png_bytes = capture_screenshot(
            html_path=tmp_path,
            width=width,
            height=height,
            delay=delay,
            scale=scale,
        )
    finally:
        Path(tmp_path).unlink(missing_ok=True)

    if path is None:
        return png_bytes
    out = Path(path)
    out.write_bytes(png_bytes)
    return out

map.Map.to_image_async async

to_image_async(
    path: str | Path | None = None,
    width: int = 1200,
    height: int = 800,
    delay: float = 2.0,
    hide_controls: bool = True,
    scale: float = 1.0,
) -> bytes | Path

Async PNG export (runs Selenium in executor).

Parameters:

  • path (str | Path | None, default: None ) –

    See to_image.

  • width (str | Path | None, default: None ) –

    See to_image.

  • height (str | Path | None, default: None ) –

    See to_image.

  • delay (str | Path | None, default: None ) –

    See to_image.

  • hide_controls (str | Path | None, default: None ) –

    See to_image.

  • scale (str | Path | None, default: None ) –

    See to_image.

Returns:

  • bytes | Path –
Source code in mapyta/map.py
2430
2431
2432
2433
2434
2435
2436
2437
2438
2439
2440
2441
2442
2443
2444
2445
2446
2447
2448
2449
2450
2451
2452
2453
2454
2455
2456
2457
2458
2459
2460
2461
async def to_image_async(
    self,
    path: str | Path | None = None,
    width: int = 1200,
    height: int = 800,
    delay: float = 2.0,
    hide_controls: bool = True,
    scale: float = 1.0,
) -> bytes | Path:
    """Async PNG export (runs Selenium in executor).

    Parameters
    ----------
    path, width, height, delay, hide_controls, scale
        See ``to_image``.

    Returns
    -------
    bytes | Path
    """
    loop = asyncio.get_running_loop()
    return await loop.run_in_executor(
        None,
        lambda: self.to_image(
            path=path,
            width=width,
            height=height,
            delay=delay,
            hide_controls=hide_controls,
            scale=scale,
        ),
    )