Skip to content

map_viewer

map_viewer #

MapViewer — locations plotted as markers on a Leaflet map.

MapViewer #

Bases: Component

locations as markers; labels caption them in order, keys caption a keyed collection.

Built with of, each marker links to its entry's page; given plainly, the markers link nowhere. An entry with no location is left out. A pin marker is the one to point at a place; dot is the one to plot hundreds within a few metres of each other. outline draws a boundary under them all, and an anchors point is drawn faint beside its marker and joined to it by a dashed line — where a capture was assigned against where it happened.

cg.ui.MapViewer(target.sessions._metadata._location)  # one marker per session, labelled by time
cg.ui.MapViewer(frames.location, marker="dot")
cg.ui.MapViewer.of(target.sessions, lambda s: s.location, label=lambda s: s.notes.value)
Source code in capturegraph-lib/capturegraph/ui/map_viewer.py
class MapViewer(Component):
    """``locations`` as markers; ``labels`` caption them in order, keys caption a keyed collection.

    Built with ``of``, each marker links to its entry's page; given plainly,
    the markers link nowhere. An entry with no location is left out. A ``pin``
    marker is the one to point at a place; ``dot`` is the one to plot hundreds
    within a few metres of each other. ``outline`` draws a boundary under them
    all, and an ``anchors`` point is drawn faint beside its marker and joined to
    it by a dashed line — where a capture was assigned against where it happened.

    ```python
    cg.ui.MapViewer(target.sessions._metadata._location)  # one marker per session, labelled by time
    cg.ui.MapViewer(frames.location, marker="dot")
    cg.ui.MapViewer.of(target.sessions, lambda s: s.location, label=lambda s: s.notes.value)
    ```
    """

    locations: list[Location] | Map | Array
    labels: list[str | None] | Array | None = None
    marker: Literal["pin", "dot"] = "pin"
    outline: list[Location] | Array | None = None
    anchors: Array | None = None
    entries: Entries | None = None

    @classmethod
    def of[K, E](
        cls,
        container: Map[K, E] | Array[E],
        location: Callable[[E], Location | None],
        label: Callable[[E], str] | None = None,
        anchor: Callable[[E], Location | None] | None = None,
        marker: Literal["pin", "dot"] = "pin",
        outline: list[Location] | Array | None = None,
    ) -> Self:
        """A map of ``location`` over ``container``'s elements, each linked to its entry's page.

        Without ``label`` the markers of a keyed container are captioned by key;
        with it, by ``label`` — and by the key again wherever ``label`` returns
        ``None``.

        Args:
            container: A ``Map`` or ``Array``, such as ``target.sessions``.
            location: The marker for one element; ``None`` (or ``Missing``)
                leaves that entry out.
            label: The caption for one kept element.
            anchor: The point one kept element is tied to, drawn faint and
                joined to its marker.
            marker: How each element is drawn, ``"pin"`` or ``"dot"``.
            outline: A boundary drawn under every marker.

        ```python
        cg.ui.MapViewer.of(target.sessions, lambda s: s.location)
        cg.ui.MapViewer.of(
            target.sessions, lambda s: s.location, anchor=lambda s: s.target_location
        )
        ```
        """

        def marked(item: E) -> tuple[Location, str | None, Location | None] | None:
            where = location(item)
            if not present(where):
                return None
            tied = None if anchor is None else anchor(item)
            return (
                where,
                label(item) if label is not None else None,
                tied if present(tied) else None,
            )

        marked_items, entries = entries_of(container, marked, cls.__name__)
        items = items_of(marked_items)
        return cls(
            [where for _, _, (where, _, _) in items],
            labels=[
                text if text is not None else (None if key is None else caption(key))
                for _, key, (_, text, _) in items
            ],
            marker=marker,
            outline=outline,
            anchors=None if anchor is None else Array([tied for _, _, (_, _, tied) in items]),
            entries=entries,
        )

    def html(self, render: Render) -> str:
        """The Leaflet stylesheet and script (once per page), a map container, and the markers."""
        items = items_of(self.locations)
        hrefs = item_hrefs(render, self.entries, len(items))
        anchors = list(self.anchors or ())
        markers: list[JSONValue] = []
        for (position, key, location), href in zip(items, hrefs, strict=True):
            text: str | None
            if self.labels is not None:
                text = self.labels[position] if position < len(self.labels) else None
            else:
                text = None if key is None else caption(key)
            tied = anchors[position] if position < len(anchors) else None
            markers.append(
                {
                    "at": [location.latitude, location.longitude],
                    "anchor": None if not present(tied) else [tied.latitude, tied.longitude],
                    "label": text,
                    "href": href,
                }
            )
        identifier = render.identifier("map")
        data: JSONValue = {
            "markers": markers,
            "outline": [[point.latitude, point.longitude] for point in self.outline or ()],
            "dots": self.marker == "dot",
        }
        return (
            render.once(
                "leaflet",
                element("link", {"rel": "stylesheet", "href": render.lib("leaflet/leaflet.css")})
                + element("script", {"src": render.lib("leaflet/leaflet.js")}),
            )
            + element("div", {"class": "cg-map", "id": identifier})
            + element(
                "script",
                None,
                _PLOT
                % {
                    "id": inline_json(identifier),
                    "data": inline_json(data),
                    "tiles": inline_json(TILES),
                    "attribution": inline_json(ATTRIBUTION),
                },
            )
        )

