KunYu
h3geohashs2spatial-indexgis-basics

H3 vs Geohash vs S2: Escolhendo o Índice Espacial Certo

Comparação entre os índices espaciais H3, Geohash e S2: tabelas de precisão, distorção por latitude, bugs de fronteira, exemplos de código em JS e Python, e uma matriz de decisão.

KunYu TeamMarch 12, 202614 min de leitura

Quando você está construindo busca por proximidade em milhões de pontos GPS, eventualmente bate na mesma parede: um scan de distância por força bruta é lento demais, e coordenadas lat/lng brutas não indexam bem. Sistemas de grade espacial resolvem isso ao encaixar coordenadas em células pré-computadas — lookup rápido, agregação limpa, identificadores compartilháveis. O Geohash faz isso desde 2008. O H3 popularizou os hexágonos quando o Uber o abriu em 2018. O S2 é o que o Google Maps usa internamente. Os três não são intercambiáveis, e as diferenças que parecem acadêmicas num protótipo se tornam críticas quando seus dados cruzam uma fronteira de célula ou abrangem vários continentes.

O que são Sistemas de Indexação por Grade Espacial?

Sistemas de indexação por grade espacial dividem a superfície da Terra em células discretas, atribuindo a cada célula um identificador único. Em vez de armazenar coordenadas brutas, você armazena um ID de célula — permitindo lookups rápidos, consultas por intervalo e agregação de dados simplesmente comparando prefixos de string ou valores inteiros. O tradeoff central está entre uniformidade das células, simplicidade de implementação e desempenho das consultas.

Os três sistemas surgiram de culturas de engenharia diferentes. Geohash foi inventado por Gustavo Niemeyer em 2008 como um sistema de geocodificação de domínio público — é o mais simples por design. S2 foi desenvolvido no Google por volta de 2011 como uma biblioteca de geometria para infraestrutura interna de mapas. H3 foi aberto pelo Uber em 2018 para resolver o matching entre motoristas e passageiros em escala urbana.

A forma das células reflete essas origens: o Geohash produz retângulos, o S2 produz quadriláteros esféricos (aproximadamente quadrados numa esfera), e o H3 produz hexágonos (com 12 pentágonos para fechar a esfera).

Como Cada Sistema Funciona

A abordagem de codificação de cada sistema molda tanto seus pontos fortes quanto seus modos de falha em produção.

H3 projeta a Terra num icosaedro regular (um poliedro de 20 faces), e depois subdivide cada face em hexágonos. O resultado são 15 níveis de resolução onde cada célula se subdivide em aproximadamente 7 filhos. Os IDs de célula são inteiros de 64 bits. A forma hexagonal significa que cada célula tem exatamente 6 vizinhos compartilhando arestas — sem ambiguidade por compartilhamento de cantos.

Geohash intercala as representações binárias dos bits de latitude e longitude e codifica o resultado como uma string Base32. É por isso que strings Geohash são legíveis por humanos: u4pruydqqvj identifica unicamente um trecho de 3 m × 3 m no centro de Paris. A precisão é controlada pelo comprimento da string (1 a 12 caracteres). A propriedade de prefixo — u4pru está sempre dentro de u4pr — torna consultas por intervalo trivialmente simples.

S2 desdobra a superfície da Terra nas seis faces de um cubo, aplica uma curva de Hilbert preenchendo o espaço em cada face e mapeia posições para inteiros de 64 bits. A curva de Hilbert preserva a localidade espacial: pontos próximos na Terra geram inteiros próximos. O S2 tem 31 níveis de resolução e suporta cobertura de polígonos arbitrários via S2RegionCoverer, que aproxima qualquer forma com o número mínimo de células necessárias.

Precisão e Cobertura — Tabela Comparativa

Os três sistemas usam escalas de precisão incompatíveis — as 15 resoluções do H3, os 12 comprimentos de caractere do Geohash e os 31 níveis do S2 não se mapeiam entre si. A tabela abaixo os alinha por área de célula aproximada em três escalas práticas: agregação em nível de cidade, roteamento em nível de bairro e precisão em nível de edificação.

