Quando costruisci una ricerca per prossimità su milioni di punti GPS, prima o poi vai a sbattere contro lo stesso muro: una scansione brute-force sulle distanze è troppo lenta, e le coordinate lat/lng grezze non si indicizzano bene. I sistemi a griglia spaziale risolvono il problema agganciando le coordinate a celle pre-calcolate — lookup veloci, aggregazioni pulite, identificatori condivisibili. Geohash fa questo dal 2008. H3 ha portato gli esagoni nel mainstream quando Uber lo ha reso open source nel 2018. S2 è quello su cui gira Google Maps internamente. I tre sistemi non sono intercambiabili, e le differenze che sembrano accademiche in un prototipo diventano consequenziali quando i dati attraversano il confine di una cella o coprono più continenti.
Cosa sono i sistemi di indicizzazione a griglia spaziale?
I sistemi di indicizzazione a griglia spaziale dividono la superficie terrestre in celle discrete, assegnando a ciascuna un identificatore univoco. Invece di memorizzare coordinate grezze, si memorizza un ID di cella — il che consente lookup rapidi, query per intervallo e aggregazioni semplicemente confrontando prefissi di stringhe o valori interi. Il compromesso fondamentale è tra uniformità delle celle, semplicità di implementazione e performance delle query.
I tre sistemi nascono da culture ingegneristiche diverse. Geohash è stato inventato da Gustavo Niemeyer nel 2008 come sistema di geocodifica di pubblico dominio — è il più semplice per scelta progettuale. S2 è stato sviluppato in Google intorno al 2011 come libreria geometrica per l'infrastruttura cartografica interna. H3 è stato reso open source da Uber nel 2018 per risolvere il matching guidatore/passeggero su scala urbana.
Le forme delle celle riflettono queste origini: Geohash produce rettangoli, S2 produce quadrilateri sferici (approssimativamente quadrati su una sfera), H3 produce esagoni (con 12 pentagoni per chiudere la sfera).
Come funziona ciascun sistema
L'approccio di codifica di ogni sistema determina sia i punti di forza che i suoi punti critici in produzione.
H3 proietta la Terra su un icosaedro regolare (un poliedro a 20 facce), poi suddivide ogni faccia in esagoni. Il risultato sono 15 livelli di risoluzione in cui ogni cella si suddivide in circa 7 figli. Gli ID delle celle sono interi a 64 bit. La forma esagonale fa sì che ogni cella abbia esattamente 6 vicini che condividono un lato — nessuna ambiguità agli angoli.
Geohash intercala le rappresentazioni binarie dei bit di latitudine e longitudine, poi codifica il risultato come stringa Base32. Per questo le stringhe Geohash sono leggibili dall'uomo: u4pruydqqvj identifica univocamente una porzione di 3 m × 3 m nel centro di Parigi. La precisione è controllata dalla lunghezza della stringa (1–12 caratteri). La proprietà del prefisso — u4pru è sempre contenuto in u4pr — rende le query per intervallo banalmente semplici.
S2 dispiega la superficie terrestre sulle sei facce di un cubo, applica una curva di Hilbert a riempimento di spazio su ciascuna faccia, poi mappa le posizioni su interi a 64 bit. La curva di Hilbert preserva la località spaziale: punti vicini sulla Terra producono interi vicini. S2 ha 31 livelli di risoluzione e supporta la copertura di poligoni arbitrari tramite S2RegionCoverer, che approssima qualsiasi forma con il numero minimo di celle necessario.
Precisione e copertura — tabella di confronto
I tre sistemi usano scale di precisione incompatibili — le 15 risoluzioni di H3, le 12 lunghezze di caratteri di Geohash e i 31 livelli di S2 non si mappano direttamente l'uno sull'altro. La tabella seguente li allinea per area di cella approssimativa a tre scale pratiche: aggregazione a livello di città, routing di quartiere e precisione a livello di edificio.
Il restringimento dei gradi di longitudine segue una curva coseno e diventa significativo più velocemente di quanto la maggior parte degli sviluppatori si aspetti. A 30° di latitudine (Los Angeles, Cairo, Shanghai), un grado di longitudine copre 96,5 km rispetto ai 110,6 km di un grado di latitudine — un deficit est-ovest del 14,9%. A 60° (Stoccolma, Anchorage, San Pietroburgo), il rapporto arriva a 2:1: un grado di longitudine è largo solo 55,7 km. Una cella Geohash a 6 caratteri che copre ~0,74 km² all'equatore ne copre solo ~0,37 km² a 60°N. Se stai costruendo un servizio globale con soglie di prossimità uniformi (ad esempio, "entro 500 metri"), Geohash con un livello di precisione fisso darà risultati incoerenti in funzione della latitudine — dovresti usare hash più lunghi alle latitudini basse e più corti vicino ai poli per ottenere una copertura reale equivalente, il che complica la logica delle query. H3 e S2 compensano entrambi la distorsione sferica: H3 alla risoluzione 9 varia meno del 3% in area su tutte le latitudini.
| Scala | Risoluzione H3 | Area cella H3 | Lunghezza Geohash | Area cella Geohash | Livello S2 | Area cella S2 |
|---|---|---|---|---|---|---|
| Città | 5 | ~253 km² | 4 caratteri | ~780 km² | 10 | ~325 km² |
| Quartiere | 9 | ~0,11 km² | 6 caratteri | ~0,74 km² | 15 | ~0,32 km² |
| Edificio | 11 | ~0,0022 km² | 7 caratteri | ~0,023 km² | 18 | ~0,005 km² |
Ricerca dei vicini ed effetti ai confini
Le query sui vicini sono l'operazione più comune con un indice spaziale: "data questa cella, trovare tutte le celle adiacenti." I tre sistemi gestiscono questo con ergonomie molto diverse.
H3 ha il modello di vicinanza più pulito. Ogni esagono ha esattamente 6 vicini che condividono un lato — nessun vicino d'angolo, nessuna ambiguità. La funzione gridDisk(cell, k) restituisce tutte le celle entro k anelli con una singola chiamata. Un disco con k=1 restituisce 7 celle (incluso il centro); k=2 ne restituisce 19. Poiché gli esagoni hanno lati di uguale lunghezza su tutti i lati, le query di distanza basate sul conteggio degli anelli sono geometricamente consistenti.
Geohash ha 8 vicini (4 lati + 4 angoli). Per la maggior parte delle celle questo funziona bene. Il problema notorio si presenta ai confini della griglia: due celle geograficamente adiacenti possono avere stringhe Geohash che non condividono alcun prefisso. Ad esempio, celle a cavallo del Meridiano di Greenwich (0° di longitudine) o dell'equatore possono avere hash completamente diversi. Il sintomo classico: una query restituisce risultati da un lato del confine ma manca silenziosamente i punti appena dall'altra parte.
Un esempio reale ben documentato: La Roche-Chalais, nel sud-ovest della Francia, ha il prefisso Geohash u000, mentre Pomerol — una città a soli 30 km di distanza — ha il prefisso ezzz. Queste due località vicine non condividono alcun prefisso comune a nessuna lunghezza perché un confine di cella principale passa direttamente tra di loro. Una query di prossimità basata sul prefisso per "punti vicino a La Roche-Chalais" restituirebbe silenziosamente zero risultati da Pomerol. Lo stesso problema si verifica al Meridiano di Greenwich (0° di longitudine): due punti GPS a 10 metri di distanza su lati opposti del Meridiano ricevono hash che iniziano con e (lato ovest) e s (lato est) — nessun prefisso condiviso, nessun intervallo di indice comune. La soluzione standard è sempre interrogare la cella target più i suoi 8 vicini, poi filtrare per distanza Haversine effettiva. Il comando GEOSEARCH di Redis gestisce questo internamente, il che è uno dei motivi per cui il supporto nativo nel database è preferibile a implementare la propria logica di prossimità Geohash.
S2 usa una curva di Hilbert che fornisce forti garanzie di località — punti vicini sulla Terra si mappano a interi vicini nell'indice. Tuttavia, "vicino nello spazio di Hilbert" e "spazialmente adiacente" non sono sempre la stessa cosa. Le sei facce del cubo introducono giunture, e il calcolo dei vicini attraverso i confini di faccia richiede una gestione attenta. S2CellId::EdgeNeighbors() di S2 gestisce questo correttamente, ma richiede più lavoro di implementazione rispetto a gridDisk di H3.
Se i tuoi dati attraversano confini geografici — coste, confini nazionali, la linea del cambiamento di data — la topologia esagonale uniforme di H3 è l'opzione più sicura di default. I sistemi a griglia rettangolare come Geohash hanno giunture a longitudini e latitudini prevedibili, e fare debugging di un miss al confine in produzione è decisamente sgradevole.
Quando usare ciascun sistema
Se stai già usando Redis o Elasticsearch, usa Geohash — la ricerca per prossimità funziona out of the box senza codice aggiuntivo. Se hai bisogno di consistenza globale o visualizzazione esagonale, usa H3. Ricorri a S2 solo se devi indicizzare poligoni arbitrari o stai lavorando con infrastrutture Google esistenti. La tabella copre gli scenari più comuni:
| Caso d'uso | Consigliato | Motivazione |
|---|---|---|
| Comandi Redis GEO / Elasticsearch geo_point | Geohash | Supporto nativo integrato, zero configurazione |
| Heatmap della domanda per ride-sharing / food delivery | H3 | Dimensione cella costante + visualizzazione esagonale |
| Aggregazione dati globale (clima, telemetria) | H3 | Elimina la distorsione in latitudine polare |
| Indicizzazione di regioni poligonali arbitrarie | S2 | S2RegionCoverer approssima qualsiasi forma |
| Sistemi a griglia per giochi (Pokémon GO lo usa) | S2 | Scelta infrastrutturale originale di Niantic |
| Prototipi rapidi e geofencing semplice | Geohash | Curva di apprendimento minima, ID leggibili dall'uomo |
| Analisi multi-risoluzione (zoom avanti/indietro) | H3 | Gerarchia padre/figlio pulita |
| PostGIS / database spaziali | Geohash | Supporto standard nell'ecosistema GIS |
La maturità dell'ecosistema spesso determina la scelta prima ancora della geometria. Geohash è integrato in Redis dal 2015 (GEOADD, GEORADIUS), e il tipo di campo geo_point di Elasticsearch usa Geohash internamente per le aggregazioni a griglia. Se il tuo stack include già uno dei due, l'integrazione con Geohash richiede quasi nessun lavoro extra.
H3 ha ottime librerie Python (h3) e JavaScript (h3-js), un ecosistema crescente di strumenti di visualizzazione (il H3HexagonLayer di Deck.gl) e supporto di prima classe nell'estensione spaziale di DuckDB. S2 ha le operazioni geometriche più potenti ma il supporto JavaScript più debole — la libreria principale è in C++, con porting in altri linguaggi mantenuti dalla community.
Lavorare con H3, Geohash e S2 nel codice
Tutti e tre i sistemi hanno librerie JavaScript e Python. Gli esempi seguenti coprono le tre operazioni che userai di più: codificare una coordinata, trovare i vicini e ottenere il poligono del confine della cella. Per un confronto dettagliato tra h3-js e ngeohash come pacchetti npm — dimensioni del bundle, benchmark di codifica e insidie in produzione — consulta la nostra guida h3-js vs ngeohash.
Due problemi noti delle librerie da tenere a mente prima di andare in produzione:
Precisione intera di ngeohash: le funzioni encode_int / decode_int sono limitate a 52 bit in JavaScript (intervallo degli interi sicuri dello standard IEEE 754 float64). Per la maggior parte dei casi d'uso va bene, ma se imposti bitDepth sopra 52, i risultati perdono precisione silenziosamente. Usa la API stringa (encode / decode) a meno che tu non abbia un motivo specifico per lavorare con interi, e passa sempre lo stesso valore bitDepth in modo coerente a entrambe le chiamate di encode e decode.
Rappresentazione degli ID cella di h3-js: gli ID di cella H3 sono interi a 64 bit, che superano l'intervallo degli interi sicuri di JavaScript (2^53 − 1). La libreria h3-js li restituisce come stringhe per default (ad esempio, "8928308280fffff"). Un bug comune in produzione: se la tua API serializza gli ID H3 come numeri JSON invece di stringhe, alcuni parser JSON arrotonderanno silenziosamente il valore e corromperanno l'ID della cella. Tratta sempre gli ID H3 come stringhe in tutto il tuo stack — nello schema del database, nelle risposte API e nel codice frontend.
JavaScript
import { latLngToCell, gridDisk, cellToBoundary } from "h3-js";
import ngeohash from "ngeohash";
// --- H3 ---
// Encode coordinate to H3 cell (resolution 9 ≈ 0.1 km²)
const h3Cell = latLngToCell(37.7749, -122.4194, 9);
// → "8928308280fffff"
// Get all cells within 1 ring (7 cells including center)
const h3Neighbors = gridDisk(h3Cell, 1);
// Get cell boundary polygon (array of [lat, lng] pairs)
const h3Boundary = cellToBoundary(h3Cell);
// --- Geohash ---
// Encode coordinate to Geohash (precision 6 ≈ 0.74 km²)
const hash = ngeohash.encode(37.7749, -122.4194, 6);
// → "9q8yyk"
// Decode back to coordinate
const { latitude, longitude } = ngeohash.decode(hash);
// Get 8 neighbors (N, NE, E, SE, S, SW, W, NW)
const geohashNeighbors = ngeohash.neighbors(hash);
// Get cell bounding box [minLat, minLon, maxLat, maxLon]
const bbox = ngeohash.decode_bbox(hash);
Python
import h3
import geohash # pip install python-geohash
# --- H3 ---
cell = h3.latlng_to_cell(37.7749, -122.4194, 9)
neighbors = h3.grid_disk(cell, 1)
boundary = h3.cell_to_boundary(cell) # list of (lat, lng) tuples
# --- Geohash ---
hash_str = geohash.encode(37.7749, -122.4194, precision=6)
decoded_lat, decoded_lng = geohash.decode(hash_str)
neighbors = geohash.neighbors(hash_str) # dict with N/NE/E/SE/S/SW/W/NW keys
S2 in JavaScript richiede un porting non ufficiale della community (pacchetto npm s2-geometry) che è in ritardo rispetto alla libreria C++. Per un utilizzo serio di S2 in ambienti JS, l'approccio più pratico è chiamare un servizio Python/Java o usare Cloudflare Workers con WASM. In Python, la libreria s2sphere offre un'interfaccia pulita:
import s2sphere
# Encode coordinate to S2 cell (level 15 ≈ 0.32 km²)
latlng = s2sphere.LatLng.from_degrees(37.7749, -122.4194)
cell_id = s2sphere.CellId.from_lat_lng(latlng).parent(15)
# Get 4 edge neighbors
neighbors = cell_id.edge_neighbors()
# Cover an arbitrary polygon with S2 cells
region_coverer = s2sphere.RegionCoverer()
region_coverer.min_level = 10
region_coverer.max_level = 15
covering = region_coverer.get_covering(some_s2_region)
Puoi sperimentare con la codifica Geohash e H3 direttamente nel browser usando lo strumento GeoHash encoder di KunYu — nessuna installazione di librerie necessaria.
FAQ
H3 è migliore di Geohash?
Per nuovi progetti senza vincoli infrastrutturali esistenti, H3 è la scelta migliore di default: dimensioni delle celle uniformi, query sui vicini più pulite e nessun bug ai confini. Geohash vince quando hai bisogno dei comandi Redis GEO o delle aggregazioni Elasticsearch out of the box, o quando un ID leggibile dall'uomo è importante — le stringhe Geohash sono facili da leggere nei log; gli ID H3 come 8928308280fffff non lo sono.
Redis supporta H3?
Redis non ha supporto nativo per H3. I comandi GEO integrati (GEOADD, GEORADIUS, GEOSEARCH) usano Geohash internamente. Per usare H3 con Redis, codifica le coordinate in ID di cella H3 nel livello applicativo e memorizzali come chiavi stringa normali o in sorted set. A partire da Redis 7.x, non è previsto aggiungere primitive H3 al server core.
Cosa significa S2?
S2 è l'abbreviazione di Sphere² (sfera al quadrato), che fa riferimento al concetto matematico di mappare una sfera su una superficie bidimensionale. La libreria è stata sviluppata in Google ed è usata internamente in Google Maps, Google Earth e altri prodotti geo di Google.
Quante celle H3 coprono la Terra alla risoluzione 5?
Alla risoluzione 5, ci sono 2.016.842 celle H3 che coprono la superficie terrestre (inclusi i 12 pentagoni che chiudono l'icosaedro). Ogni cella copre circa 252,9 km², rendendo questa risoluzione utile per analisi a livello nazionale o di grandi aree metropolitane.
Posso convertire tra H3 e Geohash?
Non esiste una conversione matematica diretta. Le due griglie sono indipendenti — un esagono H3 alla risoluzione 9 e una cella Geohash a 6 caratteri coprono aree diverse e possono essere mappate solo in modo approssimativo. L'approccio pratico è decodificare entrambi i formati in una coordinata lat/lng, poi ricodificare nel formato target. Tieni presente che la cella risultante potrebbe non contenere perfettamente la cella originale a causa delle differenze di dimensione e forma.
Qual è la precisione massima di Geohash?
La specifica Geohash supporta fino a 12 caratteri, producendo celle di circa 3,7 cm × 1,9 cm. In pratica, la maggior parte delle applicazioni usa 6–9 caratteri (da 1 km² a ~7 m²). Oltre i 9 caratteri, la precisione supera quella che la maggior parte dell'hardware GPS può fornire, e le chiavi di stringa lunghe creano un overhead inutile di storage e indicizzazione.