ÖPNV Navi – Frontend
Vanilla-JS-Webapp für Live-Abfahrten, Routenplanung und eine Live-Karte im VBB-Verbund (Berlin/Brandenburg) mit Anbindung an echte DB-Bahnhöfe. Kein Build-Schritt, kein Framework – reines HTML/CSS/JS, läuft direkt als statische Website.
Tech-Stack
- Reines HTML5, CSS3, ES2020+ JavaScript (keine Frameworks, kein Bundler)
- Leaflet für die Live-Karte (per CDN eingebunden)
- Google Fonts: IBM Plex Mono / Sans / Sans Condensed
- Service Worker für Web-Push-Benachrichtigungen
Ordnerstruktur
frontend/
├── index.html Einzige HTML-Seite (Single Page App über Tabs)
├── styles.css Komplettes Stylesheet ("Solari-Anzeigetafel bei Nacht"-Theme)
├── sw.js Service Worker (Web-Push) - MUSS im Root liegen, nicht unter js/
├── manifest.json PWA-Manifest
└── js/
├── config.js Zentrale Konfiguration (API_BASE-URL des Backends)
├── api.js Schlanker fetch()-Wrapper für alle Backend-Endpunkte
├── i18n.js Mehrsprachigkeit (DE/EN), Wörterbuch + Sprachumschaltung
├── app.js Hauptlogik: Tabs, Suche, Abfahrtstafel, Favoriten, Routenplaner
├── splitflap.js Flip-Animation der Abfahrtstafel (+ Screenreader-Textalternative)
├── modefilter.js Wiederverwendbare Verkehrsmittel-Filter-Chips
├── livemap.js Live-Karte (Leaflet): Radar-Modus, Trip-Tracking, Isolations-Modus
├── tripmodal.js Fahrt-Detail-Modal (Zwischenhalte, Mini-Karte, Verspätungsstatistik)
├── tripalarm.js Fahrtalarm ("wecke mich X Stationen vorher")
└── push.js Web-Push: Service-Worker-Registrierung, Abo-Verwaltung
Einrichtung / Deployment
Keine Installation nötig – es sind statische Dateien. Einfach den kompletten Ordnerinhalt auf einen Webserver (nginx, Apache, Vercel, o.ä.) legen.
Wichtig:
sw.jsmuss im Root des ausgelieferten Verzeichnisses liegen (dort, wo auchindex.htmlliegt), nicht unterjs/. Der Geltungsbereich eines Service Workers ist standardmäßig auf sein eigenes Verzeichnis und alles darunter beschränkt.- Web Push (und Service Worker generell) funktionieren nur über HTTPS
(oder
localhostwährend der Entwicklung). - In
js/config.jsdie Backend-URL eintragen:window.APP_CONFIG = { API_BASE: 'https://dein-server.example/live_navi', };
Features im Überblick
- Abfahrten: Live-Abfahrtstafel (VBB + echte DB-Bahnhöfe), Verkehrsmittel-Filter, Gleiswechsel-Hinweis, Störungen & Aufzugsstatus direkt unter der Tafel
- Route: Routenplanung über eine selbst gehostete OTP2-Instanz, konfigurierbarer Umstiegszeit-Puffer, rollstuhlgerechte Verbindungen, Route teilen (Link)
- Karte: Live-Fahrzeugpositionen, Fahrt isolieren/verfolgen, display-breite Darstellung auf jeder Bildschirmgröße
- Favoriten: eigener Tab, Live-Vorschau der nächsten Abfahrt, Störungs-Badges
- Fahrtalarm: weckt X Stationen vor dem Ziel (Ton, Vibration, Vollbild-Overlay)
- Push-Benachrichtigungen: funktionieren auch bei geschlossenem Tab/Browser
(Service Worker + Web Push, siehe
js/push.js) - Verlauf: zuletzt gesuchte Haltestellen, direkt als Vorschläge im Suchfeld
- Mehrsprachigkeit: Deutsch/Englisch, automatische Browsersprache-Erkennung
- Barrierefreiheit: Tastaturbedienung überall (inkl. Suche), ARIA-Tab-Pattern,
Fokus-Fallen in Dialogen, Screenreader-Textalternative zur Flip-Animation,
aria-live-Regionen für Störungen/Ergebnisse
Browser-Unterstützung
Moderne Evergreen-Browser (Chrome, Firefox, Safari, Edge). Nutzt u. a.
fetch, IntersectionObserver-freies Vanilla-DOM, Service Worker, Push API,
CSS Custom Properties und dvh-Einheiten (mit vh-Fallback). Kein IE11-Support.
Bekannte Grenzen
- Die Live-Karte ist für blinde Nutzer naturgemäß eingeschränkt zugänglich (Leaflet-Karten sind visuell) – Abfahrtstafel und Routenplaner liefern dieselben Informationen barrierefrei.
- Störungstexte kommen direkt von VBB/HAFAS auf Deutsch – eine Übersetzung ins Englische findet nicht statt (nur die App-Oberfläche selbst ist zweisprachig).
- Fahrtalarm-Ton und Vollbild-Overlay funktionieren nur bei offenem Tab; bei geschlossenem Browser zeigt ein Push nur die Systembenachrichtigung.
ÖPNV Navi – Backend
Node.js/Express-Backend für die ÖPNV-Navi-App: bündelt VBB-HAFAS, DB-IRIS (echte Bahnhöfe), eine selbst gehostete OTP2-Instanz für die Routenplanung sowie Web-Push-Benachrichtigungen hinter einer gemeinsamen API.
Tech-Stack
- Node.js, Express
- hafas-client (VBB-Profil)
- DB-IRIS (XML, via
fast-xml-parser) für echte DB-Bahnhöfe - OTP2 (GraphQL GTFS API) für die Routenplanung – eigene, extern laufende OTP2-Instanz vorausgesetzt, wird hier nicht mitgeliefert
better-sqlite3für Favoriten, Verspätungsstatistik und Web-Push-Subscriptionsweb-pushfür Browser-Push-Benachrichtigungenhelmet,cors,express-rate-limit,morgan
Einrichtung
npm install
Umgebungsvariablen (.env)
| Variable | Pflicht | Beschreibung |
|---|---|---|
PORT |
nein | Port, auf dem der Server lauscht (Standard siehe server.js) |
HAFAS_USER_AGENT |
nein | User-Agent für HAFAS-Anfragen |
DB_PATH |
nein | Pfad zur SQLite-Datenbankdatei (Standard: ./data/oepnv-navi.sqlite) |
VAPID_PUBLIC_KEY |
für Web-Push | Öffentlicher VAPID-Schlüssel |
VAPID_PRIVATE_KEY |
für Web-Push | Geheim! Privater VAPID-Schlüssel |
VAPID_SUBJECT |
für Web-Push | mailto:deine@email.de |
VAPID-Keys einmalig erzeugen:
npx web-push generate-vapid-keys
Ohne VAPID_PUBLIC_KEY/VAPID_PRIVATE_KEY bleibt Web-Push deaktiviert
(Warnung im Log beim Start), der Rest der App läuft normal weiter.
Wichtig: VAPID_PRIVATE_KEY ist ein echtes Geheimnis – nicht ins
Git-Repo committen, .env gehört in die .gitignore.
Start
npm start # node src/server.js
npm run dev # mit --watch (Node 18+)
Ordnerstruktur
backend/
├── package.json
└── src/
├── server.js Express-App, Middleware, Routen-Mounting, Hintergrund-Jobs
├── db/
│ └── database.js SQLite-Setup + Schema (Favoriten, Verspätungen, Push)
├── middleware/
│ └── apiKey.js X-API-Key-Schutz (nur für /push/register, APNs)
├── routes/
│ ├── locations.js / stations Haltestellen-/Adress-/POI-Suche
│ ├── departures.js VBB-Abfahrten (+ Störungen)
│ ├── dbTimetables.js DB-IRIS-Abfahrten für echte Bahnhöfe (+ Störungen)
│ ├── summary.js Sammel-Endpunkt für mehrere Haltestellen (Favoriten)
│ ├── trip.js Fahrt-Details (Zwischenhalte, Live-Position)
│ ├── otpJourneys.js Routenplanung über die eigene OTP2-Instanz
│ ├── journeys.js Ältere HAFAS-basierte Routenplanung (Legacy)
│ ├── radar.js Fahrzeugpositionen im Kartenausschnitt
│ ├── disruptions.js Ortsbezogene Störungen
│ ├── remarks.js Netzweite Störungsmeldungen
│ ├── fares.js VBB-Tarifzonen + BVG-Preise
│ ├── favorites.js Server-seitige Favoriten (Legacy/optional)
│ ├── delayStats.js Verspätungsstatistik pro Linie
│ ├── webPush.js Web-Push: Subscribe/Unsubscribe/Fahrtalarm
│ ├── push.js APNs-Registrierung für die separate iOS-App
│ └── health.js Health-Dashboard (/health/full, /health/dashboard)
└── utils/
├── hafas.js Zentraler HAFAS-Client
├── vbbDepartures.js VBB-Abfahrten-Kernlogik + Verspätungs-Aufzeichnung
├── dbIris.js / dbDepartureMerge.js / dbDeparturesService.js
│ DB-IRIS-Kernlogik (Planwerte + Echtzeit-Änderungen mergen)
├── dbStations.js ~5.400 echte DB-Bahnhöfe (dynamischer Import, ESM)
├── otpClient.js OTP2-GraphQL-Client (Routenplanung, Umstiegszeit, Rollstuhl)
├── disruptionFilter.js "Echte Störung" vs. Rauschen, Gleisänderungs-Anreicherung
├── delayStats.js Aggregation der Verspätungsstatistik
├── tripInfo.js Gemeinsame Fahrt-Detail-Logik (Route + Push-Job)
├── webPush.js web-push-Wrapper (VAPID, Versand, Fehlerbehandlung)
├── pushStore.js DB-Zugriff für Push-Subscriptions/Favoriten/Fahrtalarme
├── pushJobs.js Hintergrund-Jobs: Störungs-Check (60s) & Fahrtalarm-Check (20s)
├── lineColors.js Linienfarben (vbb-line-colors + Fallback-Heuristik)
├── products.js Verkehrsmittel-Filter-Mapping
├── vbbFareZones.js / bvgPriceScraper.js
│ Tarifzonen + Preise, täglich automatisch aktualisiert
├── cache.js Einfacher In-Memory-TTL-Cache
└── hafasErrors.js Nutzerfreundliche Fehlermeldungen für HAFAS-Fehler
API-Endpunkte (Übersicht)
Alle Pfade relativ zum konfigurierten Basis-Pfad (z. B. /live_navi).
| Bereich | Endpunkte |
|---|---|
| Suche | GET /locations, GET /stations (Alias) |
| Abfahrten | GET /departures/:stationId, GET /db/departures/:evaNo |
| Sammel-Abfahrten | POST /summary/departures |
| Fahrt-Details | GET /trip/:id |
| Routenplanung | GET /otp/journeys (OTP2), GET /journeys (Legacy/HAFAS) |
| Live-Karte | GET /radar |
| Störungen | GET /disruptions/nearby, GET /remarks |
| Tarife | GET /fares |
| Favoriten | GET/POST/DELETE /favorites |
| Verspätungsstatistik | GET /stats/delays/:line |
| Web-Push | GET /webpush/public-key, POST /webpush/subscribe, POST /webpush/unsubscribe, POST/DELETE /webpush/trip-alarm |
| APNs (iOS-App) | POST /push/register |
| Health | GET /health/full, GET /health/dashboard |
Datenbank (SQLite)
Automatisch angelegt beim ersten Start (db/database.js):
favorites– server-seitige Favoriten (optional, Client nutzt primärlocalStorage)push_tokens– APNs-Geräte-Tokens (iOS-App)delay_samples– Verspätungs-Rohdaten pro Fahrt, passiv befüllt bei jeder normalen Abfahrten-Abfrage; alte Einträge (>90 Tage) werden täglich automatisch gelöschtpush_subscriptions– Web-Push-Abos (ein Datensatz pro Browser/Gerät)push_favorite_stations– Kopie der Favoriten-Haltestellen je Abo (für den Störungs-Job)push_trip_alarms– höchstens ein aktiver Fahrtalarm je Abo
Hintergrund-Jobs
Beim Start (server.js) registriert:
- Vorwärmen des DB-Bahnhofs-Caches
- Tägliches Neuladen der VBB-Tarifzonen und BVG-Preise
- Tägliches Aufräumen alter Verspätungs-Samples
- Störungs-Check (alle 60s): prüft für jede Web-Push-Subscription die verknüpften Favoriten-Haltestellen auf neue Störungen, verschickt bei Bedarf Push
- Fahrtalarm-Check (alle 20s): prüft aktive, per Push gespiegelte Fahrtalarme und löst bei Erreichen der Zielhaltestelle aus
Abhängigkeiten außerhalb dieses Repos
- Eine laufende OTP2-Instanz (VBB-GTFS + Berlin/Brandenburg-OSM) für die
Routenplanung – Adresse in
utils/otpClient.jskonfigurieren - Ein Reverse Proxy (z. B. NPM/nginx), der den gewünschten Pfad (z. B.
/live_navi) auf diesen Node-Prozess weiterleitet