KunYu
h3geohashs2spatial-indexgis-basics

H3 vs Geohash vs S2 : Choisir le bon index spatial

Comparaison des index spatiaux H3, Geohash et S2 : tableaux de précision, distorsion en latitude, bugs de frontière, exemples de code en JS et Python, et matrice de décision.

KunYu TeamMarch 12, 202614 min de lecture

Quand on construit une recherche de proximité sur des millions de points GPS, on finit toujours par buter sur le même mur : un scan de distance par force brute est trop lent, et les coordonnées brutes lat/lng s'indexent mal. Les systèmes de grille spatiale résolvent ce problème en rattachant les coordonnées à des cellules précalculées — requêtes rapides, agrégation propre, identifiants partageables. Geohash fait ça depuis 2008. H3 a popularisé les hexagones quand Uber l'a rendu open-source en 2018. S2, c'est ce qui fait tourner Google Maps en interne. Ces trois systèmes ne sont pas interchangeables, et les différences qui semblent anodines sur un prototype deviennent critiques quand vos données croisent une frontière de cellule ou couvrent plusieurs continents.

Qu'est-ce qu'un système d'indexation spatiale en grille ?

Les systèmes d'indexation spatiale en grille divisent la surface de la Terre en cellules discrètes, en assignant à chacune un identifiant unique. Au lieu de stocker des coordonnées brutes, on stocke un ID de cellule — ce qui permet des recherches rapides, des requêtes par plage, et des agrégations de données en comparant simplement des préfixes de chaînes ou des valeurs entières. Le compromis central est entre l'uniformité des cellules, la simplicité d'implémentation et les performances des requêtes.

Les trois systèmes sont nés de cultures d'ingénierie différentes. Geohash a été inventé par Gustavo Niemeyer en 2008 comme système de géocodage dans le domaine public — c'est le plus simple par conception. S2 a été développé chez Google vers 2011 comme bibliothèque de géométrie pour leur infrastructure cartographique interne. H3 a été mis en open-source par Uber en 2018 pour résoudre le matching conducteur/passager à l'échelle d'une ville.

La forme de leurs cellules reflète ces origines : Geohash produit des rectangles, S2 produit des quadrilatères sphériques (à peu près carrés sur une sphère), et H3 produit des hexagones (avec 12 pentagones pour fermer la sphère).

Comment fonctionne chaque système

L'approche d'encodage de chaque système détermine à la fois ses forces et ses points de défaillance en production.

H3 projette la Terre sur un icosaèdre régulier (un polyèdre à 20 faces), puis subdivise chaque face en hexagones. Le résultat est 15 niveaux de résolution où chaque cellule se subdivise en environ 7 enfants. Les IDs de cellules sont des entiers 64 bits. La forme hexagonale signifie que chaque cellule a exactement 6 voisins partageant une arête — sans ambiguïté de coin.

Geohash entrelace les représentations binaires des bits de latitude et de longitude, puis encode le résultat en chaîne Base32. C'est pourquoi les chaînes Geohash sont lisibles : u4pruydqqvj identifie de façon unique une parcelle de 3 m × 3 m au centre de Paris. La précision est contrôlée par la longueur de la chaîne (1 à 12 caractères). La propriété de préfixe — u4pru est toujours à l'intérieur de u4pr — rend les requêtes par plage triviales.

S2 déplie la surface de la Terre sur les six faces d'un cube, applique une courbe de Hilbert remplissant l'espace sur chaque face, puis mappe les positions sur des entiers 64 bits. La courbe de Hilbert préserve la localité spatiale : des points proches sur Terre produisent des entiers proches. S2 dispose de 31 niveaux de résolution et supporte la couverture de polygones arbitraires via S2RegionCoverer, qui approxime n'importe quelle forme avec le nombre minimal de cellules nécessaires.

Précision et couverture — tableau comparatif

Les trois systèmes utilisent des échelles de précision incompatibles — les 15 résolutions de H3, les 12 longueurs de caractères de Geohash et les 31 niveaux de S2 ne se correspondent pas. Le tableau ci-dessous les aligne par superficie de cellule approximative à trois échelles pratiques : agrégation à l'échelle d'une ville, routage de quartier, et précision au niveau d'un bâtiment.

