KunYu
h3geohashs2spatial-indexgis-basics

H3 vs Geohash vs S2: Den richtigen räumlichen Index wählen

H3, Geohash und S2 im Vergleich: Präzisionstabellen, Breitengrad-Verzerrung, Grenzfehler, Code-Beispiele in JS und Python sowie eine Entscheidungsmatrix.

KunYu TeamMarch 12, 202611 Min. Lesezeit

Wenn du eine Näherungssuche gegen Millionen von GPS-Punkten aufbaust, stößt du irgendwann an dieselbe Wand: Ein Brute-Force-Distanzscan ist zu langsam, und rohe Lat/Lng-Koordinaten lassen sich schlecht indizieren. Räumliche Gittersysteme lösen dieses Problem, indem sie Koordinaten auf vorberechnete Zellen einrasten — schnelle Lookups, saubere Aggregation, teilbare Bezeichner. Geohash macht das seit 2008. H3 hat Hexagone in den Mainstream gebracht, als Uber es 2018 als Open Source veröffentlicht hat. S2 ist das, worauf Google Maps intern läuft. Die drei Systeme sind nicht austauschbar, und Unterschiede, die im Prototyp akademisch wirken, werden folgenreich, wenn deine Daten eine Zellengrenze überschreiten oder mehrere Kontinente umspannen.

Was sind räumliche Gitterindizierungssysteme?

Räumliche Gitterindizierungssysteme unterteilen die Erdoberfläche in diskrete Zellen und weisen jeder Zelle einen eindeutigen Bezeichner zu. Statt roher Koordinaten speicherst du eine Zellen-ID — das ermöglicht schnelle Lookups, Bereichsabfragen und Datenaggregation durch einfachen Vergleich von String-Präfixen oder Integer-Werten. Der grundlegende Kompromiss liegt zwischen Zelluniformität, Implementierungseinfachheit und Abfrageleistung.

Alle drei Systeme entstanden aus unterschiedlichen Ingenieurskulturen. Geohash wurde 2008 von Gustavo Niemeyer als gemeinfrei nutzbares Geokodierungssystem erfunden — es ist konzeptionell das einfachste. S2 wurde bei Google um 2011 als Geometriebibliothek für die interne Karteninfrastruktur entwickelt. H3 wurde 2018 von Uber als Open Source veröffentlicht, um das Fahrer/Fahrgast-Matching auf Stadtebene zu lösen.

Die Zellenformen spiegeln diese Ursprünge wider: Geohash erzeugt Rechtecke, S2 erzeugt sphärische Vierecke (auf einer Kugel annähernd quadratisch), und H3 erzeugt Hexagone (mit 12 Pentagonen, um die Kugel zu schließen).

Wie jedes System funktioniert

Der Kodierungsansatz jedes Systems prägt sowohl seine Stärken als auch seine Schwachstellen in der Produktion.

H3 projiziert die Erde auf ein reguläres Ikosaeder (ein 20-flächiges Polyeder) und unterteilt dann jede Fläche in Hexagone. Das Ergebnis sind 15 Auflösungsstufen, bei denen jede Zelle in ungefähr 7 Kinderzellen unterteilt wird. Zellen-IDs sind 64-Bit-Integer. Die hexagonale Form bedeutet, dass jede Zelle genau 6 kantenanliegende Nachbarn hat — keine Ecken-Mehrdeutigkeit.

Geohash verschränkt die binären Darstellungen von Breitengrad- und Längengradbits und kodiert das Ergebnis dann als Base32-String. Deshalb sind Geohash-Strings menschenlesbar: u4pruydqqvj identifiziert eindeutig einen 3 m × 3 m großen Fleck im Zentrum von Paris. Die Präzision wird durch die Stringlänge gesteuert (1–12 Zeichen). Die Präfix-Eigenschaft — u4pru liegt immer innerhalb von u4pr — macht Bereichsabfragen trivial einfach.

S2 entfaltet die Erdoberfläche auf die sechs Flächen eines Würfels, legt eine Hilbert-Raumfüllungskurve über jede Fläche und ordnet Positionen dann 64-Bit-Integern zu. Die Hilbert-Kurve bewahrt die räumliche Lokalität: Nahe beieinanderliegende Punkte auf der Erde erzeugen nahe beieinanderliegende Integer. S2 hat 31 Auflösungsstufen und unterstützt beliebige Polygon-Überdeckung über S2RegionCoverer, der jede Form mit der minimalen Anzahl benötigter Zellen annähert.

Präzision und Abdeckung — ein Vergleich