O encolhimento do grau de longitude segue uma curva cosseno e se torna severo mais rápido do que a maioria dos desenvolvedores espera. Aos 30° de latitude (Los Angeles, Cairo, Xangai), um grau de longitude cobre 96,5 km contra 110,6 km de um grau de latitude — uma deficiência leste-oeste de 14,9%. Aos 60° (Estocolmo, Anchorage, São Petersburgo), a razão chega a 2:1: um grau de longitude tem apenas 55,7 km de largura. Uma célula Geohash de 6 caracteres que cobre ~0,74 km² na linha do equador cobre apenas ~0,37 km² aos 60°N. Se você está construindo um serviço global com limiares de proximidade uniformes (por exemplo, "dentro de 500 metros"), o Geohash com um nível de precisão fixo dará resultados inconsistentes por latitude — você precisaria usar hashes mais longos em latitudes mais baixas e mais curtos próximos aos polos para obter cobertura real equivalente, o que complica a lógica das suas consultas. H3 e S2 compensam a distorção esférica: o H3 na resolução 9 varia menos de 3% em área em todas as latitudes.

Escala Resolução H3 Área Célula H3 Comprimento Geohash Área Célula Geohash Nível S2 Área Célula S2
Cidade 5 ~253 km² 4 chars ~780 km² 10 ~325 km²
Bairro 9 ~0,11 km² 6 chars ~0,74 km² 15 ~0,32 km²
Edificação 11 ~0,0022 km² 7 chars ~0,023 km² 18 ~0,005 km²

Busca por Vizinhos e Efeitos de Fronteira

Consultas por vizinhança são a operação mais comum em índices espaciais: "dada essa célula, encontre todas as células adjacentes." Os três sistemas lidam com isso com ergonomias bem diferentes.

H3 tem o modelo de vizinhança mais limpo. Todo hexágono tem exatamente 6 vizinhos compartilhando arestas — sem vizinhos de canto, sem ambiguidade. A função gridDisk(cell, k) retorna todas as células dentro de k anéis numa única chamada. Um disco k=1 retorna 7 células (incluindo o centro); k=2 retorna 19. Como os hexágonos têm comprimento de aresta igual em todos os lados, consultas de distância usando contagem de anéis são geometricamente consistentes.

Geohash tem 8 vizinhos (4 arestas + 4 cantos). Para a maioria das células isso funciona bem. O problema notório está nas fronteiras da grade: duas células geograficamente adjacentes podem ter strings Geohash que não compartilham nenhum prefixo. Por exemplo, células atravessando o Meridiano de Greenwich (0° de longitude) ou o equador podem ter hashes completamente diferentes. O sintoma clássico: uma consulta retorna resultados de um lado da fronteira, mas silenciosamente perde pontos logo do outro lado.

Um exemplo real bem documentado: La Roche-Chalais, no sudoeste da França, tem o prefixo Geohash u000, enquanto Pomerol — uma cidade a apenas 30 km de distância — tem o prefixo ezzz. Essas duas localidades próximas não compartilham nenhum prefixo comum em nenhum comprimento porque uma fronteira de célula importante passa exatamente entre elas. Uma consulta de proximidade baseada em prefixo para "pontos perto de La Roche-Chalais" retornaria silenciosamente zero resultados de Pomerol. O mesmo problema ocorre no Meridiano de Greenwich (0° de longitude): dois pontos GPS a 10 metros de distância em lados opostos do Meridiano Principal recebem hashes começando com e (lado oeste) e s (lado leste) — sem prefixo compartilhado, sem intervalo de índice compartilhado. A solução padrão é sempre consultar a célula alvo mais seus 8 vizinhos e filtrar pela distância Haversine real. O comando GEOSEARCH do Redis faz isso internamente, o que é uma das razões pelas quais o suporte nativo do banco de dados é preferível a implementar sua própria lógica de proximidade Geohash.

