我有一张带有地理定位点的长桌:
id lat lon
-----------------------
1 39.4600 110.3523410
2 39.4601 110.3523410
3 39.4605 110.3523410
4 39.4609 110.3523410
这些点中的许多点在地图上显示时会重叠,因为它们非常接近。怎样才能得到均匀分布点?也就是说,它们之间的距离大于给定点的一组点。
例如,点1和点2之间的距离(纬度)是0.0001。我可以得到一个只包含超过0.0003(或任何其他数量)的点的表结果吗?
使用地理空间数据库可能很容易,但使用普通SQL似乎不是一项明显的任务(至少对我而言)。
谢谢, 哈维尔
答案 0 :(得分:3)
最快的方法(粗略地)是将每个点分配到网格方格,然后每个方格只保留一个点。这比列出的其他技术更有效:
SELECT DISTINCT ROUND(lat*250, 0), ROUND(long*250, 0) FROM sometable;
您可能希望平均每个网格方块中的位置:
SELECT AVERAGE(lat), AVERAGE(long)
FROM sometable
GROUP BY ROUND(lat*250, 0), ROUND(long*250.0, 0);
要控制分组的粒度,只需从 250 向上或向下更改缩放系数。
另一种(和更慢的)方法是执行CROSS JOIN,以便每个点与其他每个点配对,然后使用distance formula标记低于最小阈值的对。如果距离公式感觉过于复杂,则更简单的方法是将连接限制在ABS(a.long - b.long) < 0.1 AND ABS(a.lat - b.lat) < 0.1
的位置。这将确定紧密相连的点。
请注意,交叉连接是O(n ** 2)操作,因此如果您尝试将此方法扩展到多个点,则可能会出现问题。解决方案是将点预先分组为更小的区域,并在区域中的点上运行交叉连接。
如果您可以在SQL之外做任何工作,那么使用clustering algorithm可能更合适。
答案 1 :(得分:2)
如果要抑制伪重复项,则需要以下内容:
SELECT * FROM mytable a
WHERE NOT EXISTS ( SELECT *
FROM mytable b
WHERE ABS (a.long - b.long) < 0.01
AND ABS (a.lat - b.lat) < 0.02
AND b.id < a.id
);
更新:(添加数据,索引,没有ABS()的查询)
DROP TABLE tmp.mytable;
CREATE TABLE tmp.mytable
( id INTEGER NOT NULL PRIMARY KEY
, zlat REAL NOT NULL
, zlong REAL NOT NULL
);
INSERT INTO tmp.mytable (id, zlat, zlong)
SELECT generate_series(1,10000), 0.0, 0.0
;
SET search_path=tmp;
UPDATE tmp.mytable SET zlat = 39.0 + random() ;
UPDATE tmp.mytable SET zlong = 110.0 + random() ;
CREATE INDEX latlong ON tmp.mytable (zlat, zlong);
VACUUM ANALYZE tmp.mytable;
/***/
SET search_path=tmp;
EXPLAIN ANALYZE
SELECT * FROM mytable a
WHERE NOT EXISTS ( SELECT *
FROM mytable b
WHERE 1=1
AND ABS (a.zlong - b.zlong) < 0.01
AND ABS (a.zlat - b.zlat) < 0.02
AND b.id < a.id
);
EXPLAIN ANALYZE
SELECT * FROM mytable a
WHERE NOT EXISTS ( SELECT *
FROM mytable b
WHERE 1=1
AND a.zlong - b.zlong < 0.01 AND b.zlong - a.zlong < 0.01
AND a.zlat - b.zlat < 0.02 AND b.zlat - a.zlat < 0.02
AND b.id < a.id
);
查询计划显示实际使用的唯一主索引。基本上,当abs()被“(a-b)&lt; 0.0x AND(b-a)&lt; 0.0x”代替时,产生相同的计划。但ABS()实际上更快。
---------------------------------------------
Nested Loop Anti Join (cost=0.00..1448079.64 rows=9630 width=12) (actual time=0.151..3966.487 rows=1288 loops=1)
Join Filter: ((abs((a.zlong - b.zlong)) < 0.01::double precision) AND (abs((a.zlat - b.zlat)) < 0.02::double precision))
-> Seq Scan on mytable a (cost=0.00..263.00 rows=10000 width=12) (actual time=0.139..3.463 rows=10000 loops=1)
-> Index Scan using mytable_pkey on mytable b (cost=0.00..58.68 rows=3333 width=12) (actual time=0.005..0.173 rows=1084 loops=10000)
Index Cond: (b.id < a.id)
Total runtime: 3966.853 ms
(6 rows)
---------------------------------------------
Nested Loop Anti Join (cost=0.00..1663497.55 rows=9959 width=12) (actual time=0.065..4210.616 rows=1288 loops=1)
Join Filter: (((a.zlong - b.zlong) < 0.01::double precision) AND ((b.zlong - a.zlong) < 0.01::double precision) AND ((a.zlat - b.zlat) < 0.02::double precision) AND ((b.zlat - a.zlat) < 0.02::double precision))
-> Seq Scan on mytable a (cost=0.00..263.00 rows=10000 width=12) (actual time=0.060..2.840 rows=10000 loops=1)
-> Index Scan using mytable_pkey on mytable b (cost=0.00..58.68 rows=3333 width=12) (actual time=0.005..0.173 rows=1084 loops=10000)
Index Cond: (b.id < a.id)
Total runtime: 4210.904 ms
(6 rows)
答案 2 :(得分:0)
我建议你在软件中进行过滤。我不认为您可以在SQL中表达该查询,因为结果不确定(您可以选择39.4600或39.4601)。在软件中,您可以使用kd-tree来加速计算,因为天真的方法:
foreach point1 in points
foreach point2 in points
dist = (point1 - point2).length()
if dist < epsilon: remove point2 from list
具有复杂性:O(n ^ 2)
答案 3 :(得分:0)
SELECT a.id, a.lon, a.lat
FROM points a
WHERE
NOT EXISTS(SELECT *
FROM points b
WHERE b.id < a.id
AND (a.lon - b.lon)**2 + (a.lat - b.lat)**2 < 0.00009)
如果没有**,可能会恢复为POW(......,2)或POWER(......,2)。
请注意,距离是平方根,因此您必须将0.003平方。