Die drei Systeme verwenden inkompatible Präzisionsskalen — H3s 15 Auflösungen, Geohash's 12 Zeichenlängen und S2s 31 Stufen lassen sich nicht direkt aufeinander abbilden. Die folgende Tabelle ordnet sie nach ungefährer Zellfläche auf drei praktischen Skalen: Aggregation auf Stadtebene, Nachbarschafts-Routing und Gebäude-Präzision.

Die Längengrad-Verzerrung folgt einer Kosinuskurve und wird schneller schwerwiegend, als die meisten Entwickler erwarten. Auf 30° Breite (Los Angeles, Kairo, Shanghai) deckt ein Längengrad 96,5 km ab gegenüber 110,6 km für einen Breitengrad — ein Ost-West-Defizit von 14,9 %. Auf 60° Breite (Stockholm, Anchorage, Sankt Petersburg) erreicht das Verhältnis 2:1: Ein Längengrad ist nur noch 55,7 km breit. Eine 6-stellige Geohash-Zelle, die am Äquator ~0,74 km² abdeckt, deckt auf 60°N nur noch ~0,37 km² ab. Wenn du einen globalen Dienst mit gleichmäßigen Näherungs-Schwellenwerten aufbaust (z. B. „innerhalb von 500 Metern"), liefert Geohash bei fester Präzisionsstufe je nach Breitengrad inkonsistente Ergebnisse — du müsstest kürzere Hashes bei niedrigen und längere in Polnähe verwenden, um eine äquivalente reale Abdeckung zu erhalten, was deine Abfragelogik verkompliziert. H3 und S2 kompensieren die sphärische Verzerrung beide: H3 auf Auflösung 9 variiert in der Fläche über alle Breitengrade um weniger als 3 %.

Skala H3-Auflösung H3-Zellfläche Geohash-Länge Geohash-Zellfläche S2-Stufe S2-Zellfläche
Stadt 5 ~253 km² 4 Zeichen ~780 km² 10 ~325 km²
Nachbarschaft 9 ~0,11 km² 6 Zeichen ~0,74 km² 15 ~0,32 km²
Gebäude 11 ~0,0022 km² 7 Zeichen ~0,023 km² 18 ~0,005 km²

Nachbarsuche und Grenzeffekte

Nachbarabfragen sind die häufigste räumliche Indexoperation: „Gegeben diese Zelle, finde alle benachbarten Zellen." Die drei Systeme handhaben das mit sehr unterschiedlicher Ergonomie.

H3 hat das sauberste Nachbarmodell. Jedes Hexagon hat genau 6 kantenanliegende Nachbarn — keine Ecknachbarn, keine Mehrdeutigkeiten. Die Funktion gridDisk(cell, k) gibt alle Zellen innerhalb von k Ringen in einem einzigen Aufruf zurück. Ein k=1-Disk liefert 7 Zellen (einschließlich Mittelpunkt); k=2 liefert 19. Da Hexagone auf allen Seiten gleiche Kantenlängen haben, sind Distanzabfragen per Ring-Anzahl geometrisch konsistent.

Geohash hat 8 Nachbarn (4 Kanten + 4 Ecken). Bei den meisten Zellen funktioniert das gut. Das bekannte Problem liegt an Gittergrenzen: Zwei geografisch benachbarte Zellen können Geohash-Strings haben, die kein gemeinsames Präfix teilen. Zellen, die den Nullmeridian (0° Länge) oder den Äquator überspannen, können völlig unterschiedliche Hashes haben. Das klassische Symptom: Eine Abfrage liefert Ergebnisse auf einer Seite einer Grenze, aber verfehlt stillschweigend Punkte kurz jenseits davon.

Ein gut dokumentiertes Praxisbeispiel: La Roche-Chalais im Südwesten Frankreichs hat das Geohash-Präfix u000, während Pomerol — eine nur 30 km entfernte Ortschaft — das Präfix ezzz hat. Diese beiden nahe gelegenen Orte teilen bei keiner Länge ein gemeinsames Präfix, weil eine Hauptzellengrenze direkt zwischen ihnen verläuft. Eine präfixbasierte Näherungsabfrage nach „Punkten nahe La Roche-Chalais" würde aus Pomerol stillschweigend null Ergebnisse liefern. Dasselbe Problem tritt am Nullmeridian (0° Länge) auf: Zwei GPS-Punkte, die 10 Meter voneinander entfernt auf gegenüberliegenden Seiten des Nullmeridians liegen, erhalten Hashes, die mit e (Westseite) bzw. s (Ostseite) beginnen — kein gemeinsames Präfix, kein gemeinsamer Indexbereich. Die Standardlösung besteht darin, immer die Zielzelle plus ihre 8 Nachbarn abzufragen und dann nach tatsächlicher Haversine-Distanz zu filtern. Redis' GEOSEARCH-Befehl erledigt das intern, was ein Grund dafür ist, dass native Datenbankunterstützung der eigenen Geohash-Näherungslogik vorzuziehen ist.

S2 verwendet eine Hilbert-Kurve, die starke Lokalitätsgarantien bietet — nahe beieinanderliegende Punkte auf der Erde werden auf nahe beieinanderliegende Integer im Index abgebildet. Allerdings sind „nahe im Hilbert-Raum" und „räumlich benachbart" nicht immer dasselbe. Die sechs Würfelflächen führen zu Nähten, und die Berechnung von Nachbarn über Flächengrenzen hinweg erfordert sorgfältige Behandlung. S2's S2CellId::EdgeNeighbors() handhabt das korrekt, ist aber mehr Implementierungsaufwand als H3's gridDisk.

Wenn deine Daten geografische Grenzen überspannen — Küstenlinien, Staatsgrenzen, die Datumsgrenze — ist H3's gleichmäßige hexagonale Topologie der sicherere Standard. Rechteckige Gittersysteme wie Geohash haben Nähte an vorhersehbaren Längen- und Breitengraden, und das Debuggen eines Grenzfehlers in der Produktion ist unangenehm.

Wann welches System verwenden

Wenn du bereits Redis oder Elasticsearch betreibst, verwende Geohash — die Näherungssuche funktioniert ohne zusätzlichen Code. Wenn du globale Konsistenz oder hexagonale Visualisierung brauchst, nimm H3. Greife auf S2 nur zurück, wenn du beliebige Polygone indizieren musst oder neben bestehender Google-Infrastruktur arbeitest. Die Tabelle zeigt die häufigen Szenarien:

Anwendungsfall Empfehlung Begründung
Redis GEO-Befehle / Elasticsearch geo_point Geohash Nativer Support, kein Setup
Fahrtvermittlung / Lieferservice Nachfrage-Heatmaps H3 Konsistente Zellgröße + hexagonale Visualisierung
Globale Datenaggregation (Klima, Telemetrie) H3 Eliminiert Breitengrad-Verzerrung
Beliebige Polygon-Regionindizierung S2 S2RegionCoverer nähert jede Form an
Spielgittersysteme (Pokémon GO nutzt das) S2 Niantic's ursprüngliche Infrastrukturentscheidung
Schnelle Prototypen und einfaches Geofencing Geohash Geringste Lernkurve, menschenlesbare IDs
Mehrstufige Analyse (rein-/rauszoomen) H3 Saubere Eltern-/Kind-Hierarchie
PostGIS / räumliche Datenbanken Geohash Standard im GIS-Ökosystem

Ökosystem-Reife bestimmt die Wahl oft, bevor die Geometrie es tut. Geohash ist seit 2015 in Redis eingebaut (GEOADD, GEORADIUS), und der geo_point-Feldtyp von Elasticsearch verwendet Geohash intern für Gitter-Aggregationen. Wenn dein Stack eines von beiden bereits enthält, erfordert die Geohash-Integration kaum zusätzlichen Aufwand.

H3 hat starke Python- (h3) und JavaScript-Bibliotheken (h3-js), ein wachsendes Ökosystem an Visualisierungstools (Deck.gl's H3HexagonLayer) und erstklassige Unterstützung in der DuckDB-Spatial-Extension. S2 hat die leistungsstärksten Geometrieoperationen, aber die schwächste JavaScript-Unterstützung — die Hauptbibliothek ist C++, mit von der Community gepflegten Ports in andere Sprachen.

GeoHash-Konverter

Konvertieren Sie zwischen GeoHash, H3 und Plus Codes.

Try it now

Mit H3, Geohash und S2 im Code arbeiten

Alle drei Systeme haben JavaScript- und Python-Bibliotheken. Die folgenden Beispiele decken die drei Operationen ab, die du am häufigsten nutzen wirst: eine Koordinate kodieren, Nachbarn finden und das Zellengrenz-Polygon abrufen. Einen detaillierten Vergleich von h3-js und ngeohash als npm-Pakete — einschließlich Bundle-Größen, Encoding-Benchmarks und Produktions-Stolperfallen — findest du in unserem h3-js vs ngeohash Leitfaden.

Zwei Bibliotheks-Fallstricke, die du vor dem Deployment kennen solltest:

ngeohash Integer-Präzision: Die Funktionen encode_int / decode_int sind in JavaScript auf 52 Bits begrenzt (Standard IEEE 754 float64 safe integer range). Für die meisten Anwendungsfälle ist das in Ordnung, aber wenn du bitDepth über 52 setzt, verlieren Ergebnisse stillschweigend an Präzision. Verwende die String-API (encode / decode), außer du hast einen spezifischen Grund für die Arbeit mit Integern, und übergib denselben bitDepth-Wert immer konsistent an beide Encode- und Decode-Aufrufe.

h3-js Zellen-ID-Darstellung: H3-Zellen-IDs sind 64-Bit-Integer, die den sicheren Integer-Bereich von JavaScript (2^53 − 1) überschreiten. Die h3-js-Bibliothek gibt sie standardmäßig als Strings zurück (z. B. "8928308280fffff"). Ein häufiger Produktionsfehler: Wenn deine API H3-IDs als JSON-Zahlen statt als Strings serialisiert, runden manche JSON-Parser den Wert stillschweigend und korrumpieren die Zellen-ID. Behandle H3-IDs immer als Strings im gesamten Stack — im Datenbankschema, in API-Antworten und im Frontend-Code.

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 erfordert einen Community-Port (s2-geometry npm package), der hinter der C++-Bibliothek zurückbleibt. Für ernsthafte S2-Arbeit in JS-Umgebungen ist der praktischste Ansatz, einen Python/Java-Service aufzurufen oder Cloudflare Workers mit WASM zu verwenden. In Python bietet die s2sphere-Bibliothek eine saubere Schnittstelle:

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)

Du kannst Geohash- und H3-Kodierung direkt im Browser ausprobieren — mit KunYus GeoHash-Encoder-Tool, ganz ohne Bibliotheksinstallation.

GeoHash-Konverter

Konvertieren Sie zwischen GeoHash, H3 und Plus Codes.

Try it now

FAQ

Ist H3 besser als Geohash?

Für neue Projekte ohne bestehende Infrastruktur-Einschränkungen ist H3 der bessere Standard: gleichmäßige Zellgrößen, sauberere Nachbarabfragen und keine Grenzfehler. Geohash gewinnt, wenn du Redis-GEO-Befehle oder Elasticsearch-Aggregationen direkt nutzen möchtest — oder wenn eine menschenlesbare ID wichtig ist: Geohash-Strings sind in Logs leicht zu lesen; H3-IDs wie 8928308280fffff hingegen nicht.

Unterstützt Redis H3?

Redis hat keine native H3-Unterstützung. Die eingebauten GEO-Befehle (GEOADD, GEORADIUS, GEOSEARCH) verwenden intern Geohash. Um H3 mit Redis zu nutzen, kodierst du Koordinaten im Application Layer in H3-Zellen-IDs und speicherst sie als reguläre String-Keys oder in Sorted Sets. Stand Redis 7.x gibt es keinen Plan, H3-Primitive in den Kern-Server aufzunehmen.

Wofür steht S2?

S2 steht für Sphere² (Sphäre zum Quadrat) und bezieht sich auf das mathematische Konzept, eine Kugel auf eine zweidimensionale Oberfläche abzubilden. Die Bibliothek wurde bei Google entwickelt und wird intern bei Google Maps, Google Earth und anderen Google-Geo-Produkten eingesetzt.

Wie viele H3-Zellen bedecken die Erde auf Auflösung 5?

Auf Auflösung 5 gibt es 2.016.842 H3-Zellen, die die Erdoberfläche bedecken (einschließlich der 12 Pentagone, die das Ikosaeder schließen). Jede Zelle deckt ungefähr 252,9 km² ab, was diese Auflösung für Analysen auf Landes- oder Großstadtebene nützlich macht.

Kann ich zwischen H3 und Geohash konvertieren?

Es gibt keine direkte mathematische Konvertierung. Die beiden Gitter sind unabhängig — ein H3-Hexagon auf Auflösung 9 und eine 6-stellige Geohash-Zelle decken unterschiedliche Flächen ab und können nur annäherungsweise aufeinander abgebildet werden. Der praktische Ansatz besteht darin, beide Formate in eine Lat/Lng-Koordinate zu dekodieren und dann im Zielformat neu zu kodieren. Beachte, dass die resultierende Zelle die ursprüngliche Zelle aufgrund von Größen- und Formunterschieden möglicherweise nicht perfekt enthält.

Was ist die maximale Geohash-Präzision?

Die Geohash-Spezifikation unterstützt bis zu 12 Zeichen, was Zellen von ungefähr 3,7 cm × 1,9 cm ergibt. In der Praxis verwenden die meisten Anwendungen 6–9 Zeichen (1 km² bis ~7 m²). Jenseits von 9 Zeichen übersteigt die Präzision das, was die meisten GPS-Geräte liefern können, und die langen String-Keys erzeugen unnötigen Speicher- und Index-Overhead.