我试图弄清楚使用哪个GEO实现来找到基于long / lat到某一点的最近点。如果不是数十亿不同的纬度/经度点需要进行比较,我将有数百万。我一直在寻找许多不同的实现来完成我需要完成的工作。我已经研究过Postgis(看起来非常受欢迎并且表现良好),Neo4J(图形数据库对我来说是一个新概念,我不确定它们是如何实现的),AWS dynamodb geohash(Scales非常好,但只有库是用Java,我希望在node.js中编写一个库等,但不能弄清楚哪个会表现最好。我纯粹关注与众多功能相对应的性能。我需要的是将一个点与所有点进行比较并找到最接近的(读取操作),并且还能够快速更改数据库中的一个点(写入操作)。任何人都可以根据这些要求建议一个好的实施
答案 0 :(得分:12)
PostGIS具有多种地理位置功能。如果你的字符串足够长,搜索会变得更快(每个盒子的碰撞次数减少+ 8个邻居),但插入新点时geohash的生成速度会慢一些。
问题是你想要的准确程度。在纬度增加时,纬度/长度距离"因为一定程度的经度从赤道的大约110公里减少到两极的0,所以恶化,而纬度总是大约110公里。在45度的中纬度,经度接近79km,距离误差为2(sqr(110/79))。在纬度/长度对之间给出真正距离的球面距离计算起来非常昂贵(大量的三角函数正在进行),然后你的地理位置不起作用(除非你将所有点转换为平面坐标)。
可能有效的解决方案如下:
CREATE INDEX hash8 ON tablename(substring(hash_column FROM 1 FOR 8))
。这为您提供了两倍于分辨率的框的索引,这有助于查找点并减少搜索相邻哈希框的需要。INSERT
点上,使用PostGIS计算其长度为9(大约10米分辨率)的geohash到hash_column中。您可以在这里使用BEFORE INSERT TRIGGER
。在一个功能中:
无论如何,这不会特别快。如果你确实要达到数十亿的积分,你可能想要考虑具有相当自然的聚类的聚类。 geohashing的解决方案(例如,在substring(hash_column FROM 1 FOR 2)
分解你的表,给你四个象限)。只需确保您考虑跨境搜索。
可以非常快速地进行两次优化:
首先,"规范化"您的球面坐标(意思是:通过增加纬度来补偿经度减小的长度),以便您可以使用"伪笛卡尔"来搜索最近的点。做法。这只有在点靠近时才有效,但由于你使用了很多点,这应该不是问题。更具体地说,这适用于长度为6或更长的geohash框中的所有点。
假设WGS84椭球(用于所有GPS设备),地球的主轴(a)为6,378,137米,椭圆度(e2)为0.00669438。经度的第二个长度为
longSec := Pi * a * cos(lat) / sqrt(1 - e2 * sqr(sin(lat))) / 180 / 3600
或
longSec := 30.92208078 * cos(lat) / sqrt(1 - 0.00669438 * sqr(sin(lat)))
纬度的第二个:
latSec := 30.870265 - 155.506 * cos(2 * lat) + 0.0003264 + cos(4 * lat)
用于制作局部坐标系的校正因子" square"将经度值乘以longSec/latSec
。
其次,因为您正在寻找最近的点,所以不要搜索距离,因为计算成本高的平方根。相反,如果你愿意的话,搜索平方根内的项,平方距离,因为它具有选择最近点的相同属性。
在伪代码中:
CREATE FUNCTION nearest_point(pt geometry, ptHash8 char(8)) RETURNS integer AS $$
DECLARE
corrFactor double precision;
ptLat double precision;
ptLong double precision;
currPt record;
minDist double precision;
diffLat double precision;
diffLong double precision;
minId integer;
BEGIN
minDist := 100000000.; -- a large value, 10km (squared)
ptLat := ST_Y(pt);
ptLong := ST_X(pt);
corrFactor := 30.92208078 * cos(radians(ptLat)) / (sqrt(1 - 0.00669438 * power(sin(radians(ptLat)), 2)) *
(30.870265 - 155.506 * cos(2 * radians(ptLat)) + 0.0003264 + cos(4 * radians(ptLat))));
FOR currPt IN SELECT * FROM all_points WHERE hash8 = ptHash8
LOOP
diffLat := ST_Y(currPt.pt) - ptLat;
diffLong := (ST_X(currPt.pt) - ptLong) * corrFactor; -- "square" things out
IF (diffLat * diffLat) < (minDist * diffLong * diffLong) THEN -- no divisions here to speed thing up a little further
minDist := (diffLat * diffLat) / (diffLong * diffLong); -- this does not happen so often
minId := currPt.id;
END IF;
END LOOP;
IF minDist < 100000000. THEN
RETURN minId;
ELSE
RETURN NULL;
END IF;
END; $$ LANGUAGE PLPGSQL STRICT;
毋庸置疑,这在C语言函数中要快得多。另外,不要忘记进行边界检查以查看是否需要搜索相邻的geohash框。
顺便提一下,&#34;空间纯粹主义者&#34;不会索引8-char geohash并从那里搜索;相反,他们将从9-char哈希开始,然后从那里向外工作。但是,&#34; miss&#34;在您的初始哈希框中(因为没有其他点或者您接近哈希框侧)是昂贵的,因为您必须开始计算到相邻哈希框的距离并提取更多数据。在实践中,你应该使用一个大约是典型最近点大小两倍的哈希框;这个距离取决于你的观点。