- Updated documentation to include OSM Track Harvesting Policy with details on railway types, service filters, usage filters, and geometry guardrails. - Introduced a new script `init_demo_db.py` to automate the database setup process, including environment checks, running migrations, and loading OSM fixtures for demo data.
137 lines
3.5 KiB
Python
137 lines
3.5 KiB
Python
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",
|
|
]
|