from __future__ import annotations """Geographic presets and tagging rules for OpenStreetMap imports.""" from dataclasses import dataclass from typing import Iterable, Mapping, Tuple @dataclass(frozen=True) class BoundingBox: """Geographic bounding box expressed as WGS84 coordinates.""" name: str north: float south: float east: float west: float description: str | None = None def __post_init__(self) -> None: if self.north <= self.south: msg = f"north ({self.north}) must be greater than south ({self.south})" raise ValueError(msg) if self.east <= self.west: msg = f"east ({self.east}) must be greater than west ({self.west})" raise ValueError(msg) def contains(self, latitude: float, longitude: float) -> bool: """Return True when the given coordinate lies inside the bounding box.""" return ( self.south <= latitude <= self.north and self.west <= longitude <= self.east ) def to_overpass_arg(self) -> str: """Return the bbox string used for Overpass API queries.""" return f"{self.south},{self.west},{self.north},{self.east}" # Primary metropolitan areas we plan to support. DEFAULT_REGIONS: Tuple[BoundingBox, ...] = ( BoundingBox( name="berlin_metropolitan", north=52.6755, south=52.3381, east=13.7611, west=13.0884, description="Berlin and surrounding rapid transit network", ), BoundingBox( name="hamburg_metropolitan", north=53.7447, south=53.3950, east=10.3253, west=9.7270, description="Hamburg S-Bahn and harbor region", ), BoundingBox( name="munich_metropolitan", north=48.2485, south=47.9960, east=11.7229, west=11.3600, description="Munich S-Bahn core and airport corridor", ), ) # Tags that identify passenger stations and stops. STATION_TAG_FILTERS: Mapping[str, Tuple[str, ...]] = { "railway": ("station", "halt", "stop"), "public_transport": ("station", "stop_position", "platform"), "train": ("yes", "regional", "suburban"), } # Tags that describe rail infrastructure usable for train routing. TRACK_ALLOWED_RAILWAY_TYPES: Tuple[str, ...] = ( "rail", "light_rail", "subway", "tram", "narrow_gauge", "disused", "construction", ) TRACK_TAG_FILTERS: Mapping[str, Tuple[str, ...]] = { "railway": TRACK_ALLOWED_RAILWAY_TYPES, } # Track ingestion policy TRACK_EXCLUDED_SERVICE_TAGS: Tuple[str, ...] = ( "yard", "siding", "spur", "crossover", "industrial", "military", ) TRACK_EXCLUDED_USAGE_TAGS: Tuple[str, ...] = ( "military", "tourism", ) TRACK_MIN_LENGTH_METERS: float = 75.0 TRACK_STATION_SNAP_RADIUS_METERS: float = 350.0 def compile_overpass_filters(filters: Mapping[str, Iterable[str]]) -> str: """Build an Overpass boolean expression that matches the provided filters.""" parts: list[str] = [] for key, values in filters.items(): options = "|".join(sorted(set(values))) parts.append(f' ["{key}"~"^({options})$"]') return "\n".join(parts) __all__ = [ "BoundingBox", "DEFAULT_REGIONS", "STATION_TAG_FILTERS", "TRACK_ALLOWED_RAILWAY_TYPES", "TRACK_TAG_FILTERS", "TRACK_EXCLUDED_SERVICE_TAGS", "TRACK_EXCLUDED_USAGE_TAGS", "TRACK_MIN_LENGTH_METERS", "TRACK_STATION_SNAP_RADIUS_METERS", "compile_overpass_filters", ]