46 votos

Uso de geohash para búsquedas de proximidad

Busco optimizar el tiempo de las búsquedas geográficas por proximidad de puntos.

Mi entrada es un punto lat,lng y estoy buscando en un conjunto precalculado de ubicaciones a n puntos más cercanos.

No me importa el tiempo/espacio que se necesite para construir el índice precomputado de ubicaciones, pero sí me importa que las consultas sean súper rápidas.

Estoy pensando en usar geohash como clave de búsqueda, donde primero comprobaría si obtengo resultados para X caracteres de la clave y luego seguiría recortando caracteres desde el final de la clave hasta que empiece a ver resultados.

Según mi (muy escasa por ahora) comprensión de las técnicas de geoíndices, este enfoque debería ser capaz de producir los resultados más rápidos (en términos de tiempo de consulta) en comparación con todas las demás implementaciones conocidas (como R Tree y compañía)

39voto

user28886 Puntos 11

Por supuesto que sí. Y puede ser bastante rápido. (Los bits de cálculo intensivo también pueden distribuirse)

Hay varias maneras, pero una de las que he estado trabajando es en el uso de una lista ordenada de basado en enteros y encontrar todos los rangos de geohashes vecinos más cercanos para una resolución específica de geohash (la resolución se aproxima a su distance ), y luego consultar esos rangos geohash para obtener una lista de puntos cercanos. Para ello utilizo redis y nodejs (es decir, javascript). Redis es súper rápido y puede recuperar rangos ordenados muy rápidamente, pero no puede hacer muchas de las cosas de manipulación de consultas de indexación que las bases de datos SQL pueden hacer.

El método se describe aquí: https://github.com/yinqiwen/ardb/wiki/Spatial-Index

Pero lo esencial es (parafraseando el enlace):

  1. Usted almacena todos sus puntos geohashed en la mejor resolución que desee (máximo generalmente 64bit entero si eso es accesible, o en el caso de javascript, 52bits) en un conjunto ordenado (es decir, zset en redis). La mayoría de las bibliotecas de geohash hoy en día tienen funciones de entero geohash incorporadas, y tendrás que usarlas en lugar de las geohashes más comunes de base32.
  2. Basándose en el radio en el que quiere buscar, necesita encontrar una profundidad de bits/resolución que se ajuste a su área de búsqueda y ésta debe ser menor o igual a la profundidad de bits de su geohash almacenado. El sitio enlazado tiene una tabla que correlaciona la profundidad de bits de un geohash con el área de su cuadro delimitador en metros.
  3. A continuación, se vuelve a repetir la coordenada original con esta resolución más baja.
  4. En esa resolución inferior también se encuentran las 8 áreas geohash vecinas (n, ne, e, se, s, sw, w, nw). La razón por la que hay que aplicar el método de los vecinos es que dos coordenadas casi contiguas pueden tener geohashes completamente diferentes, por lo que hay que hacer un promedio del área cubierta por la búsqueda.
  5. Una vez que obtenga todos los geohashes vecinos en esta resolución inferior, añada a la lista el geohash de su coordenada del paso 3.
  6. Entonces, es necesario que construyas un gama de valores geohash para buscar dentro de los cuales cubren estas 9 áreas. Los valores del paso 5 son el límite inferior del rango, y si añade 1 a cada uno de ellos, obtendrá el límite superior del rango. Así que debería tener una matriz de 9 rangos, cada uno con un límite inferior y un límite superior de geohash (18 geohashes en total). Estos geohashes están todavía en esa resolución inferior del paso 2.
  7. A continuación, conviertes las 18 geohachas a la profundidad de bits/resolución en la que hayas almacenado todas las geohachas de tu base de datos. Por lo general, esto se hace mediante el cambio de bits a la profundidad de bits deseada.
  8. Ahora puedes hacer una consulta de rango para los puntos dentro de estos 9 rangos y obtendrás todos los puntos aproximadamente dentro de la distancia de tu punto original. No habrá solapamiento, por lo que no necesitas hacer ninguna intersección, sólo consultas de rango puras, muy rápidas (es decir, en redis: ZRANGEBYSCORE zsetname lowerLimit upperLimit, sobre los 9 rangos producidos en este paso)

Puedes optimizar aún más (en cuanto a la velocidad) esto:

  1. Tomar esos 9 rangos del paso 6 y encontrar dónde desembocan los unos en los otros. Por lo general, puedes reducir 9 rangos separados en unos 4 o 5, dependiendo de dónde esté tu coordenada. Esto puede reducir el tiempo de consulta a la mitad.
  2. Una vez que tenga sus rangos finales, debe guardarlos para su reutilización. El cálculo de estos rangos puede tomar la mayor parte del tiempo de procesamiento, por lo que si su coordenada original no cambia mucho pero necesita hacer la misma consulta de distancia de nuevo, debe mantenerlo listo en lugar de calcularlo cada vez.
  3. Si estás usando redis, trata de combinar las consultas en un MULTI/EXEC para que las canalice y mejore el rendimiento.
  4. La mejor parte: Puedes distribuir los pasos 2-7 en los clientes en lugar de tener ese cálculo hecho todo en un solo lugar. Esto reduce en gran medida la carga de la CPU en situaciones en las que entrarían millones de solicitudes.

Puede mejorar aún más la precisión utilizando una función de distancia circular/tipo Haversine en los resultados devueltos si le importa mucho la precisión.

Aquí hay una técnica similar utilizando geohashes base32 ordinarios y una consulta SQL en lugar de redis: https://github.com/davetroy/geohash-js

No es mi intención tapar lo mío, pero he escrito un módulo para nodejs&redis que hace que esto sea realmente fácil de implementar. Echa un vistazo al código si quieres: https://github.com/arjunmehta/node-georedis