html(render) #

The Leaflet stylesheet and script (once per page), a map container, and the markers.

Source code in capturegraph-lib/capturegraph/ui/map_viewer.py
def html(self, render: Render) -> str:
    """The Leaflet stylesheet and script (once per page), a map container, and the markers."""
    items = items_of(self.locations)
    hrefs = item_hrefs(render, self.entries, len(items))
    anchors = list(self.anchors or ())
    markers: list[JSONValue] = []
    for (position, key, location), href in zip(items, hrefs, strict=True):
        text: str | None
        if self.labels is not None:
            text = self.labels[position] if position < len(self.labels) else None
        else:
            text = None if key is None else caption(key)
        tied = anchors[position] if position < len(anchors) else None
        markers.append(
            {
                "at": [location.latitude, location.longitude],
                "anchor": None if not present(tied) else [tied.latitude, tied.longitude],
                "label": text,
                "href": href,
            }
        )
    identifier = render.identifier("map")
    data: JSONValue = {
        "markers": markers,
        "outline": [[point.latitude, point.longitude] for point in self.outline or ()],
        "dots": self.marker == "dot",
    }
    return (
        render.once(
            "leaflet",
            element("link", {"rel": "stylesheet", "href": render.lib("leaflet/leaflet.css")})
            + element("script", {"src": render.lib("leaflet/leaflet.js")}),
        )
        + element("div", {"class": "cg-map", "id": identifier})
        + element(
            "script",
            None,
            _PLOT
            % {
                "id": inline_json(identifier),
                "data": inline_json(data),
                "tiles": inline_json(TILES),
                "attribution": inline_json(ATTRIBUTION),
            },
        )
    )

of(container, location, label=None, anchor=None, marker='pin', outline=None) classmethod #

A map of location over container's elements, each linked to its entry's page.

Without label the markers of a keyed container are captioned by key; with it, by label — and by the key again wherever label returns None.

Parameters:

Name Type Description Default
container Map[K, E] | Array[E]

A Map or Array, such as target.sessions.

required
location Callable[[E], Location | None]

The marker for one element; None (or Missing) leaves that entry out.

required
label Callable[[E], str] | None

The caption for one kept element.

None
anchor Callable[[E], Location | None] | None

The point one kept element is tied to, drawn faint and joined to its marker.

None
marker Literal['pin', 'dot']

How each element is drawn, "pin" or "dot".

'pin'
outline list[Location] | Array | None

A boundary drawn under every marker.

None
cg.ui.MapViewer.of(target.sessions, lambda s: s.location)
cg.ui.MapViewer.of(
    target.sessions, lambda s: s.location, anchor=lambda s: s.target_location
)
Source code in capturegraph-lib/capturegraph/ui/map_viewer.py
@classmethod
def of[K, E](
    cls,
    container: Map[K, E] | Array[E],
    location: Callable[[E], Location | None],
    label: Callable[[E], str] | None = None,
    anchor: Callable[[E], Location | None] | None = None,
    marker: Literal["pin", "dot"] = "pin",
    outline: list[Location] | Array | None = None,
) -> Self:
    """A map of ``location`` over ``container``'s elements, each linked to its entry's page.

    Without ``label`` the markers of a keyed container are captioned by key;
    with it, by ``label`` — and by the key again wherever ``label`` returns
    ``None``.

    Args:
        container: A ``Map`` or ``Array``, such as ``target.sessions``.
        location: The marker for one element; ``None`` (or ``Missing``)
            leaves that entry out.
        label: The caption for one kept element.
        anchor: The point one kept element is tied to, drawn faint and
            joined to its marker.
        marker: How each element is drawn, ``"pin"`` or ``"dot"``.
        outline: A boundary drawn under every marker.

    ```python
    cg.ui.MapViewer.of(target.sessions, lambda s: s.location)
    cg.ui.MapViewer.of(
        target.sessions, lambda s: s.location, anchor=lambda s: s.target_location
    )
    ```
    """

    def marked(item: E) -> tuple[Location, str | None, Location | None] | None:
        where = location(item)
        if not present(where):
            return None
        tied = None if anchor is None else anchor(item)
        return (
            where,
            label(item) if label is not None else None,
            tied if present(tied) else None,
        )

    marked_items, entries = entries_of(container, marked, cls.__name__)
    items = items_of(marked_items)
    return cls(
        [where for _, _, (where, _, _) in items],
        labels=[
            text if text is not None else (None if key is None else caption(key))
            for _, key, (_, text, _) in items
        ],
        marker=marker,
        outline=outline,
        anchors=None if anchor is None else Array([tied for _, _, (_, _, tied) in items]),
        entries=entries,
    )