FabioBiondi.
← Blog
ux13 min di lettura

Le API di Google Maps 3D sono fantastiche: un esperimento con i miei trail in Mountain Bike

Un esperimento per provare le API 3D di Google Maps creando una visualizzazione 3D dei miei percorsi preferiti in Mountain Bike: dalla traccia GPX agli itinerari sul terreno, fino alle animazioni e ai marker informativi.

uxgoogle-maps3dgpsangular
Le API di Google Maps 3D sono fantastiche: un esperimento con i miei trail in Mountain Bike

Mi piace sperimentare. È una delle cose che amo di più di questo lavoro: prendere una tecnologia che non conosco a fondo, un'idea sciocca che mi frulla in testa, e passare un paio di sere a vedere "fin dove arriva".

E c'è un motivo per cui oggi lo trovo ancora più importante di prima. Siamo in un'epoca in cui buona parte del codice non lo scriviamo più riga per riga: lo descriviamo, lo generiamo, lo orchestriamo. Proprio per questo conoscere i mezzi che abbiamo a disposizione, le piattaforme, le API, gli strumenti, è diventato il vero valore aggiunto.

Non basta saper chiedere e avere molta fantasia: bisogna sapere come e cosa è possibile chiedere, quali sono i limiti, dove si nasconde la complessità e quanto costa (in soldi veri) ogni scelta.

Quindi, invece dell'ennesima riflessione astratta, vi racconto un esperimento concreto, codice alla mano.

Il progetto: analizzare i trail MTB in 3D#

Sono un appassionato di mountain bike e snowboard. Ho una cartella piena di tracce GPX scaricate da Strava, Komoot e dal GPS in un macello di formati diversi (.gpx, .kml, persino .osm). Volevo una cosa sola: caricare un trail, vederlo disegnato su una mappa 3D fotorealistica di Google, e tirar fuori i numeri che a noi biker interessano davvero: lunghezza percorso, dislivello in discesa, pendenza massima, velocità di punta, difficoltà tecnica, e così via.

Qui puoi vedere la demo che ho realizzato con tanto di commento audio in cui descrivo le funzionalità:

Demo MTB Trail 3D

Il prossimo step sarà quello di crearmi un'applicazione mobile per monitorare in automatico i miei tempi durante le discese senza la necessità di dover cliccare su START e STOP, ma che faccia tutto in automatico sulla base dei percorsi già disponibili sulla mappa e che li confronti, inoltre, con i tempi dei miei amici e dei professionisti.

Ma questo è solo l'inizio. Lato business, culturale, enogastronomico, le possibilità sono infinite: agenzie immobiliari, agriturismi, eventi sportivi, sopralluoghi tecnici, logistica e flotte. Tutto quello che ha a che fare con dati geografici può essere visualizzato in 3D e arricchito di informazioni.

L'ho costruita con Angular e TypeSCript, pezzo per pezzo (ovviamente con il supporto di Gemini e Claude Code), ma la parte più interessante, ed è il motivo di questo articolo, è arrivato con le API 3D di Google Maps.

FrontEnd e AI#

Prima di passare alla parte più tecnica, un paio di parole sullo sviluppo FrontEnd oggi. Infatti, c'è un altro aspetto che mi sta a cuore, soprattutto in questa fase storica.

Se scrivere "buon codice" è una competenza che si sta progressivamente democratizzando, quello che fa davvero la differenza agli occhi di un cliente o di un utente si è spostato altrove: nell'esperienza.

Proporre soluzioni innovative, all'avanguardia, con una UI curata e una UX pensata davvero, oggi è un vantaggio competitivo enorme. Perché se il codice "che funziona" lo sa generare un po' chiunque, quello che resta scolpito nella testa di chi usa la tua app è ben altro, un'animazione della camera che ti porta sopra la casa, la mappa 3D che "si racconta da sola", il dettaglio che non ti aspettavi. È quel "wow" che nessun prompt ti regala se prima non hai capito cosa è possibile costruire e come renderlo memorabile.

Quindi, per quanto riguarda lo sviluppo FrontEnd, il valore non è più nel "saperlo fare", è nell'immaginare qualcosa che gli altri non hanno nemmeno pensato di chiedere, e nel saperlo realizzare con cura.

Prima i dati: parser e matematica (la parte noiosa, in breve)#

Ma torniamo al progetto.
I percorsi degli itinerari sono forniti da diversi siti web e applicazioni di terze parti.
Tuttavia, ci sono diversi formati di esportazione e ogni formato, a sua volta può avere specificità. Quindi serviva un punto d'ingresso unico che rilevasse il formato e facesse il parsing, scartando i punti rotti (coordinate non valide, altitudini mancanti):