La réduction des degrés de longitude suit une courbe cosinus et devient sévère plus vite que la plupart des développeurs ne l'anticipent. À 30° de latitude (Los Angeles, Le Caire, Shanghai), un degré de longitude couvre 96,5 km contre 110,6 km pour un degré de latitude — un déficit est-ouest de 14,9 %. À 60° (Stockholm, Anchorage, Saint-Pétersbourg), le rapport atteint 2:1 : un degré de longitude ne fait plus que 55,7 km de large. Une cellule Geohash à 6 caractères qui couvre ~0,74 km² à l'équateur n'en couvre plus que ~0,37 km² à 60°N. Si vous construisez un service mondial avec des seuils de proximité uniformes (par exemple, « dans un rayon de 500 mètres »), Geohash à un niveau de précision fixe donnera des résultats incohérents selon la latitude — il faudrait utiliser des hash plus longs aux basses latitudes et plus courts près des pôles pour obtenir une couverture réelle équivalente, ce qui complexifie votre logique de requête. H3 et S2 compensent tous les deux la distorsion sphérique : H3 à la résolution 9 varie de moins de 3 % en superficie sur toutes les latitudes.

Échelle Résolution H3 Superficie cellule H3 Longueur Geohash Superficie cellule Geohash Niveau S2 Superficie cellule S2
Ville 5 ~253 km² 4 chars ~780 km² 10 ~325 km²
Quartier 9 ~0,11 km² 6 chars ~0,74 km² 15 ~0,32 km²
Bâtiment 11 ~0,0022 km² 7 chars ~0,023 km² 18 ~0,005 km²

Recherche de voisins et effets de frontière

Les requêtes de voisinage sont l'opération la plus courante sur un index spatial : « à partir de cette cellule, trouver toutes les cellules adjacentes. » Les trois systèmes gèrent cela avec des ergonomies très différentes.

H3 a le modèle de voisinage le plus propre. Chaque hexagone a exactement 6 voisins partageant une arête — pas de voisins de coin, pas d'ambiguïté. La fonction gridDisk(cell, k) retourne toutes les cellules dans k anneaux en un seul appel. Un disque k=1 retourne 7 cellules (centre inclus) ; k=2 en retourne 19. Comme les hexagones ont une longueur d'arête égale de tous les côtés, les requêtes de distance par comptage d'anneaux sont géométriquement cohérentes.

Geohash a 8 voisins (4 arêtes + 4 coins). Pour la plupart des cellules, ça fonctionne bien. Le problème notoire se produit aux frontières de grille : deux cellules géographiquement adjacentes peuvent avoir des chaînes Geohash qui ne partagent aucun préfixe. Par exemple, des cellules de part et d'autre du méridien de Greenwich (0° de longitude) ou de l'équateur peuvent avoir des hash complètement différents. Le symptôme classique : une requête retourne des résultats d'un côté d'une frontière mais rate silencieusement des points juste de l'autre côté.

Un exemple réel bien documenté : La Roche-Chalais dans le sud-ouest de la France a le préfixe Geohash u000, tandis que Pomerol — une ville à seulement 30 km — a le préfixe ezzz. Ces deux emplacements proches ne partagent aucun préfixe commun à aucune longueur, car une frontière de cellule majeure passe directement entre eux. Une requête de proximité par préfixe pour « des points près de La Roche-Chalais » retournerait silencieusement zéro résultat de Pomerol. Le même problème se produit au méridien de Greenwich (0° de longitude) : deux points GPS à 10 mètres l'un de l'autre de part et d'autre du méridien principal obtiennent des hash commençant par e (côté ouest) et s (côté est) — aucun préfixe commun, aucune plage d'index commune. La correction standard est de toujours interroger la cellule cible plus ses 8 voisins, puis filtrer par distance Haversine réelle. La commande GEOSEARCH de Redis gère cela en interne, ce qui est l'une des raisons pour lesquelles le support natif de la base de données est préférable à l'implémentation de sa propre logique de proximité Geohash.

