Das Problem: navigieren und dabei bestimmte Zonen meiden
Jedes GPS gibt dir die schnellste Route. Aber manchmal willst du bestimmte Zonen meiden - eine Baustelle, die jeden Morgen die Straße blockiert, ein Viertel, das du lieber umfährst, eine Straße, auf der du weißt, dass ein Blitzer steht.
Genau dieser Bedarf hat Wayline ins Leben gerufen: eine GPS-Navigations-PWA, in der du deine Vermeidungszonen definierst, und die Anwendung berechnet automatisch Routen neu, die sie umgehen.
Hier sind die technischen Entscheidungen, die ich getroffen habe, und warum.
Der Stack: warum keine native App?
Für GPS-Navigation ist der Reflex React Native oder Flutter. Ich habe PWA Next.js + Mapbox aus mehreren Gründen gewählt:
- Keine App-Store-Validierung: sofortiges Deployment, Updates ohne Review
- Native Geolokalisierung über die Web-API:
navigator.geolocationfunktioniert genauso gut wie eine native App auf aktuellen iOS und Android - Mapbox GL JS: ultraschnelle Vektor-Rendering-Engine, gleiche Fähigkeiten wie native SDKs
- Service Worker: ich kann Karte und Routen für die Offline-Nutzung cachen
- Keine Install-Reibung: der Nutzer öffnet die URL, fügt sie zum Homescreen hinzu, fertig
Der einzige echte Trade-off: kein Zugriff auf iOS-Push-Notifications außerhalb einer installierten PWA. Für Wayline nicht kritisch - das GPS wird beim Rausgehen genutzt, nicht im Hintergrund.
Eine Route mit Zonenvermeidung berechnen
Das ist der Kern des Projekts. Mapbox bietet eine Routing-API (Directions API), die Waypoints nimmt und die optimale Route zurückgibt. Aber sie kann nicht "dieses Polygon meiden".
Der Trick: man berechnet eine Basis-Route, schaut, ob sie eine Vermeidungszone durchquert, und falls ja, fügt man einen Waypoint ein, der den Umweg erzwingt.
Um die Schnittmenge zwischen Routenlinie und Polygon zu prüfen, nutze ich Turf.js:
import * as turf from '@turf/turf';
function routeIntersectsZone(routeGeoJSON, zonePolygon) {
return turf.booleanIntersects(routeGeoJSON, zonePolygon);
}
function findDetourWaypoint(zonePolygon) {
const center = turf.centroid(zonePolygon);
const bbox = turf.bbox(zonePolygon);
// Platziert den Waypoint leicht außerhalb der Nordseite der Zone
return [bbox[0] - 0.01, bbox[3] + 0.01];
}
Der vereinfachte Algorithmus:
- Aufruf von Mapbox Directions mit Ursprung + Ziel
- Für jede definierte Zone: prüfen, ob die Route sie durchquert
- Falls ja, einen Detour-Waypoint berechnen
- Erneuter Aufruf von Directions mit dem eingefügten Waypoint
- Finale Route zurückgeben
Bleibt bei 1-2 API-Aufrufen pro Routenberechnung, aber das Erlebnis ist flüssig (<300ms im Schnitt).
Lokale Speicherung mit SQLite
Für die Vermeidungszonen und die Routen-Historie habe ich serverseitiges SQLite gewählt statt Postgres oder einem Managed Service. Warum:
- Wayline ist ein persönliches Projekt, kein Bedarf, auf Tausende User zu skalieren
- SQLite läuft im selben Node.js-Prozess (
better-sqlite3), zero connection overhead - Einfache Backups: eine einzige
.db-Datei, ich kann sie auf den NAS rsyncen - Quasi-instantane Lesezugriffe für die Zonen des Nutzers (typischerweise 5-20 Zonen)
Für räumliche Abfragen (enthält diese Zone diesen Punkt?) erledigt Turf.js den Job serverseitig. Kein PostGIS nötig.
const Database = require('better-sqlite3');
const db = new Database('wayline.db');
db.exec(`
CREATE TABLE IF NOT EXISTS zones (
id INTEGER PRIMARY KEY,
user_id TEXT NOT NULL,
name TEXT,
geojson TEXT NOT NULL,
created_at INTEGER
);
`);
const getUserZones = db.prepare('SELECT * FROM zones WHERE user_id = ?');
Offline-Modus mit Service Worker
Das ist der echte Differenzierer PWA vs klassische native App. Mit einem Service Worker kann ich cachen:
- Die App-Shell (HTML/CSS/JS)
- Bereits besuchte Mapbox-Tiles (Mapbox GL handhabt das nativ mit einem konfigurierten Cache)
- Die Zonen des Nutzers (in
localStoragegespeichert) - Die zuletzt berechnete Route
Resultat: der Nutzer kann seine Karte und seine Route weiter sehen, auch bei Netzverlust, was regelmäßig in ländlichen Gebieten oder im Untergeschoss vorkommt.
Minimales Service-Worker-Setup (mit next-pwa):
// next.config.js
const withPWA = require('next-pwa')({
dest: 'public',
runtimeCaching: [
{
urlPattern: /^https:\/\/api\.mapbox\.com\/styles\//,
handler: 'CacheFirst',
options: { cacheName: 'mapbox-tiles', expiration: { maxAgeSeconds: 7 * 24 * 60 * 60 } }
}
]
});
Die Stolperfallen
1. navigator.geolocation kann lügen. Auf manchen mobilen Browsern variiert die Genauigkeit zwischen 5m und 500m von einem Frame zum nächsten. Ich musste einen vereinfachten Kalman-Filter implementieren, um das Tracking zu glätten.
2. Mapbox-Tiles werden jenseits des Free Tiers teuer. Hätte Wayline massiven Traffic, würde ich auf einen Open-Source-Tile-Anbieter wechseln (Stadia Maps, Protomaps). Aktuell reicht das Free Tier locker.
3. iOS Safari und der Service Worker. Apple hat seine eigenen Regeln: Service Worker werden aggressiv entladen, der Cache ist nicht garantiert. Für kritische Use Cases (ein Radfahrer, der seine Route abrufen können muss) empfehle ich, mit localStorage zu doppeln.
Die Bilanz
| Metrik | Wert |
|---|---|
| Initiales JS-Bundle | ~140 KB gzip |
| Time to Interactive | <2s über 4G |
| Routen-Neuberechnung mit Vermeidung | 250-350 ms |
| Offline-Betrieb | ✅ Ja (Karte + letzte Route) |
| User-seitige Speicherung | ~3 MB nach mehreren Routen |
Was ich daraus mitnehme
Für 95% der GPS-Use-Cases ersetzt eine gut gemachte PWA eine native App. Der einzige echte Vorteil einer nativen App für ein Navigationsprojekt ist die kontinuierliche Hintergrund-Nutzung (à la Waze, der dir beim Fahren etwas sagt). Brauchst du das nicht, ist die PWA schneller zu entwickeln, leichter zu deployen, und bietet ein vergleichbares Erlebnis.
Mapbox + Turf.js ist eine kraftvolle Kombination: Mapbox für Rendering und Standard-Routing, Turf.js für alles, was außerhalb ihrer API liegt (Schnittmengen, Buffer, Custom-Distanzen). Mit diesen beiden Bausteinen kannst du nahezu jede geo-räumliche Logik im Browser bauen.