track-loader.service.ts
async load(url: string, format?: TrackFormat): Promise<Track[]> {
  const res = await fetch(url);
  if (!res.ok) throw new Error(`Failed to load ${url}: ${res.status}`);
  const text = await res.text();
  const fmt = format ?? this.detectFormat(url); // gpx | kml | osm
  return this.parse(text, fmt);
}

Poi la matematica: per la distanza reale tra due punti GPS serve la formula dell'emisenoverso (haversine), che tiene conto della curvatura terrestre. E soprattutto il calcolo del dislivello con un filtro del rumore: il GPS balla di un metro in su e in giù di continuo, e se sommi tutto ti ritrovi "2000m di salita" su un sentiero piatto.

Distanza + filtro del jitter GPS
function haversine(a: TrackPoint, b: TrackPoint): number {
  const dLat = toRad(b.lat - a.lat);
  const dLng = toRad(b.lng - a.lng);
  const h = Math.sin(dLat / 2) ** 2 +
    Math.cos(toRad(a.lat)) * Math.cos(toRad(b.lat)) * Math.sin(dLng / 2) ** 2;
  return 2 * EARTH_RADIUS_M * Math.asin(Math.sqrt(h));
}
 
// dislivello: ignora i micro-saltelli sotto 1 metro
const dEle = elevAt(curr) - elevAt(prev);
if (dEle > ELEV_NOISE_M) ascentM += dEle;
else if (dEle < -ELEV_NOISE_M) descentM += -dEle;

Roba importante. Fondamentale direi ma di cui onestamente non sapevo molto.
Ad ogni modo, ho risolto facilmente il problema (o quanto meno abbozzato) con un po' di domande ai soliti amici Claudio e Gennaro (Claude e Gemini). Il divertimento vero comincia adesso.

Il cuore: le mappe 3D di Google#

Google Maps Platform, da un po' di tempo, espone le Photorealistic 3D Tiles: lo stesso terreno fotorealistico che vedi in Google Earth, ma pilotabile via codice dalla Maps JavaScript API, con la libreria maps3d. Niente più mappa piatta vista dall'alto: hai colline, edifici, alberi, ombre, e ci puoi appoggiare sopra i tuoi dati, i tuoi modelli 3d e tanto altro.

La mappa 3D fotorealistica della zona di Monfalcone(Gorizia)
La mappa 3D fotorealistica della zona di Monfalcone(Gorizia)

Google offre due strade per usarle. La prima è dichiarativa, con dei web component (<gmp-map-3d>, <gmp-polyline-3d> e affini): componi la scena quasi come scrivessi HTML, settando degli attributi. Comoda per partire, ed è così che ho buttato giù il primo prototipo.

Ma appena ho iniziato a volerne di più, leggere la camera a ogni frame, orchestrare animazioni concatenate, ridisegnare gli overlay senza ricostruire la mappa, gli attributi stringa hanno iniziato a stare stretti. Così sono passato all'API imperativa: importLibrary('maps3d') e poi istanze vere (new Map3DElement, new Polyline3DElement, new Marker3DElement) che manipolo come oggetti, con i loro metodi e i loro eventi. Stessa potenza, molta più flessibilità.

1. Creare l'istanza della mappa 3D#

Tutto parte dall'importLibrary e da un Map3DElement. Definisci dove guardare (center, con tanto di altitude), quanto sei lontano (range) e l'inclinazione (tilt):

map-3d.service.ts
const { Map3DElement } = await google.maps.importLibrary('maps3d') as
  google.maps.Maps3DLibrary;
 
const map = new Map3DElement({
  center: { lat: 45.806, lng: 13.534, altitude: 400 }, // zona Monfalcone
  range: 2500,   // distanza della camera dal punto centrale (metri)
  tilt: 65,      // 0 = dall'alto, 90 = verso l'orizzonte
  heading: 0,    // rotazione bussola
});
 
this.container.nativeElement.appendChild(map);

Da qui in poi la mappa è un nodo del DOM a cui appendi gli elementi. Pulito.

2. Dalla traccia GPX alla polyline sul terreno#

Questo è il momento che aspettavo: prendere i punti del trail e disegnarli sulla collina vera. Si usa Polyline3DElement, e la chiave è l'altitudeMode.

