Usando geohash para pesquisas por proximidade?

30

Estou procurando otimizar o tempo de pesquisas geográficas de proximidade de pontos.

Minha entrada é lat, lng point e estou pesquisando em um conjunto pré-computado de locais para n pontos mais próximos.

Não me importo quanto tempo / espaço a construção do índice pré-computado de locais levará, mas me importo que as consultas sejam super rápidas.

Estou pensando em usar o geohash como a chave de pesquisa, onde primeiro verificaria se obtive resultados para X caracteres da chave e continuaria a reduzir caracteres do final da chave até começar a ver os resultados.

Para meu entendimento (muito escasso por enquanto) das técnicas de índice geográfico, essa abordagem deve ser capaz de produzir os resultados mais rápidos (em termos de tempo de consulta) em comparação com todas as outras implementações conhecidas (como R Tree e companhia).

Maxim Veksler
fonte
Existe uma diferença significativa entre o uso de uma geohash e o armazenamento de seu lat / long no leste / norte (por exemplo)? Presumivelmente, com os dois, você pode alterar a precisão da pesquisa cortando caracteres / dígitos. (Isso é puramente uma questão de curiosidade - não estou familiarizado com este tópico).
DJQ
Esses pontos são armazenados em um banco de dados ou na memória ou?
Marc Pfister
@ MarcPfister, esse problema tem 2 anos (para o meu caso de uso), mas é sempre relevante para a comunidade, por isso continuarei a discussão ativa. Os dados discutidos foram realmente armazenados em um banco de dados nosql.
precisa
Além disso, acredito que desde o momento em que essa pergunta foi respondida, o MongoDB implementou com sucesso a indexação e pesquisa de geohash, o que prova esse ponto. Ainda não vi um white paper da implementação, mas o código está aberto e disponível para qualquer parte interessada.
precisa
Ah ok. O CouchDB também tinha indexação espacial agora, provavelmente também usando geohash.
Marc Pfister

Respostas:

25

Absolutamente você pode. E isso pode ser bastante rápido. (Os bits intensivos de computação também podem ser distribuídos)

Existem várias maneiras, mas uma delas com a qual estou trabalhando é usar uma lista ordenada de geohashes baseadas em números inteiros e encontrar todos os intervalos de geohash vizinhos mais próximos para uma resolução específica de geohash (a resolução se aproxima de seus distancecritérios) e, em seguida, consultando esses intervalos de geohash para obter uma lista de pontos próximos. Eu uso redis e nodejs (isto é, javascript) para isso. O Redis é super rápido e pode recuperar intervalos ordenados muito rapidamente, mas não pode executar muitas das tarefas de manipulação de consulta de indexação que os bancos de dados SQL podem fazer.

O método está descrito aqui: https://github.com/yinqiwen/ardb/wiki/Spatial-Index

Mas a essência disso é (parafraseando o link):

  1. Você armazena todos os seus pontos com geohashed na melhor resolução desejada (no máximo, geralmente, número inteiro de 64 bits, se estiver acessível, ou no caso de javascript, 52 bits) em um conjunto ordenado (por exemplo, zset in redis). Atualmente, a maioria das bibliotecas de geohash possui funções inteiras de geohash integradas, e você precisará usá-las em vez das geohashes base32 mais comuns.
  2. Com base no raio em que você deseja pesquisar, é necessário encontrar uma profundidade / resolução de bits que corresponda à sua área de pesquisa e isso deve ser menor ou igual à profundidade de bits da geohash armazenada. O site vinculado possui uma tabela que correlaciona a profundidade de bits de uma geohash com a área da caixa delimitadora em metros.
  3. Em seguida, refaça a sua coordenada original nesta resolução mais baixa.
  4. Nessa resolução mais baixa, encontre também as 8 áreas de geohash vizinhas (n, ne, e, se, s, sw, w, nw). A razão pela qual você deve executar o método vizinho é que duas coordenadas quase ao lado uma da outra podem ter geohashes completamente diferentes; portanto, é necessário fazer uma média da área coberta pela pesquisa.
  5. Depois de obter todas as geohashes vizinhas nesta resolução mais baixa, adicione à lista a geohash de sua coordenada na etapa 3.
  6. Então você precisa criar um intervalo de valores de geohash para pesquisar dentro dos quais abrangem essas 9 áreas. Os valores da etapa 5 são o limite inferior do intervalo e, se você adicionar 1 a cada um deles, obterá o limite superior. Portanto, você deve ter uma matriz de 9 intervalos, cada um com um limite inferior e um limite superior de geohash (18 geohashes no total). Essas geohashes ainda estão nessa resolução mais baixa da etapa 2.
  7. Em seguida, você converte todos os 18 desses geohashes em qualquer profundidade / resolução em que armazenou todos os geohashes no banco de dados. Geralmente, você faz isso deslocando os bits para a profundidade de bits desejada.
  8. Agora você pode fazer uma consulta por pontos dentro desses 9 intervalos e obterá todos os pontos aproximadamente dentro da distância do seu ponto original. Não haverá sobreposição, portanto você não precisa fazer interseções, apenas consultas de alcance puro, muito rapidamente. (ou seja, em redis: ZRANGEBYSCORE zsetname lowerLimit upperLimit, nos 9 intervalos produzidos nesta etapa)