S2 usa uma curva de Hilbert que fornece fortes garantias de localidade — pontos próximos na Terra mapeiam para inteiros próximos no índice. No entanto, "próximo no espaço de Hilbert" e "espacialmente adjacente" nem sempre são a mesma coisa. As seis faces do cubo introduzem costuras, e computar vizinhos através das fronteiras de face requer tratamento cuidadoso. O S2CellId::EdgeNeighbors() do S2 lida com isso corretamente, mas dá mais trabalho de implementação do que o gridDisk do H3.

Se seus dados abrangem fronteiras geográficas — linhas de costa, fronteiras nacionais, a linha de data — a topologia hexagonal uniforme do H3 é o padrão mais seguro. Sistemas de grade retangular como o Geohash têm costuras em longitudes e latitudes previsíveis, e depurar um miss de fronteira em produção é desagradável.

Quando Usar Cada Sistema

Se você já está rodando Redis ou Elasticsearch, use Geohash — busca por proximidade funciona imediatamente sem código extra. Se você precisa de consistência global ou visualização hexagonal, use H3. Só recorra ao S2 se precisar indexar polígonos arbitrários ou estiver trabalhando junto com infraestrutura existente do Google. A tabela cobre os cenários mais comuns:

Caso de Uso Recomendado Motivo
Comandos Redis GEO / Elasticsearch geo_point Geohash Suporte nativo embutido, sem configuração
Heatmaps de demanda para ride-sharing / delivery H3 Tamanho de célula consistente + visualização hexagonal
Agregação de dados globais (clima, telemetria) H3 Elimina distorção de latitude em latitudes polares
Indexação de polígonos de regiões arbitrárias S2 S2RegionCoverer aproxima qualquer forma
Sistemas de grade para jogos (Pokémon GO usa isso) S2 Escolha de infraestrutura original da Niantic
Protótipos rápidos e geofencing simples Geohash Menor curva de aprendizado, IDs legíveis por humanos
Análise multirresolução (zoom in/out) H3 Hierarquia limpa de pai/filho
PostGIS / bancos de dados espaciais Geohash Suporte padrão no ecossistema GIS

Maturidade do ecossistema frequentemente determina a escolha antes da geometria. O Geohash está integrado ao Redis desde 2015 (GEOADD, GEORADIUS), e o tipo de campo geo_point do Elasticsearch usa Geohash internamente para agregações em grade. Se sua stack já inclui algum dos dois, a integração com Geohash exige praticamente nenhum trabalho extra.

O H3 tem bibliotecas sólidas em Python (h3) e JavaScript (h3-js), um ecossistema crescente de ferramentas de visualização (o H3HexagonLayer do Deck.gl) e suporte de primeira classe na extensão espacial do DuckDB. O S2 tem as operações de geometria mais poderosas, mas o suporte JavaScript mais fraco — a biblioteca principal é em C++, com ports mantidos pela comunidade para outras linguagens.

Conversor GeoHash

Converta entre GeoHash, H3 e Plus Codes.

Try it now

Trabalhando com H3, Geohash e S2 no Código

Os três sistemas têm bibliotecas em JavaScript e Python. Os exemplos a seguir cobrem as três operações que você mais vai usar: codificar uma coordenada, encontrar vizinhos e obter o polígono de fronteira da célula. Para uma comparação detalhada de h3-js e ngeohash como pacotes npm — incluindo tamanhos de bundle, benchmarks de codificação e armadilhas em produção — veja nosso guia h3-js vs ngeohash.

Dois problemas de biblioteca que valem a pena conhecer antes de subir em produção:

Precisão inteira do ngeohash: as funções encode_int / decode_int são limitadas a 52 bits em JavaScript (intervalo seguro de inteiros float64 IEEE 754 padrão). Para a maioria dos casos de uso isso é suficiente, mas se você definir bitDepth acima de 52, os resultados perderão precisão silenciosamente. Use a API de string (encode / decode) a menos que tenha um motivo específico para trabalhar com inteiros, e sempre passe o mesmo valor de bitDepth consistentemente tanto para encode quanto para decode.