La scelta "ovvia" sarebbe CLAMP_TO_GROUND, ovvero "incolla la linea al terreno nudo". Il problema: nelle tile fotorealistiche il "terreno" include alberi ed edifici, e una linea incollata al suolo spariva sotto le chiome degli alberi. La soluzione è stata RELATIVE_TO_MESH: la linea si adagia sopra l'intera mesh 3D (suolo + alberi + edifici), restando sempre visibile.

C'è anche un dettaglio non banale del runtime: passare queste proprietà nel costruttore non sempre veniva applicato durante l'upgrade dell'elemento, drawsOccludedSegments restava al default e la linea sfarfallava quando il terreno la occludeva. La soluzione è stata quella di impostare le proprietà dopo la costruzione, via setter:

Disegnare il trail
const { Polyline3DElement } =
  await google.maps.importLibrary('maps3d') as google.maps.Maps3DLibrary;
 
const line = new Polyline3DElement();
line.altitudeMode = 'RELATIVE_TO_MESH'; // sopra la mesh: niente sparizioni sotto gli alberi
line.strokeColor = trail.color;
line.strokeWidth = 28;
line.outerColor = '#ffffff';            // casing bianco, per staccare dal terreno
line.outerWidth = 0.5;
line.drawsOccludedSegments = false;
line.path = track.points.map(p => ({ lat: p.lat, lng: p.lng, altitude: 0 }));
 
map.appendChild(line);

È il tipo di cosa che non trovi nella doc "happy path": la scopri solo quando la linea sparisce e ti tocca capire il motivo.

La polyline del trail sulla mesh 3D, sopra alberi ed edifici
La polyline del trail sulla mesh 3D, sopra alberi ed edifici

3. Il volo di camera (fly around)#

Qui l'app smette di sembrare una mappa e inizia a sembrare un trailer, un filmato. La libreria offre flyCameraTo (per l'avvicinamento cinematografico iniziale) e flyCameraAround (per girare attorno a un punto). All'avvio faccio prima un volo dalla quota stratosferica fino al trail con flyCameraTo, poi, appena finisce, un'orbita lenta attorno al punto di arrivo:

Orbita attorno al trail
map.flyCameraAround({
  camera: INITIAL_CAMERA,   // il punto attorno a cui ruotare
  durationMillis: 50_000,   // un giro lento e disteso
  repeatCount: 1,
});

Un volo orbitale lento, e il sentiero (o una qualunque zona della città) si racconta da solo: dove c'è pendenza, dove il bosco si chiude, dove arriva l'aperto. Un effetto che una foto statica si sogna.

Guarda il video demo (no audio):

Volo orbitale (flyCameraAround) attorno al trail

4. I marker di partenza (e le etichette native)#

Ultimo pezzo: i punti d'interesse. Per ogni trail piazzo un marker sul punto di partenza con Marker3DElement con estruso di qualche metro sul terreno così da emergere rispetto gli alberi, e con il nome del percorso:

Marker di partenza
const { Marker3DElement } =
  await google.maps.importLibrary('maps3d') as google.maps.Maps3DLibrary;
 
const marker = new Marker3DElement({
  position: { lat: start.lat, lng: start.lng, altitude: 15 },
  altitudeMode: 'RELATIVE_TO_GROUND', // l'altitudine è l'altezza SUL terreno
  extruded: true,                     // gambo verticale, così non resta sepolto
  label: trail.name,
});
 
map.appendChild(marker);
I marker di partenza estrusi sul terreno, con la label del percorso
I marker di partenza estrusi sul terreno, con la label del percorso

Per i veri "hotspot" della mappa (negozi, fontane, rifugi) c'è una scorciatoia: basta passare la mappa da SATELLITE a HYBRID e Google disegna da sé le sue etichette POI. Nell'app è una semplice checkbox:

Accendere le etichette POI native
map.mode = showPoi ? 'HYBRID' : 'SATELLITE';

Polyline adagiata sulla mesh, volo orbitale, marker di partenza: tre API, ed era pronto lo "scheletro" di qualcosa che sembra un prodotto vero.

La demo#

Qui sotto puoi vedere una demo che ho creato per la mia zona (Monfalcone) e alcune aree del Friuli Venezia Giulia, disegnato sulla mappa 3D di Google con tanto di volo di camera iniziale, animazioni tra gli itinerari, hotspot e cosi via.

Aumentate il volume, perché c'è anche il mio commento audio.

Demo MTB Trail 3D

Non è solo per i biker...#