S2 utilise une courbe de Hilbert qui offre de fortes garanties de localité — des points proches sur Terre se mappent à des entiers proches dans l'index. Cependant, « proche dans l'espace de Hilbert » et « spatialement adjacent » ne sont pas toujours la même chose. Les six faces du cube introduisent des coutures, et le calcul de voisins à travers les frontières de faces nécessite une gestion soigneuse. S2CellId::EdgeNeighbors() de S2 gère cela correctement, mais c'est plus de travail d'implémentation que gridDisk de H3.

Si vos données traversent des frontières géographiques — côtes, frontières nationales, ligne de changement de date — la topologie hexagonale uniforme de H3 est la valeur par défaut la plus sûre. Les systèmes de grille rectangulaire comme Geohash ont des coutures à des longitudes et latitudes prévisibles, et déboguer un raté de frontière en production est particulièrement désagréable.

Quand utiliser chaque système

Si vous utilisez déjà Redis ou Elasticsearch, optez pour Geohash — la recherche de proximité fonctionne directement sans code supplémentaire. Si vous avez besoin d'une cohérence globale ou d'une visualisation hexagonale, prenez H3. Ne passez à S2 que si vous devez indexer des polygones arbitraires ou si vous travaillez avec une infrastructure Google existante. Le tableau couvre les scénarios courants :

