【问题标题】:Efficiently computing distance有效计算距离
【发布时间】:2018-04-16 17:36:34
【问题描述】:

我正在尝试查找 30 英里半径范围内每个地点的地点数。例如,对于伊利诺伊州斯普林菲尔德,半径 30 英里范围内有多少个城镇?

对于每个地方,我都有名称、纬度和经度,并且 n = 30k。

如果数据集更小,这个问题会相对简单:

PROC SQL; 
    CREATE TABLE Distance_Table_1 AS 
        SELECT 
             MASTER.PlaceID AS PlaceID 
            ,Master.INTPTLAT AS LAT1
            ,Master.INTPTLONG AS LONG1
            ,Match.INTPTLAT AS LAT2
            ,Match.INTPTLONG AS LONG2
            ,GEODIST(Master.INTPTLAT, Master.INTPTLONG, Match.INTPTLAT,Match.INTPTLONG,'M') AS DISTANCE
        FROM MASTER_TABLE_CLEANED_ MASTER
        CROSS JOIN MASTER_TABLE_CLEANED_ AS MATCH
        ; 
QUIT; 

然后,我将为每个地点创建 30 英里内的所有地点的计数。

问题在于,这会产生一个我的 SAS 无法处理的大得离谱的表(900M 行)。

我怎样才能更有效地处理这个问题?

【问题讨论】:

  • 使用表中坐标与斯普林菲尔德坐标相比的距离公式 - 您可以一次循环遍历每个坐标对并进行计算以确定位置是否在范围内
  • @DanielMarcus 澄清一下,我需要为所有 30k(30k 个不同的 Springfields 相互配对)执行此操作
  • 如果时间不是问题,我会一个一个去
  • 看起来,至少,您应该能够通过避免重复计算将结果集减半。如果您知道从 Town_X 到 Town_Y 的“乌鸦飞翔”距离,那么您不需要存储从 Town_Y 到 Town_X 的距离,因为它们是相同的。
  • 遗憾的是,如果您想找到最小距离,您需要首先计算所有距离或找到一种不同的过滤方式。即,如果它的两个状态超过它可能是无效的。您还有其他可以添加到此练习中的限制吗?

标签: sql sas proc proc-sql


【解决方案1】:

减少对选择空间。

使用 DomPazz PlaceId 限制要评估的对集合,并使用格方法要求每个纬度和经度值接近 30 英里。

成对选择标准是

where
  fromCity.placeId < toCity.placeId
  and abs(fromCity.lat - toCity.lat) < &precomputed_Max30mileLatRange
  and abd(fromCity.lont - toCity.long) < &precomputed_Max30mileLongRange

使用http://longitudestore.com/how-big-is-one-gps-degree.html的信息:

  • 纬度刻度名义上是线性的,纬度为“1 度”。是 ~ 69 英里
  • 纵向尺度变化,需要更多的长度。距离北极或南极越近,距离为 30 英里。在北纬 80 度,1 度经度大约是 12 英里

因此,假设您的地图数据中没有纬度超过 80 度的地方,以下选择标准将大大减少需要计算地理距离的配对。

where
  fromCity.placeId < toCity.placeId
  and abs(fromCity.lat - toCity.lat) < 0.5 /* ~35 miles */
  and abs(fromCity.lont - toCity.long) < 2.5 /* anywhere from ~36 miles (at 80 lat to ~175mi at equator */

这一切都假定了一个球形地球信念。

【讨论】:

    【解决方案2】:

    Gord 在 cmets 中提到了这一点,只需在查询中添加一个过滤器,就可以省去重复计算和对自身距离的计算。

    PROC SQL; 
        CREATE TABLE Distance_Table_1 AS 
            SELECT 
                 MASTER.PlaceID AS PlaceID 
                ,Master.INTPTLAT AS LAT1
                ,Master.INTPTLONG AS LONG1
                ,Match.INTPTLAT AS LAT2
                ,Match.INTPTLONG AS LONG2
                ,GEODIST(Master.INTPTLAT, Master.INTPTLONG, Match.INTPTLAT,Match.INTPTLONG,'M') AS DISTANCE
            FROM MASTER_TABLE_CLEANED_ MASTER
            CROSS JOIN MASTER_TABLE_CLEANED_ AS MATCH
            where match.PlaceID < master.PlaceID
            ; 
    QUIT; 
    

    添加where子句:

    where match.PlaceID < master.PlaceID
    

    这将返回 449,985,000 条记录 ( (n^2-n)/2 )。希望它足够小,可以处理。

    (在我的笔记本电脑上有 30k 条记录的测试表上运行需要 1:05)

    【讨论】:

    • 这可能会有所帮助,但最终我需要双向距离,如果不计算至少以某种方式存储。如果我计算从博伊西到斯普林菲尔德的距离,我还需要存储从斯普林菲尔德到博伊西的信息,因为这会影响博伊西自己的距离。
    • 这没有任何意义。从 A->B 与 B->A 的距离如何变化?
    • @falling_up 不完全,你可以在查找距离时对变量进行排序,即 BA sorted 将找到从 table 到 AB 的距离。
    【解决方案3】:

    当您针对自身加入表时,您希望尝试完成一些有助于提高性能的事情:

    • 使生成的数据集尽可能小
    • 尽可能轻松地比较两个条目

    看到问题了吗?您根本没有减少数据集,而是执行了 30k x 30k 次的复杂距离计算。您不是尽可能快地消除尽可能多的记录,而是继续暴力破解所有内容。

    马上,提高性能的一种简单方法是执行以下操作:

    select *
    from cities c1
    JOIN cities c2
    on c1.ID < c2.ID
    and c2.Lat between c1.Lat - 30 miles and c1.Lat + 30 miles
    and c2.Long between c1.Long - 30 miles and c1.Long + 30 miles
    

    ...这将为您提供一个 更小的可能候选人列表。这不是最终的答案 - 您将拥有位于北部 25 英里和西部 25 英里的城市,总距离超过 30 英里。但是您大大减少了您需要执行的距离检查的总数以及您正在对其进行操作的数据集。

    之后,您应该在表上使用索引。我的猜测是您需要一个同时包含 Lat 和 Long 列的索引,这样您只需要一个索引来执行操作。

    这有望让您到达您需要去的地方 - 我猜这就是您需要的所有优化。但是如果你需要让事情变得更快,你可以细分数据。毕竟,该国西部的任何地方都不会在东部 30 英里范围内。 (不过,您需要考虑处于分界线边缘的城市。)

    【讨论】:

    • 刚刚做了一个快速测试——我能够在 4 秒内让查询在 30k 行的随机样本数据上运行。结果数据集是 388k 行而不是 9 亿行。
    猜你喜欢
    • 2017-11-24
    • 1970-01-01
    • 1970-01-01
    • 2016-01-19
    • 2019-07-23
    • 1970-01-01
    • 2017-08-10
    • 2014-08-21
    • 1970-01-01
    相关资源
    最近更新 更多