Cómo funciona el sistema de hexágonos de Uber
H3 es el índice geoespacial que Uber abrió en 2018 para convertir posiciones continuas (lat/lng) en celdas hexagonales discretas sobre las que contar, comparar y agregar. La malla se construye proyectando la Tierra sobre un icosaedro, se divide en 16 resoluciones cada vez más finas y asigna a cada celda un entero de 64 bits que lleva codificada su posición en la jerarquía. Uber lo usa para medir oferta y demanda por zona y alimentar el precio dinámico y el reparto de viajes. Si tienes una app con datos de localización y necesitas razonar sobre "qué pasa en esta zona", este es el mecanismo que hay debajo.
Por qué hexágonos
Solo tres polígonos regulares teselan el plano sin dejar huecos: el triángulo, el cuadrado y el hexágono. De los tres, el hexágono es el único donde todos los vecinos están a la misma distancia del centro. Un cuadrado tiene dos distancias (los cuatro lados y las cuatro diagonales); un triángulo, tres; el hexágono, una sola.
Esa uniformidad importa más de lo que parece. Cualquier cálculo que dependa de lo que rodea a un punto (densidad de demanda, cómo se difunde un precio de una zona a la contigua, suavizado de un gradiente) deja de tener casos especiales por dirección. Un vecino es un vecino, y todos pesan igual. El hexágono además aproxima un círculo mejor que el cuadrado, y un radio alrededor de un punto es justo lo que quieres cuando preguntas cuántos conductores hay cerca de aquí.
Cómo se construye la malla
Aquí está el detalle que casi todas las explicaciones se saltan: no puedes cubrir una esfera solo con hexágonos. Es topológicamente imposible. Así que H3 no tesela la Tierra directamente.
El sistema proyecta la esfera sobre un icosaedro (el poliedro de 20 caras) con una proyección gnomónica centrada en cada cara. Sobre él coloca 122 celdas base: 110 hexágonos y 12 pentágonos. Los pentágonos son la deuda geométrica de trabajar sobre una esfera, uno por cada vértice del icosaedro. Uber los orienta con la disposición Dymaxion de Buckminster Fuller, que deja los 12 vértices en el océano. La razón es práctica: un pentágono tiene cinco vecinos en lugar de seis, y cualquier algoritmo que dé por hecho seis se rompe sobre él. Esconderlos en mitad del Atlántico y el Pacífico baja la probabilidad de tropezar con uno en tierra, pero no la anula.
Sobre esas celdas base se levanta la jerarquía con apertura 7: cada resolución más fina parte la celda padre en aproximadamente siete hijas, la longitud unitaria se escala por raíz de 7 y cada hexágono pasa a tener un séptimo del área del nivel anterior. Hay 16 resoluciones, de la 0 a la 15, y el número de celdas por resolución sale de una fórmula cerrada:
c(r) = 2 + 120 · 7^r
Que da esta escala:
| Res | Celdas | Área media | Arista media |
|---|---|---|---|
| 0 | 122 | 4.357.449 km² | 1.281 km |
| 5 | 2.016.842 | 252,9 km² | 9,85 km |
| 7 | 98.825.162 | 5,16 km² | 1,41 km |
| 9 | 4.842.432.842 | 0,11 km² | 0,20 km |
| 11 | 237.279.209.162 | 2.100 m² | 29 m |
| 15 | 569.707.381.193.162 | 0,9 m² | 0,58 m |
La resolución 9, con hexágonos de unos 0,2 km de arista (una manzana grande), es la que Uber emplea para buena parte de su análisis de mercado urbano. La 15 baja de un metro cuadrado por celda y llega a 569 billones de celdas, de sobra para direccionar cualquier punto del planeta.
El índice de 64 bits
Cada celda es un entero de 64 bits, y ese entero no es opaco: lleva dentro toda la jerarquía. El reparto de bits es este:
- 1 bit reservado (0)
- 4 bits de modo (para celdas, un valor fijo)
- 3 bits reservados (0)
- 4 bits de resolución (0-15)
- 7 bits de celda base (0-121)
- 45 bits de dígitos: 15 dígitos de 3 bits, uno por resolución, cada uno con valor de 0 a 6
De ahí sale la propiedad que hace útil a H3. Para subir a la celda padre en una resolución más gruesa truncas los dígitos finos y ajustas el campo de resolución. Es una operación de bits: sin consultar tablas, sin recalcular geometría. Moverse por la jerarquía, de un barrio a su ciudad y de la ciudad a su región, es aritmética entera. Y como el índice cabe en un bigint, lo ordenas, lo indexas y lo agrupas en la base de datos que ya tienes, sin tipos geométricos especiales.
La trampa: los hexágonos no anidan
Este es el punto que conviene entender antes de construir nada encima, porque contradice lo que sugiere la palabra "jerárquico".
Un cuadrado se parte en cuatro cuadrados exactos. Un hexágono no se parte en siete hexágonos exactos: es geométricamente imposible. Los siete hijos de una celda H3 no caben dentro del padre sin pequeños huecos y solapamientos en el borde. La contención padre-hijo es aproximada. Para la mayoría de usos (pintar densidad, agrupar demanda, fijar un precio de zona) ese error de borde es despreciable. Pero si tu requisito es un recuento exacto, cuántos pedidos cayeron dentro de este polígono ni uno más ni uno menos, H3 no es la herramienta: la celda no hereda del padre una frontera nítida.
Los pentágonos son la segunda excepción. Hay 12 en cada resolución, con cinco vecinos en vez de seis. Cualquier función que recorra la vecindad de una celda, como gridDisk (el anillo de celdas que la rodean), se comporta distinto junto a un pentágono. Están en el océano por diseño, pero si tu operación es marítima, aérea o global, tarde o temprano los pisas y el caso especial reaparece.
H3 frente a S2 y geohash
H3 no es el único índice, y elegirlo por inercia es un error. Las alternativas serias son geohash y S2, y cada una gana en algo distinto.
Geohash codifica la posición en una cadena de texto donde el prefijo es la jerarquía: gcpv contiene a gcpvj. Eso vuelve triviales las consultas por rango con un LIKE 'gcpv%' en cualquier base de datos. A cambio usa rectángulos que se deforman con la latitud y arrastra el problema de frontera: dos puntos pegados a lados opuestos de un borde tienen códigos distintos, y buscar vecinos obliga a calcular a mano hasta ocho celdas adyacentes.
S2 proyecta la esfera sobre un cubo y recorre cada cara con una curva de Hilbert, lo que da celdas de área casi uniforme y muy buena localidad espacial en el índice ordenado. Divide de cuatro en cuatro, compatible con binario y rápido, y ofrece 31 niveles frente a los 16 de H3. Si procesas cientos de millones de registros por segundo y priorizas rendimiento bruto y consultas por rango, S2 es la opción más fría.
H3 gana en lo que Uber necesitaba: agregar y razonar sobre vecindad. Los hexágonos teselan parejo, la distancia única a los vecinos quita casos especiales de cualquier cálculo de proximidad, y la malla se lee bien cuando la pintas en un mapa. El precio es que su ID no es prefijable como el geohash: no hay consulta por rango sobre el texto del índice, subes por la jerarquía con la librería.
Qué haríamos nosotros
Si tu app tiene datos de localización y necesitas agregar, comparar zonas o preguntar qué hay cerca (reparto, flota, analítica de retail por área de influencia, telemática de seguros, mapas de calor de uso), H3 es una buena elección y el tooling está maduro: hay soporte en BigQuery, Snowflake, DuckDB y PostGIS, y bindings para casi cualquier lenguaje. Escoges una resolución acorde a tu grano de análisis, guardas el bigint junto a tus filas y agregas con un GROUP BY normal.
No lo elijas si tu problema es encontrar el punto más cercano en crudo: para eso una columna geográfica con el índice espacial de tu base de datos es más directa. Tampoco si necesitas recuentos exactos por polígono, donde los bordes aproximados de H3 te darán cifras que no cuadran. Y si tu carga es global sobre océano, o de rendimiento extremo con consultas por rango, evalúa S2 antes de cerrar la decisión.
El hexágono de Uber es una pieza de ingeniería bien pensada, pero resuelve un problema acotado: pasar de posiciones continuas a celdas comparables para agregar y analizar a escala de ciudad. Cuando ese es tu problema, encaja; cuando no, añade complejidad sin pagarla.
Elegir el índice geoespacial correcto es una decisión de arquitectura de backend que condiciona el resto del sistema, y suele decidirse antes de escribir la primera migración. Es el tipo de trabajo que hacemos en nuestros servicios de desarrollo; si además el backend va en Go, contamos por qué lo elegimos para cargas de alto rendimiento.