Cas d'usage Recommandé Raison
Commandes Redis GEO / Elasticsearch geo_point Geohash Support natif intégré, zéro configuration
Heatmaps de demande (VTC, livraison) H3 Taille de cellule uniforme + visualisation hexagonale
Agrégation de données globales (climat, télémétrie) H3 Élimine la distorsion polaire en latitude
Indexation de régions polygonales arbitraires S2 S2RegionCoverer approxime n'importe quelle forme
Systèmes de grille de jeux (Pokémon GO l'utilise) S2 Choix d'infrastructure originel de Niantic
Prototypes rapides et géofencing simple Geohash Courbe d'apprentissage minimale, IDs lisibles par un humain
Analyse multi-résolution (zoom avant/arrière) H3 Hiérarchie parent/enfant propre
PostGIS / bases de données spatiales Geohash Support standard de l'écosystème GIS

La maturité de l'écosystème détermine souvent le choix avant même la géométrie. Geohash est intégré à Redis depuis 2015 (GEOADD, GEORADIUS), et le type de champ geo_point d'Elasticsearch utilise Geohash en interne pour les agrégations en grille. Si votre stack inclut déjà l'un ou l'autre, l'intégration de Geohash ne demande presque aucun travail supplémentaire.

H3 dispose de bibliothèques solides en Python (h3) et JavaScript (h3-js), d'un écosystème croissant d'outils de visualisation (le H3HexagonLayer de Deck.gl), et d'un support de premier ordre dans l'extension spatiale DuckDB. S2 offre les opérations géométriques les plus puissantes mais le support JavaScript le plus faible — la bibliothèque principale est en C++, avec des portages communautaires vers d'autres langages.

Convertisseur GeoHash

Convertir entre GeoHash, H3 et Plus Codes.

Try it now

Travailler avec H3, Geohash et S2 en code

Les trois systèmes disposent de bibliothèques JavaScript et Python. Les exemples suivants couvrent les trois opérations les plus courantes : encoder une coordonnée, trouver des voisins, et obtenir le polygone de frontière d'une cellule. Pour une comparaison détaillée de h3-js et ngeohash en tant que packages npm — tailles de bundle, benchmarks d'encodage et pièges en production — consultez notre guide h3-js vs ngeohash.

Deux pièges de bibliothèques à connaître avant de mettre en production :

Précision entière de ngeohash : les fonctions encode_int / decode_int sont plafonnées à 52 bits en JavaScript (plage d'entiers sûrs IEEE 754 float64 standard). Pour la plupart des cas d'usage c'est suffisant, mais si vous définissez bitDepth au-delà de 52, les résultats perdent silencieusement en précision. Utilisez l'API chaîne (encode / decode) sauf si vous avez une raison spécifique de travailler avec des entiers, et passez toujours la même valeur bitDepth de façon cohérente aux appels encode et decode.

Représentation des IDs de cellules h3-js : les IDs de cellules H3 sont des entiers 64 bits, ce qui dépasse la plage d'entiers sûrs de JavaScript (2^53 − 1). La bibliothèque h3-js les retourne sous forme de chaînes par défaut (par exemple, "8928308280fffff"). Un bug de production courant : si votre API sérialise les IDs H3 comme des nombres JSON au lieu de chaînes, certains parseurs JSON arrondissent silencieusement la valeur et corrompent l'ID de cellule. Traitez toujours les IDs H3 comme des chaînes dans toute votre stack — dans votre schéma de base de données, vos réponses API et votre code 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 en JavaScript nécessite un portage communautaire (package npm s2-geometry) qui est en retard sur la bibliothèque C++. Pour un travail sérieux avec S2 dans des environnements JS, l'approche la plus pratique est d'appeler un service Python/Java ou d'utiliser Cloudflare Workers avec WASM. En Python, la bibliothèque s2sphere offre une interface propre :

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)

Vous pouvez expérimenter l'encodage Geohash et H3 directement dans le navigateur avec l'outil GeoHash de KunYu — sans installation de bibliothèque.

Convertisseur GeoHash

Convertir entre GeoHash, H3 et Plus Codes.

Try it now

FAQ

H3 est-il meilleur que Geohash ?

Pour les nouveaux projets sans contraintes d'infrastructure existante, H3 est la meilleure option par défaut : tailles de cellules uniformes, requêtes de voisinage plus propres, et aucun bug de frontière. Geohash l'emporte quand vous avez besoin des commandes GEO de Redis ou des agrégations Elasticsearch dès le départ, ou quand un ID lisible par un humain est important — les chaînes Geohash sont faciles à lire dans les logs ; les IDs H3 comme 8928308280fffff, beaucoup moins.

Redis supporte-t-il H3 ?

Redis n'a pas de support natif pour H3. Les commandes GEO intégrées (GEOADD, GEORADIUS, GEOSEARCH) utilisent Geohash en interne. Pour utiliser H3 avec Redis, encodez les coordonnées en IDs de cellules H3 dans votre couche applicative et stockez-les comme des clés chaînes ordinaires ou dans des ensembles triés. À partir de Redis 7.x, il n'est pas prévu d'ajouter des primitives H3 au serveur principal.

Que signifie S2 ?

S2 est l'abréviation de Sphere² (sphère au carré), en référence au concept mathématique de projection d'une sphère sur une surface bidimensionnelle. La bibliothèque a été développée chez Google et est utilisée en interne dans Google Maps, Google Earth et d'autres produits géographiques de Google.

Combien de cellules H3 couvrent la Terre à la résolution 5 ?

À la résolution 5, il y a 2 016 842 cellules H3 couvrant la surface de la Terre (incluant les 12 pentagones qui ferment l'icosaèdre). Chaque cellule couvre environ 252,9 km², ce qui rend cette résolution utile pour l'analyse au niveau des pays ou des grandes zones métropolitaines.

Peut-on convertir entre H3 et Geohash ?

Il n'existe pas de conversion mathématique directe. Les deux grilles sont indépendantes — un hexagone H3 à la résolution 9 et une cellule Geohash à 6 caractères couvrent des zones différentes et ne peuvent être qu'approximativement mis en correspondance. L'approche pratique est de décoder l'un ou l'autre format en coordonnée lat/lng, puis de ré-encoder dans le format cible. Notez que la cellule résultante peut ne pas contenir parfaitement la cellule d'origine en raison des différences de taille et de forme.

Quelle est la précision maximale de Geohash ?

La spécification Geohash supporte jusqu'à 12 caractères, produisant des cellules d'environ 3,7 cm × 1,9 cm. En pratique, la plupart des applications utilisent 6 à 9 caractères (de 1 km² à ~7 m²). Au-delà de 9 caractères, la précision dépasse ce que la plupart des matériels GPS peuvent fournir, et les longues clés de chaîne génèrent un surcoût de stockage et d'index inutile.