9voto

cjstehno Puntos 131

La pregunta puede leerse de varias maneras. Yo la interpreto como que tienes un gran número de puntos y pretendes sondearlos repetidamente con puntos arbitrarios, dados como pares de coordenadas, y deseas obtener los n puntos más cercanos al sondeo, con n fijado de antemano. (En principio, si n va a variar, se podría establecer una estructura de datos para cada posible n y seleccionarlo en tiempo O(1) con cada sonda: esto podría llevar un tiempo de configuración muy largo y requerir mucha RAM, pero se nos dice que ignoremos tales preocupaciones).

Construir el diagrama de Voronoi de orden n de todos los puntos. Esto divide el plano en regiones conectadas, cada una de las cuales tiene los mismos n vecinos. Esto reduce la situación al problema de punto en polígono, que tiene muchas soluciones eficientes.

Utilizando una estructura de datos vectorial para el diagrama de Voronoi, las búsquedas de puntos en polígonos tardarán O(log(n)). A efectos prácticos, se puede hacer O(1) con un coeficiente implícito extremadamente pequeño simplemente creando una versión raster del diagrama. Los valores de las celdas del raster son: (i) un puntero a una lista de los n puntos más cercanos o (ii) una indicación de que esta celda se encuentra a caballo entre dos o más regiones del diagrama. La prueba para un punto arbitrario en (x,y) se convierte en

Fetch the cell value for (x,y).
If the value is a list of points, return it.
Else apply a vector point-in-polygon algorithm to (x,y).

Para conseguir un rendimiento O(1), la malla rasterizada tiene que ser lo suficientemente fina como para que relativamente pocos puntos de la sonda caigan en celdas que atraviesen múltiples regiones de Voronoi. Esto siempre puede lograrse, con un gasto potencialmente grande en el almacenamiento de las mallas.

4voto

Richard Dingwall Puntos 1092

Yo recomendaría utilizar la consulta GEORADIUS en redis.

Empuje los datos fragmentados por el nivel de geohash más adecuado utilizando la llamada GEOADD.

Además, echa un vistazo a esto -> ProximityHash .

ProximityHash genera un conjunto de geohashes que cubren un área circular, dadas las coordenadas del centro y el radio. También tiene una opción adicional para utilizar GeoRaptor que crea la mejor combinación de geohashes a través de varios niveles para representar el círculo, empezando por el nivel más alto e iterando hasta que se elabora la mezcla óptima. La precisión del resultado sigue siendo la misma que la del nivel de geohash inicial, pero el tamaño de los datos se reduce considerablemente, lo que mejora la velocidad y el rendimiento.

4voto

Iznogood Puntos 143

Actualización para los de 2018, y algunas fundaciones matemáticas o histórico-probatorias de Geohash:

  • la inspiración para Geohash fue el intercalación simple de dígitos binarios , quizás una optimización de los algoritmos ingenuos que intercalan dígitos decimales, como la de C-cuadrado .

  • el entrelazamiento binario dio lugar a un Curva de orden Z estrategia de índices, naturalmente, el inventor de Geohash pas comenzó a "buscar la mejor curva fractal"... Pero curiosamente, esta optimización del diseño, una mejor curva fractal, es posible (!).

Utilizar la biblioteca de geometría S2

El enfoque de la geometría S2 es mejor que el de Geohash porque utiliza la topología esférica del globo terráqueo (un cubo), utiliza la opción proyección (para que todas las celdas tengan casi la misma forma y área), y porque la indexación con Curva de Hilbert es mejor que Curva de orden Z :

... podemos hacerlo mejor... La discontinuidad al pasar del cuadrante superior derecho al inferior izquierdo hace que tengamos que dividir algunos rangos que de otro modo podríamos hacer contiguos. (...) podemos eliminar completamente las discontinuidades (...)
blog.notdot.net/2009 sobre la indexación espacial con Quadtrees y Curvas de Hilbert

Ahora es una biblioteca gratuita y eficiente, ver https://s2geometry.io

P.D.: también hay (buenas) versiones simplificadas no oficiales como NodeJS's s2-geometry y muchos "playgrounds", complementos y demos, como s2.sidewalklabs.com .

3voto

Mickey Shine Puntos 420

Yo utilizo los geohashes exactamente para esto. La razón por la que lo hago es porque necesitaba implementar búsquedas de proximidad utilizando un sistema de información de estilo piramidal.. donde las geohachas con un nivel de precisión 8 eran la "base" y formaban nuevos totales para las geohachas de la precisión 7.. y así sucesivamente. Estos totales eran superficie, tipos de cobertura del suelo, etc. Era una forma muy elegante de hacer cosas muy elegantes.

Así, los geohashes de 8º nivel contendrían información como:

tipo: hierba hectáreas: 1,23

y la 7ª, 6ª etc contendría información como:

tipos_de_hierba: 123 hectáreas: 6502

Esto siempre se construyó desde la más baja precisión. Esto me permitió hacer todo tipo de estadísticas divertidas muy rápidamente. También pude asignar una referencia geométrica a cada referencia geohash utilizando GeoJSON.

Pude escribir varias funciones para encontrar los geohashes más grandes que componen mi viewport actual y luego usarlos para encontrar geohashes de la segunda precisión más grande dentro del viewport. Esto podría extenderse fácilmente a las consultas de rango indexado en las que buscaría un mínimo de '86ssaaaa' y un máximo de '86sszzzz' para cualquier precisión que quisiera.

Estoy haciendo esto usando MongoDB.

i-Ciencias.com

I-Ciencias es una comunidad de estudiantes y amantes de la ciencia en la que puedes resolver tus problemas y dudas.
Puedes consultar las preguntas de otros usuarios, hacer tus propias preguntas o resolver las de los demás.

Powered by:

X