Representação de ID de célula do h3-js: os IDs de célula H3 são inteiros de 64 bits, que excedem o intervalo seguro de inteiros do JavaScript (2^53 − 1). A biblioteca h3-js os retorna como strings por padrão (por exemplo, "8928308280fffff"). Um bug comum em produção: se sua API serializa IDs H3 como números JSON em vez de strings, alguns parsers JSON vão arredondar silenciosamente o valor e corromper o ID da célula. Sempre trate IDs H3 como strings em toda a sua stack — no schema do banco de dados, nas respostas da API e no código 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

O S2 em JavaScript requer um port mantido pela comunidade (pacote npm s2-geometry) que fica atrás da biblioteca C++. Para trabalhos sérios com S2 em ambientes JS, a abordagem mais prática é chamar um serviço em Python/Java ou usar Cloudflare Workers com WASM. Em Python, a biblioteca s2sphere fornece uma interface limpa:

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)

Você pode experimentar a codificação Geohash e H3 diretamente no navegador usando a ferramenta GeoHash do KunYu — sem precisar instalar nenhuma biblioteca.

Conversor GeoHash

Converta entre GeoHash, H3 e Plus Codes.

Try it now

FAQ

O H3 é melhor que o Geohash?

Para novos projetos sem restrições de infraestrutura existente, o H3 é o melhor padrão: tamanhos de célula uniformes, consultas de vizinhança mais limpas e sem bugs de fronteira. O Geohash ganha quando você precisa dos comandos GEO do Redis ou das agregações do Elasticsearch de cara, ou quando um ID legível por humanos importa — strings Geohash são fáceis de ler em logs; IDs H3 como 8928308280fffff não são.

O Redis suporta H3?

O Redis não tem suporte nativo a H3. Os comandos GEO embutidos (GEOADD, GEORADIUS, GEOSEARCH) usam Geohash internamente. Para usar H3 com Redis, codifique as coordenadas para IDs de célula H3 na camada de aplicação e armazene-os como chaves string comuns ou em sorted sets. A partir do Redis 7.x, não há planos de adicionar primitivas H3 ao servidor principal.

O que significa S2?

S2 é uma abreviação de Sphere² (esfera ao quadrado), referindo-se ao conceito matemático de mapear uma esfera numa superfície bidimensional. A biblioteca foi desenvolvida no Google e é usada internamente no Google Maps, Google Earth e outros produtos geo do Google.

Quantas células H3 cobrem a Terra na resolução 5?

Na resolução 5, há 2.016.842 células H3 cobrindo a superfície da Terra (incluindo os 12 pentágonos que fecham o icosaedro). Cada célula cobre aproximadamente 252,9 km², tornando essa resolução útil para análise em nível de país ou de grandes regiões metropolitanas.

Posso converter entre H3 e Geohash?

Não há conversão matemática direta. As duas grades são independentes — um hexágono H3 na resolução 9 e uma célula Geohash de 6 caracteres cobrem áreas diferentes e só podem ser mapeadas aproximadamente. A abordagem prática é decodificar qualquer um dos formatos para uma coordenada lat/lng e depois recodificar no formato de destino. Esteja ciente de que a célula resultante pode não conter perfeitamente a célula original devido a diferenças de tamanho e forma.

Qual é a precisão máxima do Geohash?

A especificação Geohash suporta até 12 caracteres, gerando células de aproximadamente 3,7 cm × 1,9 cm. Na prática, a maioria das aplicações usa 6 a 9 caracteres (de 1 km² até ~7 m²). Acima de 9 caracteres, a precisão supera o que a maioria dos receptores GPS consegue fornecer, e as chaves de string longas geram overhead desnecessário de armazenamento e índice.