Você pode otimizar ainda mais (velocidade) isso:

  1. Tomar esses 9 intervalos da etapa 6 e descobrir onde eles levam um ao outro. Normalmente, você pode reduzir 9 intervalos separados para cerca de 4 ou 5, dependendo da sua coordenada. Isso pode reduzir o tempo de consulta pela metade.
  2. Depois de ter seus intervalos finais, você deve mantê-los para reutilização. O cálculo desses intervalos pode levar a maior parte do tempo de processamento; portanto, se sua coordenada original não mudar muito, mas você precisar fazer a mesma consulta de distância novamente, mantenha-o pronto em vez de calculá-lo sempre.
  3. Se você estiver usando redis, tente combinar as consultas em um MULTI / EXEC para que elas sejam direcionadas para um desempenho um pouco melhor.
  4. A parte MELHOR: você pode distribuir as etapas de 2 a 7 nos clientes, em vez de fazer esse cálculo em um só lugar. Isso reduz bastante a carga da CPU em situações em que milhões de solicitações chegariam.

Você pode melhorar ainda mais a precisão usando uma função de distância do círculo / tipo haversine nos resultados retornados se você se preocupa muito com a precisão.

Aqui está uma técnica semelhante usando geohashes base32 comuns e uma consulta SQL em vez de redis: https://github.com/davetroy/geohash-js

Não pretendo conectar minhas coisas, mas escrevi um módulo para nodejs & redis que facilita muito a implementação. Dê uma olhada no código, se você quiser: https://github.com/arjunmehta/node-georedis

