使用geohash进行邻近搜索?


30

我正在寻找优化点邻近地理搜索时间的方法。

我的输入是lat,lng点,我正在搜索到n个最近点的一组预先计算的位置。

我不在乎预先计算位置索引所花费的时间/空间,但我确实在意查询会非常快。

我正在考虑使用geohash作为搜索关键字,在该方法中,我将首先检查是否获得了该关键字的X个字符的结果,然后继续从关键字的末尾开始缩减字符,直到开始看到结果为止。

就我(目前非常稀疏)对地理索引技术的理解而言,与所有其他已知实现(例如R Tree和co。)相比,该方法应能够产生最快的结果(就查询时间而言)。


使用geohash和将纬度/经度存储在东部/北部(例如)之间有显着区别吗?大概两者都可以通过修剪字符/数字来更改搜索精度。(这纯粹是出于好奇而提出的问题-我不熟悉此主题)。
djq 2011年

这些点存储在数据库还是内存中?
Marc Pfister 2014年

@MarcPfister这个问题已有2年历史了(对于我的用例而言),但是它与社区始终相关,因此我将继续进行积极的讨论。讨论的数据确实存储在nosql数据库中。
Maxim Veksler 2014年

另外,我相信从回答这个问题开始,MongoDB就已经成功实现了geohash索引和搜索,这证明了这一点。我还没有看到实现的白皮书,但是该代码是开放的,任何感兴趣的人士都可以使用。
Maxim Veksler 2014年

喔好吧。CouchDB现在也有空间索引,可能也使用了geohash。
Marc Pfister 2014年

Answers:


25

绝对可以。而且速度可能很快。(密集计算位也可以分配)

有几种方法,但是我一直在使用的一种方法是使用基于整数的 geohash 的有序列表,并为特定的geohash分辨率(分辨率近似于您的distance标准)找到所有最近的邻域geohash范围,然后查询这些geohash范围以获取附近点的列表。我为此使用redis和nodejs(即javascript)。Redis超级快,可以非常快速地检索有序范围,但是它不能执行SQL数据库可以执行的很多索引查询操作。

该方法在此处概述:https : //github.com/yinqiwen/ardb/wiki/Spatial-Index

但其要点是(释义链接):

  1. 您将所有geohashed点以所需的最佳分辨率(如果可以访问,则通常为最大64位整数,如果是javascript,则为52位整数)存储在有序集中(即redis中的zset)。如今,大多数geohash库都内置了geohash整数函数,您需要使用这些函数而不是更常见的base32 geohash。
  2. 根据要在其中搜索的半径,然后需要找到与搜索区域相匹配的位深度/分辨率,并且该深度/分辨率必须小于或等于存储的geohash位深度。链接的站点具有一个表格,该表格将geohash的位深度与其以米为单位的边界框区域相关联。
  3. 然后,您以较低的分辨率重新哈希化原始坐标。
  4. 在该较低分辨率下,还可以找到8个邻居(n,ne,e,se,s,sw,w,nw)geohash区域。之所以必须执行近邻方法,是因为彼此紧邻的两个坐标可能具有完全不同的地理哈希值,因此您需要对搜索范围进行平均。
  5. 在以较低的分辨率获得所有相邻的geohash后,将步骤3中的坐标的geohash添加到列表中。
  6. 然后,您需要构建要搜索的geohash值范围,该范围涵盖这9个区域。第5步中的值是您的范围下限,如果将每个范围加1,您将获得范围的上限。因此,您应该有一个9个范围的数组,每个范围都有一个下限和一个上限geohash限制(总共18个geohashhes)。这些geohash仍处于步骤2中的较低分辨率。
  7. 然后,您将所有18种地理哈希值转换为将所有地理哈希值存储在数据库中的任何位深度/分辨率。通常,您可以通过将其移位到所需位深度来实现此目的。
  8. 现在,您可以对这9个范围内的点进行范围查询,所有点都将在原始点的距离内。不会有重叠,因此您不需要非常快地进行任何交叉,只需进行纯范围查询即可。(即在redis中:ZRANGEBYSCORE zsetname lowerLimit upperLimit,在此步骤中生成的9个范围内)

您可以通过以下方法(在速度方面)进一步优化:

  1. 从第6步中获取这9个范围,并找出它们之间的相互影响。通常,您可以根据坐标所在的位置将9个单独的范围缩小为大约4或5。这样可以将查询时间减少一半。
  2. 拥有最终范围后,应保留它们以备重复使用。这些范围的计算会占用大部分处理时间,因此,如果原始坐标变化不大,但是您需要再次进行相同的距离查询,则应准备好该距离,而不是每次都进行计算。
  3. 如果您使用的是redis,请尝试将查询合并到MULTI / EXEC中,以便对其进行管线化以提高性能。
  4. 最好的部分:您可以在客户端上分配步骤2-7,而不是将计算全部放在一个地方。在有数百万个请求进入的情况下,这可以大大减少CPU负载。

