SQL统一分配点

时间:2011-11-09 12:59:15

标签: sql database

我有一张带有地理定位点的长桌:

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似乎不是一项明显的任务(至少对我而言)。

谢谢, 哈维尔

4 个答案:

答案 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平方。