Il problema: navigare evitando certe zone
Tutti i GPS ti danno il percorso più veloce. Ma a volte vuoi evitare certe zone - un cantiere che blocca la strada ogni mattina, un quartiere che preferisci aggirare, una strada dove sai che c'è un autovelox.
È questa esigenza che ha dato origine a Wayline: una PWA di navigazione GPS dove definisci le tue zone di evitamento, e l'applicazione ricalcola automaticamente percorsi che le aggirano.
Ecco le scelte tecniche che ho fatto, e perché.
Lo stack: perché non un'app nativa?
Per la nav GPS, il riflesso è React Native o Flutter. Ho scelto PWA Next.js + Mapbox per diversi motivi:
- Niente validazione App Store: deploy istantaneo, aggiornamenti senza review
- Geolocalizzazione nativa via API Web:
navigator.geolocationfunziona bene quanto un'app nativa su iOS e Android recenti - Mapbox GL JS: motore di rendering vettoriale ultra-rapido, stesse capacità degli SDK nativi
- Service Worker: posso cachare la mappa e i percorsi per l'uso offline
- Niente attrito di installazione: l'utente apre l'URL, lo aggiunge alla home, fatto
L'unico vero trade-off: niente accesso alle notifiche push iOS fuori dalla PWA installata. Per Wayline non è critico - il GPS si usa uscendo, non in background.
Calcolare un percorso con evitamento di zone
È il cuore del progetto. Mapbox propone un'API di routing (Directions API) che prende dei waypoint e restituisce il percorso ottimale. Ma non sa "evitare tale poligono".
Il trucco: si calcola un percorso di base, si guarda se attraversa una zona di evitamento, e in caso affermativo, si inserisce un waypoint che forza la deviazione.
Per verificare l'intersezione tra la linea del percorso e il poligono, uso 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);
// Posiziona il waypoint leggermente all'esterno del nord della zona
return [bbox[0] - 0.01, bbox[3] + 0.01];
}
L'algoritmo semplificato:
- Chiamata a Mapbox Directions con origine + destinazione
- Per ogni zona definita: check se il percorso la attraversa
- Se sì, calcola un waypoint di deviazione
- Ri-chiamata a Directions con il waypoint inserito
- Ritorna il percorso finale
Restano 1-2 chiamate API per calcolo di percorso, ma l'esperienza è fluida (<300ms in media).
Archiviazione locale con SQLite
Per le zone di evitamento e lo storico dei percorsi, ho scelto SQLite lato server invece di Postgres o un servizio gestito. Perché:
- Wayline è un progetto personale, non serve scalare a migliaia di utenti
- SQLite gira nello stesso processo Node.js (
better-sqlite3), zero connection overhead - Backup semplici: un solo file
.db, posso fare rsync sul NAS - Lettura quasi istantanea per le zone dell'utente (tipicamente 5-20 zone)
Per le query spaziali (questa zona contiene questo punto?), Turf.js fa il lavoro lato server. Non serve PostGIS.
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 = ?');
Modalità offline con Service Worker
È il vero differenziatore PWA vs app nativa classica. Con un service worker, posso cachare:
- La shell dell'app (HTML/CSS/JS)
- I tile Mapbox già visitati (Mapbox GL gestisce questo nativamente con una cache configurata)
- Le zone dell'utente (memorizzate in
localStorage) - L'ultimo percorso calcolato
Risultato: l'utente può continuare a vedere la sua mappa e il suo percorso anche in perdita di rete, cosa che capita regolarmente in zona rurale o sotterraneo.
Setup minimo del service worker (con 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 } }
}
]
});
Le trappole incontrate
1. navigator.geolocation può mentire. Su alcuni browser mobili, la precisione varia da 5m a 500m da un frame all'altro. Ho dovuto implementare un Kalman filter semplificato per smorzare il tracking.
2. I tile Mapbox sono cari oltre il free tier. Se Wayline avesse traffico massiccio, passerei a un fornitore di tile open-source (Stadia Maps, Protomaps). Per ora, il free tier basta abbondantemente.
3. iOS Safari e il service worker. Apple ha le sue regole: i service worker vengono scaricati aggressivamente, la cache non è garantita. Per usi critici (un ciclista che deve poter consultare il suo percorso), raccomando di raddoppiare con localStorage.
Il bilancio
| Metrica | Valore |
|---|---|
| Bundle JS iniziale | ~140 KB gzip |
| Time to Interactive | <2s su 4G |
| Ricalcolo percorso con evitamento | 250-350 ms |
| Funzionamento offline | ✅ Sì (mappa + ultimo percorso) |
| Archiviazione lato utente | ~3 MB dopo diversi percorsi |
Cosa ne traggo
Per il 95% dei casi d'uso GPS, una PWA ben fatta sostituisce un'app nativa. L'unico vero interesse di un'app nativa per un progetto di navigazione è l'uso in background continuo (tipo Waze che ti parla in guida). Se non hai bisogno di questo, la PWA è più rapida da sviluppare, più facile da deployare, e offre un'esperienza paragonabile.
Mapbox + Turf.js è una combinazione potente: Mapbox per il rendering e il routing standard, Turf.js per tutto ciò che esce dalla loro API (intersezioni, buffer, distanze custom). Con questi due mattoni, puoi costruire praticamente qualsiasi logica geospaziale nel browser.
