"""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