Arjun Mehta
fonte
Um par de acompanhamento P - Como você calcula os vizinhos? Is hash inteiro permite o corte (a base32 com base na curva z não, por exemplo (7 está muito longe de 8 na base32 geohash). Como é o método descrito em geohash-js github.com/davetroy/geohash-js/blob/ master / matrix.txt semelhante Enquanto este algoritmo suposto produz proximidade geo-pontos Geohash-js faz O (1) cálculo das células vizinhas única?.
Maxim Veksler
Uau, isso foi tão útil. Tanta experiência nessa resposta. Tarefa bastante desafiadora
simon
9

A pergunta pode ser lida de várias maneiras. Interpreto que isso significa que você tem um grande número de pontos e pretende sondá-los repetidamente com pontos arbitrários, dados como pares de coordenadas, e deseja obter os n pontos mais próximos da sonda, com n fixado previamente. (Em princípio, se n variar, você poderá configurar uma estrutura de dados para todos os n possíveis e selecioná-la no tempo O (1) com cada análise: isso pode levar um tempo de configuração muito longo e exigir muita RAM, mas nós são instruídos a ignorar essas preocupações.)

Crie o diagrama Voronoi de ordem n de todos os pontos. Isso divide o plano em regiões conectadas, cada uma com os mesmos n vizinhos. Isso reduz a situação ao problema do ponto no polígono, que tem muitas soluções eficientes.

Usando uma estrutura de dados vetoriais para o diagrama de Voronoi, as pesquisas point-in-polygon levarão tempo O (log (n)). Para fins práticos, você pode fazer esse O (1) com um coeficiente implícito extremamente pequeno, simplesmente criando uma versão rasterizada do diagrama. Os valores das células na varredura são: (i) um ponteiro para uma lista dos n pontos mais próximos ou (ii) uma indicação de que essa célula se estende por duas ou mais regiões no diagrama. O teste para um ponto arbitrário em (x, y) se torna:

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 obter o desempenho O (1), a malha rasterizada deve ser suficientemente fina para que relativamente poucos pontos de sonda caiam nas células que atravessam várias regiões de Voronoi. Isso sempre pode ser realizado, com uma despesa potencialmente grande em armazenamento para as grades.

whuber
fonte
3

Eu uso geohashes exatamente para isso. A razão de eu ser é porque eu precisava implementar pesquisas de proximidade usando um sistema de informações no estilo pirâmide .. onde geohashes com precisão de 8º nível eram a 'base' e formava novos totais para geohashes de 7ª precisão ... e assim por diante. . Esses totais foram área, tipos de cobertura do solo, etc. Era uma maneira muito sofisticada de fazer coisas muito sofisticadas.

Portanto, as geohashes do 8º nível conteriam informações como:

tipo: grama acres: 1,23

e 7, 6, etc., etc. conteriam informações como:

Tipo de grama: 123 acres: 6502

Isso sempre foi construído com a menor precisão. Isso me permitiu fazer todo tipo de estatística divertida muito rapidamente. Também pude atribuir uma referência de geometria a cada referência de geohash usando GeoJSON.

Consegui escrever várias funções para encontrar as maiores geohashes que compõem minha viewport atual e depois usá-las para encontrar geohashes da segunda maior precisão dentro da viewport. Isso poderia ser facilmente estendido para consultas de intervalo indexado, nas quais eu solicitaria um mínimo de '86ssaaaa' e um máximo de '86sszzzz' para qualquer precisão que eu quisesse.

Estou fazendo isso usando o MongoDB.

mais difícil
fonte
3

Atualização para 2018 e algumas fundações matemáticas ou proveniência histórica de Geohash:

  • a inspiração para Geohash foi o interlave simples de dígitos binários , talvez uma optimização de algoritmos ingênuos que intercalados dígitos decimais, como o de C-quadrados .

  • Como o entrelaçamento binário resultou em uma estratégia de índice de curva de ordem Z naturalmente, o inventor do Geohash não começou a "procurar a melhor curva fractal" ... Mas, curiosamente, essa otimização de design, uma melhor curva fractal, é possível (!).

Usar biblioteca de geometria S2

A abordagem da geometria S2 é melhor que o Geohash, porque usa a topologia esférica do mundo (um cubo), usa projeção opcional (para que todas as células tenham quase a mesma forma e a área próxima) e porque a indexação com a curva de Hilbert é melhor que Z- curva de ordem :

... podemos fazer melhor ... A descontinuidade, à medida que avançamos do quadrilátero superior direito para o inferior esquerdo, resulta na divisão de alguns intervalos que, de outra forma, poderíamos tornar contíguos. (...) podemos eliminar completamente quaisquer descontinuidades (...)
blog.notdot.net/2009 sobre indexação espacial com as curvas de Quadtrees e Hilbert

Agora é uma biblioteca gratuita e eficiente, consulte https://s2geometry.io

PS: também existem (boas) versões simplificadas não oficiais como NodeJSs2-geometry e muitos "playgrounds", suplementos e demos, como s2.sidewalklabs.com .

Peter Krauss
fonte
2

Eu recomendaria usar a consulta GEORADIUS em redis.

Envie os dados agrupados pelo nível de geohash mais adequado usando a chamada GEOADD.

Além disso, dê uma olhada neste -> ProximityHash .

O ProximityHash gera um conjunto de geohashes que cobrem uma área circular, dadas as coordenadas do centro e o raio. Ele também possui uma opção adicional para usar o GeoRaptor, que cria a melhor combinação de geohashes em vários níveis para representar o círculo, começando do nível mais alto e repetindo até que a mistura ideal seja preparada. A precisão do resultado permanece a mesma do nível inicial de geohash, mas o tamanho dos dados reduz consideravelmente, melhorando assim a velocidade e o desempenho.

Ashwin Nair
fonte