Ao construir pesquisa por proximidade sobre milhões de pontos GPS, acabas inevitavelmente por esbarrar na mesma barreira: uma varredura de distância por força bruta é demasiado lenta, e as coordenadas lat/lng brutas não indexam bem. Os sistemas de grelha espacial resolvem isto ao encaixar as coordenadas em células pré-computadas — pesquisa rápida, agregação limpa, identificadores partilháveis. O Geohash faz isto desde 2008. O H3 trouxe os hexágonos para o mainstream quando a Uber o disponibilizou como open-source 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 tornam-se relevantes quando os teus dados atravessam uma fronteira de célula ou abrangem múltiplos continentes.
O que São Sistemas de Indexação por Grelha Espacial?
Os sistemas de indexação por grelha espacial dividem a superfície da Terra em células discretas, atribuindo a cada uma um identificador único. Em vez de armazenar coordenadas brutas, armazenas um ID de célula — permitindo pesquisas rápidas, consultas por intervalo e agregação de dados simplesmente comparando prefixos de strings ou valores inteiros. O compromisso central é entre a uniformidade das células, a simplicidade de implementação e o desempenho das consultas.
Os três sistemas emergiram de culturas de engenharia diferentes. O Geohash foi inventado por Gustavo Niemeyer em 2008 como um sistema de geocodificação de domínio público — é o mais simples por design. O S2 foi desenvolvido na Google por volta de 2011 como biblioteca de geometria para infraestrutura de mapeamento interno. O H3 foi disponibilizado como open-source pela Uber em 2018 para resolver o problema de correspondência entre condutores e passageiros à escala de uma cidade.
As formas das células refletem estas 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 Funciona Cada Sistema
A abordagem de codificação de cada sistema determina tanto os seus pontos fortes como os seus modos de falha em produção.
O H3 projeta a Terra num icosaedro regular (um poliedro de 20 faces) e subdivide depois cada face em hexágonos. O resultado são 15 níveis de resolução em que cada célula se subdivide em aproximadamente 7 filhas. Os IDs de célula são inteiros de 64 bits. A forma hexagonal significa que cada célula tem exatamente 6 vizinhos que partilham uma aresta — sem ambiguidade de vizinhos por canto.
O Geohash intercala as representações binárias dos bits de latitude e longitude e depois codifica o resultado como uma string Base32. É por isso que as strings Geohash são legíveis por humanos: u4pruydqqvj identifica de forma única uma zona 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 as consultas por intervalo trivialmente simples.
O S2 desdobra a superfície da Terra nas seis faces de um cubo, aplica uma curva de Hilbert de preenchimento de espaço em cada face e depois mapeia as posições para inteiros de 64 bits. A curva de Hilbert preserva a localidade espacial: pontos próximos na Terra produzem inteiros próximos. O S2 tem 31 níveis de resolução e suporta cobertura de polígonos arbitrários através do 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 — os 15 resoluções do H3, os 12 comprimentos de string do Geohash e os 31 níveis do S2 não se mapeiam uns nos outros. A tabela abaixo alinha-os por área de célula aproximada a três escalas práticas: agregação ao nível da cidade, encaminhamento por bairro e precisão ao nível de edifício.
O encolhimento por grau de longitude segue uma curva cosseno e torna-se acentuado mais depressa do que a maioria dos programadores 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 — um défice de 14,9% no eixo este-oeste. Aos 60° (Estocolmo, Anchorage, São Petersburgo), a relaçã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² no equador cobre apenas ~0,37 km² aos 60°N. Se estiveres a construir um serviço global com limiares de proximidade uniformes (por exemplo, "a menos de 500 metros"), o Geohash a um nível de precisão fixo dará resultados inconsistentes por latitude — precisarias de usar hashes mais longos em latitudes mais baixas e mais curtos perto dos polos para obter cobertura equivalente no mundo real, o que complica a lógica das consultas. H3 e S2 compensam ambos 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² |
| Edifício | 11 | ~0,0022 km² | 7 chars | ~0,023 km² | 18 | ~0,005 km² |
Pesquisa de Vizinhos e Efeitos de Fronteira
As consultas de vizinhos são a operação de índice espacial mais comum: "dada esta célula, encontrar todas as células adjacentes." Os três sistemas tratam isto com ergonomias muito diferentes.
O H3 tem o modelo de vizinhos mais limpo. Cada hexágono tem exatamente 6 vizinhos que partilham uma aresta — sem vizinhos de canto, sem ambiguidade. A função gridDisk(cell, k) devolve todas as células dentro de k anéis numa única chamada. Um disco de k=1 devolve 7 células (incluindo o centro); k=2 devolve 19. Como os hexágonos têm comprimento de aresta igual em todos os lados, as consultas de distância por contagem de anéis são geometricamente consistentes.
O Geohash tem 8 vizinhos (4 arestas + 4 cantos). Para a maioria das células isto funciona bem. O problema notório surge nas fronteiras da grelha: duas células geograficamente adjacentes podem ter strings Geohash que não partilham nenhum prefixo. Por exemplo, células que atravessam o Meridiano de Greenwich (0° de longitude) ou o equador podem ter hashes completamente diferentes. O sintoma clássico: uma consulta devolve resultados de um lado de uma fronteira mas perde silenciosamente pontos mesmo do outro lado.
Um exemplo real bem documentado: La Roche-Chalais, no sudoeste de França, tem o prefixo Geohash u000, enquanto Pomerol — uma localidade a apenas 30 km de distância — tem o prefixo ezzz. Estas duas localizações próximas não partilham nenhum prefixo comum em qualquer comprimento porque uma fronteira de célula principal passa diretamente entre elas. Uma consulta de proximidade baseada em prefixo para "pontos perto de La Roche-Chalais" devolveria 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 ocidental) e s (lado oriental) — sem prefixo partilhado, sem intervalo de índice partilhado. A solução padrão é sempre consultar a célula alvo mais os seus 8 vizinhos e depois filtrar pela distância real de Haversine. O comando GEOSEARCH do Redis trata disto internamente, o que é uma das razões pelas quais o suporte nativo de base de dados é preferível a implementar a tua própria lógica de proximidade Geohash.
O S2 usa uma curva de Hilbert que oferece garantias fortes 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 calcular vizinhos através de fronteiras de face requer um tratamento cuidadoso. O S2CellId::EdgeNeighbors() do S2 trata disto corretamente, mas é mais trabalho de implementação do que o gridDisk do H3.
Se os teus dados abrangem fronteiras geográficas — linhas costeiras, fronteiras nacionais, a linha de data — a topologia hexagonal uniforme do H3 é a opção mais segura por defeito. Os sistemas de grelha retangular como o Geohash têm costuras em longitudes e latitudes previsíveis, e depurar uma falha de fronteira em produção é desagradável.
Quando Usar Cada Sistema
Se já estás a usar Redis ou Elasticsearch, usa Geohash — a pesquisa por proximidade funciona de imediato sem código adicional. Se precisas de consistência global ou visualização hexagonal, usa H3. Só deves recorrer ao S2 se precisares de indexar polígonos arbitrários ou se estiveres a trabalhar junto de infraestrutura Google existente. A tabela cobre os cenários mais comuns:
| Caso de Uso | Recomendado | Motivo |
|---|---|---|
| Comandos GEO do Redis / geo_point do Elasticsearch | Geohash | Suporte nativo integrado, sem configuração |
| Mapas de calor de procura em ride-sharing / entrega de comida | H3 | Tamanho consistente de célula + visualização hexagonal |
| Agregação de dados globais (clima, telemetria) | H3 | Elimina distorção por latitude polar |
| Indexação de regiões com polígonos arbitrários | S2 | S2RegionCoverer aproxima qualquer forma |
| Sistemas de grelha em jogos (Pokémon GO usa isto) | S2 | Escolha de infraestrutura original da Niantic |
| Protótipos rápidos e geofencing simples | Geohash | Curva de aprendizagem mais baixa, IDs legíveis por humanos |
| Análise multi-resolução (zoom in/out) | H3 | Hierarquia pai/filho limpa |
| PostGIS / bases de dados espaciais | Geohash | Suporte padrão no ecossistema GIS |
A maturidade do ecossistema determina frequentemente a escolha antes de a geometria o fazer. O Geohash está integrado no Redis desde 2015 (GEOADD, GEORADIUS), e o tipo de campo geo_point do Elasticsearch usa Geohash internamente para agregações em grelha. Se a tua stack já inclui algum destes, a integração com Geohash exige quase nenhum trabalho adicional.
O H3 tem bibliotecas robustas 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 geométricas mais poderosas mas o suporte JavaScript mais fraco — a biblioteca principal é em C++, com portes mantidos pela comunidade para outras linguagens.
Trabalhar com H3, Geohash e S2 em Código
Os três sistemas têm bibliotecas em JavaScript e Python. Os exemplos seguintes cobrem as três operações que mais vais 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 — consulta o nosso guia h3-js vs ngeohash.
Dois problemas de biblioteca que vale a pena conhecer antes de colocar em produção:
Precisão de inteiros no ngeohash: as funções encode_int / decode_int estão limitadas a 52 bits em JavaScript (intervalo padrão de inteiro seguro float64 IEEE 754). Para a maioria dos casos de uso isto está bem, mas se definires bitDepth acima de 52, os resultados perdem precisão silenciosamente. Usa a API de string (encode / decode) a não ser que tenhas uma razão específica para trabalhar com inteiros, e passa sempre o mesmo valor de bitDepth de forma consistente tanto nas chamadas de codificação como de descodificação.
Representação de ID de célula no h3-js: os IDs de célula H3 são inteiros de 64 bits, que excedem o intervalo de inteiros seguros do JavaScript (2^53 − 1). A biblioteca h3-js devolve-os como strings por defeito (por exemplo, "8928308280fffff"). Um bug de produção comum: se a tua API serializar IDs H3 como números JSON em vez de strings, alguns parsers JSON arredondarão silenciosamente o valor e corromperão o ID de célula. Trata sempre os IDs H3 como strings em toda a tua stack — no esquema da base de dados, nas respostas da API e no código de 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 porte mantido pela comunidade (pacote npm s2-geometry) que fica atrás da biblioteca C++. Para trabalho sério com S2 em ambientes JS, a abordagem mais prática é chamar um serviço Python/Java ou usar Cloudflare Workers com WASM. Em Python, a biblioteca s2sphere oferece 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)
Podes experimentar a codificação Geohash e H3 diretamente no browser usando a ferramenta de codificação GeoHash da KunYu — sem necessidade de instalar bibliotecas.
FAQ
O H3 é melhor do que o Geohash?
Para novos projetos sem restrições de infraestrutura existente, o H3 é a melhor opção por defeito: tamanhos de célula uniformes, consultas de vizinhos mais limpas e sem bugs de fronteira. O Geohash ganha quando precisas dos comandos GEO do Redis ou de agregações do Elasticsearch de imediato, ou quando é importante ter um ID legível por humanos — as strings Geohash são fáceis de ler em logs; os IDs H3 como 8928308280fffff não são.
O Redis suporta H3?
O Redis não tem suporte nativo para H3. Os comandos GEO integrados (GEOADD, GEORADIUS, GEOSEARCH) usam Geohash internamente. Para usar H3 com Redis, codifica as coordenadas em IDs de célula H3 na camada da tua aplicação e armazena-as como chaves de string normais ou em sorted sets. A partir do Redis 7.x, não há plano de adicionar primitivas H3 ao servidor central.
O que significa S2?
S2 é a abreviatura de Sphere² (esfera ao quadrado), referindo-se ao conceito matemático de mapear uma esfera numa superfície bidimensional. A biblioteca foi desenvolvida na Google e é usada internamente no Google Maps, Google Earth e outros produtos geo da Google.
Quantas células H3 cobrem a Terra na resolução 5?
Na resolução 5, existem 2.016.842 células H3 que cobrem a superfície da Terra (incluindo os 12 pentágonos que fecham o icosaedro). Cada célula cobre aproximadamente 252,9 km², tornando esta resolução útil para análise a nível de país ou grandes áreas metropolitanas.
Posso converter entre H3 e Geohash?
Não existe conversão matemática direta. As duas grelhas 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 de forma aproximada. A abordagem prática é descodificar qualquer um dos formatos para uma coordenada lat/lng e depois recodificar no formato de destino. Tem em atenção 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, produzindo células com 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 excede o que a maioria do hardware GPS consegue fornecer, e as chaves de string longas criam sobrecarga desnecessária de armazenamento e de índice.