Files
watermaps/README.md
T
2026-07-24 11:29:24 +02:00

223 lines
19 KiB
Markdown
Raw 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.
# Watermaps
iPhone-taugliche Browser-PWA für Bootsfahrer: Karte, GPS, Kompass, Wetter/Wellen, BSH-Tiden, Fahrwasser-Routing mit Bootsmaßen sowie Schleusen- und Hafeninformationen.
## Funktionen
- Routing auf See- und Binnenwasserstraßen statt Luftlinien
- explizit gestarteter Kursassistent mit dynamischem Sollkurs über Grund, Querabweichung, Reststrecke und Kurswechsel-Hinweisen
- zweistufige Ankerwache mit festem Ankerpunkt, GPS-Schwojkreis, Alarm, Tidenanstieg und nachvollziehbarer Ketten-/Leinenreserve
- bis zu drei ausreichend unterschiedliche Routenvorschläge, sofern der Wasserstraßengraph echte Alternativen enthält
- Prüfung von Tiefgang plus Sicherheitsreserve, Bootshöhe, Breite und bekannten Einbahnregeln
- Fahrwasser-Unterstützung für die Korridore EmdenBorkum und EmdenHamm nach manueller Auswahl von Start und Ziel
- antippbare Info-Buttons ab Kartenzoom 12 an Schleusen und Häfen mit Telefon, Website, E-Mail, VHF, Öffnungszeiten, Betreiber und Adresse, soweit in den Quelldaten vorhanden
- Brückenhöhen, bekannte Tiefen, Wetter/Wellen, Tide, GPS und Kompass
- frei sortierbare Zwischenziele direkt von der Karte
- Abfahrtszeit-bezogene Wetter-, Wellen-, Strömungs- und Tidenplanung entlang der Route
- Live-Wasserstände aus der offiziellen PEGELONLINE-API der WSV mit Cache- und Veraltet-Status
- Tagesetappen mit Hafenwahl und Filtern für Strom, Wasser, Treibstoff, Entsorgung und Übernachtung
- Schleusenliste entlang der Route mit bekannten Betriebszeiten/Kontakten und einstellbarem Planungspuffer
- GPX-1.1-Export, lokal gespeicherte Offline-Routen und ein nur auf Nutzeraktion gestarteter Kursabweichungsalarm
- begrenztes Offline-Caching bereits besuchter Kartenressourcen ohne ungefragten Kacheldownload
## Start
Dieses Projekt benötigt Node `>=20.19`. In dieser Arbeitskopie liegt eine lokale Node-Version unter `.tools/`; nutze sie so:
```bash
export PATH="$PWD/.tools/bin:$PATH"
npm install
npm run build
npm run test
npm run dev
```
Web: `http://localhost:5173`<br>
API: `http://localhost:5174`
Für GPS-Tests auf dem iPhone muss die App über HTTPS laufen. Starte dafür:
```bash
npm run dev:https
```
Web HTTPS: `https://localhost:5173` bzw. die von Vite ausgegebene `https://192.168...:5173`-Adresse im gleichen WLAN. Beim lokalen Dev-Zertifikat muss Safari die Zertifikatswarnung einmal akzeptieren; falls iOS Geolocation danach weiterhin blockiert, nutze ein vertrauenswürdiges lokales Zertifikat oder einen HTTPS-Tunnel.
## Datenstatus
Die freie Datenstrategie ist bewusst als Fahr- und Planungshilfe umgesetzt. Die App zeigt Attribution und den Status `Nicht amtlich`, weil freie Karten-/Modelldaten keine amtlich zugelassene Seekarte ersetzen.
## Routingstatus
`POST /api/routes` nutzt zuerst lokale PostGIS-Fahrwasserdaten aus `marine_fairway_edges`. Wenn in der Datenbank kein passender Graph liegt, versucht die API live extrahierte OSM/OpenSeaMap-Fahrwasserdaten für die Bounding Box zwischen Start, Wegpunkten und Ziel. Lokale und Live-Fragmente werden topologisch zusammengeführt. Berücksichtigt werden unter anderem `navigation_line`, `recommended_track`, `fairway`, navigierbare Kanäle und explizit für Boote oder Schiffe freigegebene Flüsse. Gesperrte, private, stillgelegte oder im Bau befindliche Wege werden ausgeschlossen.
Wenn freie Laufzeitdaten fehlen, stehen zwei klar als nicht amtlich markierte Fallback-Korridore bereit:
- Emden Außenhafen → Borkum Reede
- Emden Außenhafen → Ems/Dortmund-Ems-Kanal → Datteln → Datteln-Hamm-Kanal → Wasserwanderrastplatz Hamm-Innenstadt (rund 153 sm)
Der EmdenHamm-Fallback besitzt keine belastbaren Tiefen- oder Schleusenzeitdaten und ist bewusst auf Sportboote bis 2,5 m Tiefgang begrenzt. Vor der Fahrt sind aktuelle Sperrungen, Betriebszeiten, Wasserstände und amtliche Karten zu prüfen. Kostenfreie Inland-ENCs für den Dortmund-Ems- und Datteln-Hamm-Kanal stellt [ELWIS](https://www.elwis.de/DE/dynamisch/IENC/) bereit.
Wenn kein Graph passt, liefert die API bewusst `422 no_fairway_route`, damit keine irreführende Luftlinie als Bootsroute gezeichnet wird. `alternatives` im Ergebnis enthält bis zu zwei weitere, topologisch unterschiedliche Optionen.
## Kursassistent entlang einer Route
Nach erfolgreicher Routenplanung erscheint „Kursassistent starten“. Der Assistent verwendet den bereits vorhandenen hochgenauen GPS-Datenstrom und berechnet bei jedem neuen Fix einen geschwindigkeits- und genauigkeitsabhängigen Vorausschaupunkt auf der Route. Daraus entstehen Sollkurs über Grund, Kurskorrektur gegenüber dem GPS-Kurs über Grund (COG), Querabstand, Routenfortschritt, Reststrecke und der nächste deutliche Backbord-/Steuerbord-Kurswechsel. Vor engen Richtungswechseln endet die Vorausschau am Kurvenpunkt, damit nicht diagonal über das Fahrwasser abgekürzt wird.
Der Fortschritt wird gegen GPS-Sprünge und sich kreuzende Routenabschnitte stabilisiert. Bei einem mehr als 15 Sekunden alten oder zu ungenauen Fix pausiert die Steueranweisung. Der Gerätekompass bleibt als gekennzeichnete Orientierung sichtbar, wird aber nicht mit dem geografischen Sollkurs verrechnet; eine Kurskorrektur wird erst aus einem belastbaren GPS-COG gebildet. Laut [W3C-Geolocation-Spezifikation](https://www.w3.org/TR/geolocation/) kann der Browser Position, Genauigkeit, Geschwindigkeit und einen Kurs relativ zu geografisch Nord liefern, garantiert aber nicht die tatsächliche Position des Geräts.
Der Kursassistent steuert weder Ruder noch Maschine und gibt keine NMEA-Kommandos aus. Er ist eine nicht amtliche Navigationshilfe; Ausguck, sichere Geschwindigkeit, Tonnen, Ufer, Verkehr, Wasserstände und amtliche Unterlagen haben immer Vorrang. Browser und Betriebssystem können GPS-Aktualisierungen bei gesperrtem Display oder im Hintergrund anhalten.
## Ankerwache mit Tide und Leinenreserve
Die Ankerwache ist unabhängig von einer geplanten Route über das Ankersymbol rechts auf der Karte erreichbar. Sie startet nie automatisch:
1. Live-GPS starten und genau beim Erreichen des Grundes „Anker gefallen Position jetzt setzen“ wählen. Der Fix muss jünger als zehn Sekunden und auf höchstens 30 m genau sein. Der gespeicherte Ankerpunkt bleibt danach fest.
2. Tiefe beim Setzen, Höhe der Bugrolle über Wasser, ausgesteckte Ketten-/Leinenlänge, gewünschtes Verhältnis, zusätzliche Wasserstandsreserve, Alarmradius und Tidenzeitraum prüfen.
3. Erst „Wache starten“ schaltet Positionsalarm, Warnton, Vibration, optionale Browser-Mitteilungen und soweit unterstützt eine Bildschirm-Wachhalteanforderung ein.
Kursassistent, separater Kursalarm und Ankerwache laufen nicht parallel. Beim Öffnen der Ankerfunktion wird eine laufende Kursführung beendet; die geplante Route selbst bleibt erhalten.
Die Leinenplanung verwendet:
```text
(Tiefe beim Setzen + Bugrollenhöhe + maximaler weiterer Tidenanstieg + Wasserstandsreserve) × gewähltes Verhältnis
```
Die BSH-Pegelhöhe wird dabei ausdrücklich nicht als örtliche Wassertiefe verwendet. Aus der Kurve wird nur die relative Änderung gegenüber dem Zeitpunkt des Ankersetzens abgeleitet. Station und Entfernung bleiben sichtbar. Deckt die Prognose den gewählten Zeitraum nicht vollständig ab, zeigt Watermaps nur den tideunabhängigen Mindestbedarf und bestätigt weder Bedarf noch Reserve. Die laufende Positionswache bleibt trotzdem nutzbar. Die Differenz aus höchstem und niedrigstem Kurvenwert im Zeitraum wird als Tidenhub angezeigt; allgemein bezeichnet „tidal range“ die Höhendifferenz zwischen Hoch- und Niedrigwasser ([NOAA](https://oceanservice.noaa.gov/facts/tides.html)).
Das Verhältnis ist absichtlich einstellbar. Die RYA nennt als Orientierung viermal die maximale Wassertiefe bei Kette beziehungsweise sechsmal bei einer Kombination aus Kette und Leine, weist aber zugleich auf Grund und Schwojbereich hin ([RYA Anchoring with care](https://www.rya.org.uk/environment-and-sustainability/anchoring-with-care/)). Wind, Wellen, Strom, Schwell, Ankerbauart, Grund, Bootslänge und nahe Gefahren können mehr Länge oder einen enger gewählten Alarmbereich erfordern; die Rechnung entscheidet das nicht selbst.
Auf der Karte erscheinen der feste Ankerpunkt, ein geodätisch in Metern berechneter Alarmring und die Verbindung zum Boot. Ein Driftalarm wird erst ausgelöst, wenn der Abstand auch nach Abzug der gemeldeten GPS-Ungenauigkeit außerhalb des Radius liegt. Umgekehrt werden ein mehr als 20 Sekunden alter Fix, GPS-Ausfall oder eine Genauigkeit schlechter als 30 m als eigener Alarmzustand angezeigt. Das vermindert Fehlalarme, kann eine echte Drift aber auch später melden. Peilmarken, Ankerkontrolle und Ausguck bleiben deshalb erforderlich.
Web-Apps können keine ununterbrochene Hintergrundüberwachung garantieren. iOS, der Browser oder ein gesperrtes Display können GPS, JavaScript, Vibration, Ton und Mitteilungen anhalten. Für eine Nachtwache muss Watermaps sichtbar bleiben; die Funktion ersetzt keinen eigenständigen zugelassenen Ankeralarm.
## Reise-, Zeit- und Live-Datenplanung
Eine Route kann bis zu 25 Zwischenziele und eine ISO-Abfahrtszeit enthalten. Die Grund-ETA beginnt an dieser Abfahrtszeit. Für Start, Mitte und Ziel fragt die Web-App die Prognose zum geschätzten Passierzeitpunkt ab. Die Open-Meteo-Marine-Daten enthalten dabei Wellen und modellierte Strömung; die Strömungs-Komponente längs zum jeweiligen Routenkurs wird als klar gekennzeichnete Modellkorrektur der ETA dargestellt. Werte außerhalb des verfügbaren Vorhersagefensters werden nicht als aktuelle Prognose ausgegeben.
Für Start und Ziel werden passende BSH-Tidenstationen zur geplanten Zeit abgefragt. Die Stationsentfernung wird angezeigt, weil Bezugsnull und lokale Abweichungen für die Navigation entscheidend bleiben.
`GET /api/navigation/live` bindet aktuelle Wasserstände über die dokumentierte [PEGELONLINE REST-API v2](https://pegelonline.wsv.de/webservice/dokuRestapi) ein. Ergebnisse werden 60 Sekunden frisch gehalten; bei Ausfall kann höchstens sechs Stunden lang der letzte erfolgreiche Stand mit dem Status `stale` angezeigt werden. Für die EmdenHamm-Route werden EMS, DEK und DHK abgefragt.
ELWIS veröffentlicht [Schleuseninformationen](https://www.elwis.de/DE/dynamisch/Schleuseninformationen/) und [Nachrichten für die Binnenschifffahrt](https://www.elwis.de/DE/dynamisch/Nfb/). Da dafür keine verlässlich dokumentierte öffentliche REST-/JSON-Schnittstelle vorliegt, wird kein vermeintlicher Live-Status aus HTML gescrapt. Das Backend besitzt stattdessen eine streng auf offizielle WSV-/ELWIS-HTTPS-Quellen begrenzte Adapter-Schnittstelle. Ohne konfigurierten amtlichen Feed zeigt die App den offiziellen Prüf-Link. Der einstellbare Schleusenpuffer ist ausdrücklich nur eine eigene Planannahme, keine gemessene Wartezeit.
Die Etappenplanung wählt nur Häfen innerhalb des Tageslimits und des erlaubten Abstechers. Ausstattungsmerkmale ohne bestätigten Wert gelten sicherheitshalber als unbekannt und erfüllen keinen aktivierten Versorgungsfilter.
Offline gespeichert werden Route, Wegpunkte, Bootsprofil und Abfahrtszeit auf dem jeweiligen Gerät. Live-GPS-Positionen werden weder gespeichert noch übertragen. Der Service Worker hält nur tatsächlich besuchte OpenFreeMap-/OpenSeaMap-Ressourcen zeitlich und mengenmäßig begrenzt vor; Watermaps lädt nicht automatisch ganze Routenkorridore herunter.
Für produktivere Daten kann `scripts/import-geofabrik.sh` aus einem Geofabrik-PBF routbare Fahrwasser in `marine_fairway_edges` importieren. Das ist der richtige Weg für "alle Fahrwasserdaten" im eigenen Backend; Rasterkacheln der Karte werden nicht zurückdigitalisiert.
Die Küsten-PBFs lassen sich reproduzierbar von Geofabrik laden und ohne lokal installierte GIS-Tools importieren:
```bash
./scripts/download-geofabrik.sh
docker compose up -d postgres redis martin
DATABASE_URL=postgres://seacompass:seacompass@localhost:55432/seacompass \
./scripts/import-geofabrik-docker.sh \
data/geofabrik/germany-latest.osm.pbf \
data/geofabrik/netherlands-latest.osm.pbf
```
Ohne Argumente lädt `download-geofabrik.sh` die vollständigen Extrakte für Deutschland und die Niederlande. Das benötigt mehrere Gigabyte Speicher und Downloadvolumen. Für den EmdenHamm-Korridor genügen gezielt:
```bash
./scripts/download-geofabrik.sh niedersachsen nordrhein-westfalen
./scripts/import-geofabrik-docker.sh \
data/geofabrik/niedersachsen-latest.osm.pbf \
data/geofabrik/nordrhein-westfalen-latest.osm.pbf
```
Die vollständigen Niederlande lassen sich separat laden und importieren:
```bash
./scripts/download-geofabrik.sh netherlands
DATABASE_URL=postgres://seacompass:seacompass@localhost:55432/seacompass \
./scripts/import-geofabrik-docker.sh data/geofabrik/netherlands-latest.osm.pbf
```
Der Import erkennt auch Schleusen, Häfen, Marinas und Ports und aktualisiert geänderte OSM-Kontaktdaten per Upsert. Für Schleusen werden unter anderem `lock=yes`, `waterway=lock_gate`, `water=lock`, `obstacle=lock` und entsprechende Seamark-Gates berücksichtigt. Flächen aus überlappenden Extrakten werden über ihre kanonische OSM-ID zusammengeführt. Nach einem Update der Importlogik sollte der bestehende Datenbestand erneut importiert werden.
Deutsche Schleusen können zusätzlich aus dem offiziellen EuRIS-Datenangebot abgeglichen werden. Ein Trockenlauf prüft den Abruf, ohne die Datenbank zu verändern:
```bash
DATABASE_URL=postgres://seacompass:seacompass@localhost:55432/seacompass \
EURIS_COUNTRIES=DE EURIS_DRY_RUN=true \
npm run sync:euris-locks
```
Zum Speichern `EURIS_DRY_RUN=false` setzen. `EURIS_DETAIL_LIMIT` ist optional und wegen der API-Belastung auf 20 Detailaufrufe pro Lauf begrenzt; die kompakten Schleusen- und RIS-Index-Daten werden unabhängig davon vollständig seitenweise gelesen. Ein optionales Zugriffstoken kann über `EURIS_API_TOKEN` gesetzt werden.
Fehlende Kontakte können zunächst aus den bereits in OSM oder EuRIS verlinkten Facility-Websites ergänzt werden. Dieser erste Adapter verwendet keine Suchmaschine, ist standardmäßig ein Trockenlauf und verarbeitet standardmäßig höchstens 25 Datensätze:
```bash
DATABASE_URL=postgres://seacompass:seacompass@localhost:55432/seacompass \
npm run enrich:marine-websites
```
Erst nach Prüfung der Zusammenfassung wird das Schreiben explizit aktiviert:
```bash
DATABASE_URL=postgres://seacompass:seacompass@localhost:55432/seacompass \
MARINE_WEBSITE_DRY_RUN=false \
npm run enrich:marine-websites
```
Die Ergänzungen werden mit derselben Geometrie separat als Quelle `facility-website` gespeichert. Abgerufen werden ausschließlich öffentliche HTTP(S)-Ziele; private und lokale IP-Bereiche sowie unsichere Redirects werden blockiert. Extrahiert werden strukturierte JSON-LD-Kontakte und explizite `tel:`-/`mailto:`-Links, keine frei im Seitentext vermuteten Telefonnummern.
Für benannte Anlagen, denen danach weiterhin Website- oder Kontaktdaten fehlen, gibt es einen konservativen Suchmaschinen-Fallback. Er ist ebenfalls standardmäßig ein Trockenlauf:
```bash
DATABASE_URL=postgres://seacompass:seacompass@localhost:55432/seacompass \
MARINE_SEARCH_PROVIDER=duckduckgo \
npm run enrich:marine-search
```
Zum kontrollierten Speichern eines kleinen Batches:
```bash
DATABASE_URL=postgres://seacompass:seacompass@localhost:55432/seacompass \
MARINE_SEARCH_PROVIDER=duckduckgo \
MARINE_SEARCH_DRY_RUN=false \
MARINE_SEARCH_LIMIT=10 \
npm run enrich:marine-search
```
Die Routine lehnt generische Namen wie `Hafen` oder `Schleuse` ab, bewertet Anlagenname, Ort, Wasserstraße, Typ und Domain und verlangt einen deutlichen Abstand zum zweitbesten Host. Ein Suchsnippet wird niemals als Kontaktquelle verwendet: Der Ziel-Link wird erneut durch die SSRF-, DNS-, Redirect-, TLS-, Größen- und Timeout-Prüfungen geschickt. Erst wenn auch die Zielseite eindeutig zur Anlage passt, werden dort vorhandene JSON-LD-Daten sowie `tel:`-/`mailto:`-Links übernommen. Eine verifizierte offizielle Website kann auch ohne weitere Kontaktfelder ergänzt werden; bestehende OSM-, EuRIS- und frühere Anreicherungswerte werden nicht überschrieben.
DuckDuckGo dokumentiert seine [HTML- und Lite-Seiten als Non-JavaScript-Suche](https://duckduckgo.com/duckduckgo-help-pages/features/non-javascript), jedoch nicht als stabile allgemeine Such-API. Deshalb läuft dieser Provider mit Parallelität 1, standardmäßig fünf Sekunden Abstand und einem Circuit Breaker: HTTP 202/403/429 oder eine Bot-Prüfung stoppen den Batch, statt fälschlich „keine Treffer“ zu speichern. Als zuverlässigere optionale API kann [Brave Web Search](https://api-dashboard.search.brave.com/app/documentation/web-search/get-started) verwendet werden:
```bash
DATABASE_URL=postgres://seacompass:seacompass@localhost:55432/seacompass \
MARINE_SEARCH_PROVIDER=brave \
BRAVE_SEARCH_API_KEY=... \
MARINE_SEARCH_DRY_RUN=false \
npm run enrich:marine-search
```
Im Schreibmodus führt `marine_enrichment_attempts` je Provider und Suchfingerabdruck einen Checkpoint. Erfolgreiche, mehrdeutige, kontaktlose und fehlgeschlagene Versuche werden mit unterschiedlichen Wiederholungsfristen gespeichert, sodass aufeinanderfolgende Batches fortschreiten, anstatt dieselben Anlagen sofort erneut abzufragen. Die eigentliche Kartenanreicherung bleibt für die bestehende Zusammenführung unter der Datenquelle `facility-website`; Properties wie `enrichmentSource=facility-search`, Provider, Suchabfrage, Treffer-URL, Scores und feldweise Provenienz machen den Ursprung auditierbar.
Die Karten-API vereinigt nahe OSM-, EuRIS- und Website-Objekte anhand offizieller Kennungen, normalisierter Anlagennamen, Entfernung und Objektrolle zu einer kanonischen Anlage. Rohdaten bleiben für spätere Neuberechnungen erhalten. Schleusen- und Hafenobjekte werden erst ab Zoom 12 geladen und in einer geclusterten MapLibre-GeoJSON-Ebene GPU-beschleunigt gezeichnet; beim Verschieben wird nur nach `moveend` neu geladen und die vorherige Anfrage abgebrochen. Erst beim Heranzoomen erscheinen einzelne Info-Symbole. Die streckenbezogene Etappen- und Schleusenplanung bleibt davon unabhängig verfügbar.
Um die Kartenantwort klein zu halten, liefert die API bei Brücken nur Einträge mit bekannter Durchfahrtshöhe oder einem beweglichen Brückentyp aus.
Der lokale PostGIS-Container nutzt standardmäßig Host-Port `55432`, damit er nicht mit bestehenden lokalen Postgres-Installationen auf `5432` kollidiert. Setze deshalb für API und Import `DATABASE_URL=postgres://seacompass:seacompass@localhost:55432/seacompass`.
## Lokale Infrastruktur
```bash
docker compose up -d postgres redis martin
```
Danach `.env` aus `.env.example` ableiten und für echte PostGIS-Features `WATERMAPS_DEMO_DATA=false` setzen. Die frühere Variable `SEA_COMPASS_DEMO_DATA` wird übergangsweise weiterhin akzeptiert.
## APIs
- `GET /api/config`
- `GET /api/weather/marine?lat=54.18&lon=12.09`
- `GET /api/weather/marine?lat=54.18&lon=12.09&at=2026-07-20T08:00:00.000Z`
- `GET /api/tides/nearest?lat=54.18&lon=12.09&at=2026-07-20T08:00:00.000Z`
- `GET /api/navigation/live?waterways=EMS,DEK,DHK`
- `GET /api/features?bbox=12,54,13,55&layers=seamarks,bridges,locks,harbours`
- `POST /api/routes`