243 lines
12 KiB
Markdown
243 lines
12 KiB
Markdown
# Ö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](https://leafletjs.com/) 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.js` muss im **Root** des ausgelieferten Verzeichnisses liegen (dort, wo
|
||
auch `index.html` liegt), nicht unter `js/`. 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 `localhost` während der Entwicklung).
|
||
- In `js/config.js` die Backend-URL eintragen:
|
||
```js
|
||
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](https://github.com/public-transport/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-sqlite3` für Favoriten, Verspätungsstatistik und Web-Push-Subscriptions
|
||
- `web-push` für Browser-Push-Benachrichtigungen
|
||
- `helmet`, `cors`, `express-rate-limit`, `morgan`
|
||
|
||
## Einrichtung
|
||
|
||
```bash
|
||
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:
|
||
|
||
```bash
|
||
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
|
||
|
||
```bash
|
||
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är `localStorage`)
|
||
- `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öscht
|
||
- `push_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.js` konfigurieren
|
||
- Ein Reverse Proxy (z. B. NPM/nginx), der den gewünschten Pfad (z. B.
|
||
`/live_navi`) auf diesen Node-Prozess weiterleitet
|