Files
Claude ef67df6e00 Spanien (Renfe + Cercanías), Polen, Norwegen, Dänemark, Österreich (ÖBB + Wiener Linien), Schweden (Key)
- mehrere Quellen je Land (country), Grenzen aus Natural Earth, OSM und Kachelausschnitt erweitert
- Fahrpläne glätten: Unterordner im Zip, aufgefüllte Felder, fehlende parent_station, level_id
- Gattung aus der Zugnummer (ÖBB 'RJ 658'), Fernverkehr auch über Routentyp 101/102/105
- Echtzeit: Verspätung vor Uhrzeit (Renfe), Fahrten ohne Datum, Key in der Adresse (Trafiklab)
- httpx-Logs aus (würden Keys zeigen)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01X48pE4bzjHej8w9HyZXCjb
2026-10-23 09:53:56 +02:00

68 lines
2.6 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""Landesgrenzen, damit jeder Fahrplan seine Züge nur im eigenen Land zeigt – sonst wären
grenzüberschreitende Züge doppelt auf der Karte (z. B. ICE Basel–Frankfurt in CH- und DE-Daten).
Schweiz aus OpenStreetMap (~400 m genau, mit Büsingen und Campione als Löcher), die übrigen
Länder aus Natural Earth 1:10 Mio. (gemeinfrei), auf ~400 m vereinfacht."""
import json
from pathlib import Path
_RAW = json.loads((Path(__file__).with_name("borders.json")).read_text())
ORDER = ["ch", "de", "fr", "be", "nl", "at", "dk", "es", "no", "pl", "se"] # Schweiz zuerst: genauere Grenze, gewinnt bei Überlappung
CELL = 0.01 # ~1 km; im Grenzbereich wird je Zelle einmal gerechnet und gemerkt
def _bbox(ring):
xs, ys = [p[0] for p in ring], [p[1] for p in ring]
return min(xs), min(ys), max(xs), max(ys)
_POLYS = {code: [(_bbox(p[0]), p) for p in _RAW[code]] for code in ORDER if code in _RAW}
def _in_ring(x: float, y: float, ring) -> bool:
inside = False
for (x1, y1), (x2, y2) in zip(ring, ring[1:] + ring[:1]):
if (y1 > y) != (y2 > y) and x < (x2 - x1) * (y - y1) / (y2 - y1) + x1:
inside = not inside
return inside
def _country(x: float, y: float) -> str | None:
for code, polys in _POLYS.items():
for (x0, y0, x1, y1), (outer, *holes) in polys:
if x0 <= x <= x1 and y0 <= y <= y1 and _in_ring(x, y, outer) \
and not any(_in_ring(x, y, h) for h in holes):
return code
return None
_cache: dict = {}
def country(lon: float, lat: float) -> str | None:
"""Ländercode (ch, de, fr, be, nl) oder None für alle anderen Länder."""
key = (round(lon / CELL), round(lat / CELL))
if key not in _cache:
if len(_cache) > 500_000:
_cache.clear()
_cache[key] = _country(key[0] * CELL, key[1] * CELL)
return _cache[key]
def bbox(code: str, margin: float = 0.6) -> str:
"""Ausschnitt eines Landes (+ ~50 km Rand) als "x0,y0,x1,y1" für osmium."""
boxes = [b for b, _ in _POLYS[code]]
return ",".join(f"{v:.2f}" for v in (min(b[0] for b in boxes) - margin, min(b[1] for b in boxes) - margin,
max(b[2] for b in boxes) + margin, max(b[3] for b in boxes) + margin))
def in_switzerland(lon: float, lat: float) -> bool:
return _country(lon, lat) == "ch"
def shows(source: str, enabled, lon: float, lat: float) -> bool:
"""Zeigt diese Quelle den Zug an dieser Stelle? Im eigenen Land ja, in einem Land mit eigenem
aktivem Fahrplan nein (dort zeigt ihn dieser). Überall sonst ja."""
c = country(lon, lat)
return c is None or c == source or c not in enabled