Skip to content

mamut_routing_tools.generation.pois

Amenity POI extraction from an OSM XML extract.

NoPoiFoundError

Bases: ValueError

The extract holds no amenity of the requested categories.

A ValueError still, so callers that only care that the selection failed keep working; named so that the hybrid method can tell this apart from a genuine bad request and fall back to parametric points instead of failing.

is_poi_source_tag

is_poi_source_tag(tag: str) -> bool

Whether a node's source_tag means it came from an amenity.

Covers poi and poi_manual; parametric tags (param, param_fill) are not POIs.

Source code in MAMUT-routing-tools/src/mamut_routing_tools/generation/pois.py
def is_poi_source_tag(tag: str) -> bool:
    """Whether a node's ``source_tag`` means it came from an amenity.

    Covers ``poi`` and ``poi_manual``; parametric tags (``param``,
    ``param_fill``) are not POIs.
    """
    return str(tag).startswith("poi")

poi_ref

poi_ref(
    osm_type: str | None, osm_id: int | None
) -> tuple[str, int] | None

A hashable (type, id) identity, or None for an unidentifiable POI.

Source code in MAMUT-routing-tools/src/mamut_routing_tools/generation/pois.py
def poi_ref(osm_type: str | None, osm_id: int | None) -> tuple[str, int] | None:
    """A hashable ``(type, id)`` identity, or ``None`` for an unidentifiable POI."""
    if osm_id is None:
        return None
    return (str(osm_type or "node"), int(osm_id))

find_pois

find_pois(
    osm_path: str | Path,
    categories: list[str] | None = None,
) -> list[Poi]

Every element tagged with an amenity in categories, in file order.

Nodes, ways and relations alike: in a real city a large share of the bars, restaurants, schools and car parks are mapped as building outlines rather than points, and reading nodes alone silently loses them.

Source code in MAMUT-routing-tools/src/mamut_routing_tools/generation/pois.py
def find_pois(osm_path: str | Path, categories: list[str] | None = None) -> list[Poi]:
    """Every element tagged with an ``amenity`` in ``categories``, in file order.

    Nodes, ways and relations alike: in a real city a large share of the bars,
    restaurants, schools and car parks are mapped as building outlines rather
    than points, and reading nodes alone silently loses them.
    """
    wanted = set(categories or DEFAULT_CATEGORIES)
    pois: list[Poi] = []
    for _event, element in ET.iterparse(str(osm_path), events=("end",)):
        if element.tag not in ("node", "way", "relation"):
            continue
        point = _element_point(element)
        if point is not None:
            # The whole tag block is read rather than stopping at the amenity,
            # because the display name usually sits after it in file order.
            category: str | None = None
            name: str | None = None
            for tag in element:
                if tag.tag != "tag":
                    continue
                key = tag.get("k")
                if key == "amenity":
                    value = tag.get("v")
                    if value in wanted:
                        category = str(value)
                    else:
                        break
                elif key == "name" and name is None:
                    name = str(tag.get("v") or "") or None
            if category is not None:
                raw_id = element.get("id")
                pois.append(
                    Poi(
                        point[0],
                        point[1],
                        category,
                        int(raw_id) if raw_id is not None else None,
                        name,
                        element.tag,
                    )
                )
        element.clear()
    return pois

load_poi_catalog

load_poi_catalog(osm_path: str | Path) -> list[Poi]

Every POI of every known category in an extract, parsed at most once.

The whole catalog is read rather than a filtered subset, so switching the category filter never costs another pass over the file. The returned list is shared and must not be mutated by callers.

Source code in MAMUT-routing-tools/src/mamut_routing_tools/generation/pois.py
def load_poi_catalog(osm_path: str | Path) -> list[Poi]:
    """Every POI of every known category in an extract, parsed at most once.

    The whole catalog is read rather than a filtered subset, so switching the
    category filter never costs another pass over the file. The returned list is
    shared and must not be mutated by callers.
    """
    path = Path(osm_path)
    stat = path.stat()
    key = (str(path.resolve()), int(stat.st_mtime_ns), int(stat.st_size))
    cached = _CATALOG_CACHE.get(key)
    if cached is not None:
        _CATALOG_CACHE.move_to_end(key)
        return cached
    pois = find_pois(path, list(POI_CATEGORIES))
    _CATALOG_CACHE[key] = pois
    while len(_CATALOG_CACHE) > _CATALOG_CACHE_MAX_CITIES:
        _CATALOG_CACHE.popitem(last=False)
    return pois