如果您非常关注精度,则可以对返回的结果使用圆距离/ haversine类型函数来进一步提高精度。

这是使用普通的base32地理哈希和SQL查询而不是redis的类似技术:https : //github.com/davetroy/geohash-js

我并不是要插入自己的东西,但我已经为nodejs&redis编写了一个模块,该模块非常容易实现。如果您愿意,请看一下代码:https : //github.com/arjunmehta/node-georedis


一些后续问题Q-您如何计算邻居?是整数哈希允许修整(例如,base32 z曲线不允许修整)(base32 geohash中的7与8距离非常远)。geohash-js github.com/davetroy/geohash-js/blob/中概述的方法如何? master / matrix.txt类似吗?虽然该算法应该产生邻近地理点geohash-js,但仅执行邻区的O(1)计算
Maxim Veksler,2015年

哇,这真有用。如此丰富的专业知识。颇具挑战性的任务
simon,

9

这个问题可以通过几种方式来理解。我将其解释为意味着您拥有大量点,并且打算使用坐标对给出的任意点重复探测它们,并希望获得n个与探测点最接近的点,并预先固定n个点。(原则上,如果n会变化,则可以为每个可能的n 设置一个数据结构,并在每个探针的O(1)时间中选择它:这可能需要很长的设置时间,并且需要大量RAM,但是我们被告知忽略此类担忧。)

建立所有点的n阶Voronoi图。这将平面划分为相连的区域,每个区域具有相同的n个邻居。这将情况减少到多边形点问题,该问题具有许多有效的解决方案。

使用Voronoi图的矢量数据结构,多边形中点搜索将花费O(log(n))时间。出于实际目的,只需创建图表的栅格版本,即可使O(1)的隐式系数极小。栅格中像元的值要么是(i)指向n个最近点的列表的指针,要么是(ii)该像元跨过图中两个或多个区域的指示。在(x,y)处的任意点的检验变为:

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).

为了实现O(1)性能,栅格网格必须足够精细,以使相对较少的探测点落入跨越多个Voronoi区域的像元中。这总是可以实现的,而且可能会为网格存储付出巨大的代价。


3

我完全使用geohash。我之所以这样,是因为我需要使用金字塔样式的信息系统来实现邻近搜索。在这种情况下,精度为8级的地质哈希是“基础”,并为精度为7的地质哈希形成了新的总计。等等。 。这些总数是面积,地面覆盖物的类型等。这是做一些非常花哨的事情的非常花哨的方法。

因此,第8级地理哈希将包含以下信息:

类型:草英亩:1.23

和7th,6th ..等。将包含以下信息:

grass_types:123英亩:6502

这总是以最低的精度建立的。这使我可以非常快速地进行各种有趣的统计。我还能够使用GeoJSON为每个geohash参考分配一个几何参考。

我能够编写几个函数来查找构成当前视口的最大地理哈希,然后使用这些函数来查找视口中第二大精度的地理哈希。这可以很容易地扩展到索引范围查询,无论我想要什么精度,我都会查询最小的“ 86ssaaaa”和最大的“ 86sszzzz”。

我正在使用MongoDB执行此操作。


3

更新2018年以及Geohash的一些数学基础或历史渊源:

  • 对于地理散列的灵感是二进制数字简单interlave,也许那交错的十进制数字,像天真的算法优化了的C-广场

  • 二进制隔行扫描自然产生了Z阶曲线索引策略,Geohash发明者并未开始“寻找最佳的分形曲线”……但是,有趣的是,这种设计优化,更好的分形曲线是可能的(!)。

使用S2几何库

S2几何方法比Geohash更好,因为它使用了地球的球形拓扑(立方体),使用了可选的投影(所以所有像元都具有近似相同的形状和面积),并且因为用希尔伯特曲线进行分度比Z-订单曲线

...我们可以做得更好...从右上角到左下角四边形的不连续性导致我们不得不分割一些我们可以连续的范围。(...)我们可以完全消除使用四叉树和希尔伯特曲线进行空间索引的所有不连续性(...)
blog.notdot.net/2009

现在它是一个免费高效的库,请参阅https://s2geometry.io

PS:还有(好)非官方精简版本为的NodeJS的s2-geometry,和许多“游乐场”,加载项和演示,为s2.sidewalklabs.com


2

我建议在redis中使用GEORADIUS查询。

使用GEOADD调用推送按最合适的geohash级别分片的数据。

另外,看看这个-> ProximityHash

给定中心坐标和半径,ProximityHash会生成一组覆盖圆形区域的地质哈希。它还有一个使用GeoRaptor的附加选项,它可以从最高级别开始迭代并直到酿造出最佳的混合物,从而在各个级别上创建最佳的地质哈希组合以表示圆。结果精度与起始geohash级别相同,但是数据大小显着减小,从而提高了速度和性能。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.