Mentre la costruivo mi sono venute in mente mille idee.
Il "motore" di questa app, come il caricamento dati geografici, disegno sulla mappa 3D, estrazione metriche, ecc , non ha niente a che fare con la MTB (Mountain Bike). È un'impalcatura riutilizzabile. Esempi che mi sono venuti in mente:

  • Agenzie immobiliari 🏠: invece dei trail, farlo per gli immobili. Mappa 3D del quartiere, pin sulle proprietà, e per ognuna distanza a piedi da scuole/fermate, esposizione solare, vista. Il volo di camera che ti porta "sopra" la casa fa un effetto che una foto statica si sogna.
  • Logistica e flotte 🚚: le stesse tracce GPS, ma di furgoni. Quanti km reali, soste, velocità medie per tratta. Il codice del calcolo velocità è già lì.
  • Agriturismi e turismo outdoor 🥾: percorsi trekking, punti panoramici, rifugi, con dislivelli e tempi di percorrenza generati in automatico.
  • Eventi sportivi 🏃: profili altimetrici di gare di trail running o granfondo, da pubblicare sul sito dell'evento.
  • Catasto / sopralluoghi tecnici 📐: perimetri di terreni da file .osm, aree e confini visualizzati in 3D.

Stesso scheletro, dati diversi. Cambi i marker, cambi la sorgente, e hai un'altra applicazione.

Parliamo di soldi (e come difenderci)#

Ma non è tutto gratuito: le API di Google Maps si pagano a sessione, e non sono a buon prezzo, soprattutto quelle 3D. È esattamente il tipo di cosa che intendevo quando dicevo che conoscere lo strumento conta: se non sai come fatturano, ti ritrovi un conto salato per aver lasciato la mappa 3D attiva a ogni reload in fase di sviluppo.

Quindi nell'app ho dovuto difendermi.

  1. La mappa parte con una visualizzazione in 2Ds (molto più economica) e il 3D è opt-in.
Difendersi dai costi delle tile 3D
// Default 2D
protected readonly viewMode = signal<'2d' | '3d'>('2d');
  1. Ma non è tutto: quando utilizzi l'AI per programmare, capita spesso che il progetto si ricarichi in automatico dozzine di volte. Ogni volta che l'API 3D viene invocata, Google fattura. Quindi ho dovuto fare attenzione a non ricaricare la mappa a ogni refresh della pagina.

Ho fatto banalmente due cose:

  • Prima di caricare questa demo, sono obbligato a cliccare su un pulsante START. Non l'ho messo nel video ma nella realtà la mia demo funzionava in questo modo. Così evito a monte che ad ogni reload venga conteggiata una sessione di Google Map.
Difendersi dai costi delle tile 3D
// La mappa (e il suo consumo) viene conteggiata
// solo su richiesta, al click su LOAD MAP.
protected readonly mapLoaded = signal(false);
  • Su Google Cloud Console ho impostato una quota massima di 300 richieste al giorno a questa API Key. In realtà la quota mensile gratuita è di circa 1000 richieste, ma in questo modo avrei avuto un segnale (un errore di quota exceeded) nel momento in cui stessi per superare la soglia, e non un conto salato a fine mese.

BONUS: su Google Cloud Console consiglio di limitare l'utilizzo dell'API KEY a specifici domini, cosi da evitare che, in caso di perdita o furto, qualcuno la usi per scopi malevoli a tuo nome.

In conclusione#

Non ho fatto nulla di rivoluzionario: ho preso dei dati che già avevo, un'API che non conoscevo, e ci ho giocato finché non ha preso forma. Ma è proprio questo il punto.

Oggi che generare codice è sempre più facile, la parte che fa davvero la differenza si è spostata: sapere cosa è possibile, con quali strumenti, a quali condizioni e a quale costo: come funzionano le API 3D di Google Maps, la differenza tra l'API dichiarativa e quella imperativa, l'altitudeMode giusto per evitare sovrapposizioni tra polyline e mesh 3d, il volo di camera, il modello di pricing, sono cose che scopri solo mettendoci le mani ma soprattutto leggendo la documentazione (RTFM).

Lo so, leggere la documentazione è un lavoro che richiede tempo, pazienza e curiosità. Ma devi farlo : )

Quindi il consiglio è banale ma sincero: scegliete una tecnologia che vi incuriosisce, leggetevi la documentazione, analizzate il modello di pricing e costruiteci una scemenza. Un esperimento piccolo, vostro, senza pressioni. È il modo migliore che conosco per imparare davvero uno strumento prima di andare in produzione o di proporlo ad un cliente.
E, diciamo la verità, è anche uno dei momenti che divertono di più. 🚀

Alla prossima, e occhio ai sassi in discesa (il nostro Carso, in FVG,s ne è pieno ahimè). 🚵