【问题标题】:How can I do efficient range searching + counting with latitude/longitude data?如何使用纬度/经度数据进行有效的范围搜索+计数?
【发布时间】:2010-10-05 17:15:35
【问题描述】:

我正在处理由纬度/经度对表示的大量点(这些点不一定是唯一的,集合中可能有多个点位于同一位置)。这些点存储在数据库中。

我需要做的是找出一种有效执行搜索的方法,以获取位于任意点的给定半径(例如 25 英里)内的点数。 计数不需要 100% 准确 - 更重要的是,它只需要快速,并且合理地接近正确计数。使用 SQL 执行此操作是可能的,方法是在 WHERE 子句中使用带有一些三角函数的查询来过滤点到参考点的距离。不幸的是,这个查询非常非常昂贵,而且缓存不太可能提供太多帮助,因为位置会非常分散。

我最终希望构建某种能够有效处理此类操作的内存结构 - 权衡数据的一些准确性和活跃性(也许每天只重建一次)返回速度。我一直在对 kd-trees 进行一些研究,但我还不清楚这可以如何应用于纬度/经度数据(与二维平面中的 x,y 数据相反)。

如果有人有我应该研究的任何想法或解决方案,我将不胜感激 - 提前致谢。

【问题讨论】:

  • 如果您在平台上提供更多信息会有所帮助,您将在...
  • [这是一个真正的答案] 如果您想使用 kd 树,您需要将笛卡尔距离查询转换为纬度和经度范围(或者,只需执行数学来查看分裂的纬度/经度平面是否与您的查询相交)。

标签: algorithm data-structures geospatial latitude-longitude geography


【解决方案1】:

我认为你不应该使用这个解决方案。几天前随机想到它,我认为测量与特定点的距离,网格方块的位置将基于圆形而不是统一的网格。离 0,0 越远,这就越不准确!

我所做的是在我的 PostalCode 类上添加 2 个附加值。每当我更新 PostalCode 上的 Long/Lat 时,我都会计算出 Long 0, Lat 0 的 X、Y 距离。

public static class MathExtender
{
    public static double GetDistanceBetweenPoints(double sourceLatitude, double sourceLongitude, double destLatitude, double destLongitude)
    {
        double theta = sourceLongitude - destLongitude;
        double distance =
            Math.Sin(DegToRad(sourceLatitude))
            * Math.Sin(DegToRad(destLatitude))
            + Math.Cos(DegToRad(sourceLatitude))
            * Math.Cos(DegToRad(destLatitude))
            * Math.Cos(DegToRad(theta));
        distance = Math.Acos(distance);
        distance = RadToDeg(distance);
        distance = distance * 60 * 1.1515;
        return (distance);
    }


    public static double DegToRad(double degrees)
    {
        return (degrees * Math.PI / 180.0);
    }

    public static double RadToDeg(double radians)
    {
        return (radians / Math.PI * 180.0);
    }
}

然后我像这样更新我的课程:

private void CalculateGridReference()
{
    GridReferenceX = MathExtender.GetDistanceBetweenPoints(0, 0, 0, Longitude);
    GridReferenceY = MathExtender.GetDistanceBetweenPoints(0, 0, Latitude, 0);
}

所以现在对于我的数据库中的每一行,我有一个距网格参考 0,0 的 x,y 网格距离(以英里为单位)。如果我想找到长/纬度为 5 英里的所有地方,我首先会获得 X、Y 网格参考(比如 25,75),然后我会在数据库中搜索 20..30、70..80,然后再进一步使用

过滤内存中的结果
MathExtensder.GetDistanceBetweenPoints(candidate.Lat, candidate.Long, search.Lat, search.Long) < TheRadiusOfInterest

数据库中的部分非常快,而内存中的部分在较小的集合上工作以使其非常准确。

【讨论】:

  • 谢谢,这是我在这类问题上看到的最清晰的答案,大多数人基本上建议你获得 Oracle、MS SQL 或深入研究专业数据结构,而出于很多目的,这很快(比我尝试过的大多数解决方案更快,无论是商业的还是免费的),易于实施且运行良好。它可以很容易地进行微调,以适应你所能投入的一切。
  • 您不应使用此解决方案。它将测量到特定点的距离,这意味着网格正方形将基于圆形而不是统一的网格。离 0,0 越远,这就越不准确!
【解决方案2】:

使用R-Trees

在 Oracle 中,使用 Oracle Spatial,您可以创建索引:

CREATE INDEX ix_spatial ON spatial_table (locations) INDEXTYPE IS MDSYS.SPATIAL_INDEX;

这将为您创建一个R-Tree 并对其进行搜索。

您可以使用任何您喜欢的Earth ModelWGS84PZ-90 等。

【讨论】:

    【解决方案3】:

    对空间数据使用某种搜索树,例如quad tree。更多此类数据结构在“另请参阅”下引用。

    【讨论】:

      【解决方案4】:

      您可以在 Jan Philip Matuschek 的文章“Finding Points Within a Distance of a Latitude/Longitude Using Bounding Coordinates”中找到对 Bombe 建议的出色解释。

      【讨论】:

        【解决方案5】:

        您能否提供一个现有昂贵查询的样本?

        如果您根据参考点和其他数据点的 sine() 和 cosine() 进行适当的大圆计算,则可以通过实际存储这些 sin/cos 值来进行非常重要的优化除了 lat/long 值之外,在数据库中。

        或者,只需使用您的数据库提取匹配的经度/经度范围的矩形,然后过滤掉真正圆形半径之外的矩形。

        但请记住,高纬度地区的经度比赤道地区的距离要短一些。不过,应该很容易找出该矩形的正确纵横比。如果您需要考虑非常靠近极点的区域,您也会遇到错误,因为矩形选择无法处理与极点重叠的圆。

        【讨论】:

          【解决方案6】:

          此 UDF (SQL Server) 将为您提供两个纬度/经度点之间的距离:

          CREATE FUNCTION [dbo].[zipDistance] (
              @Lat1 decimal(11, 6),
              @Lon1 decimal(11, 6),
              @Lat2 decimal(11, 6),
              @Lon2 decimal(11, 6)
          )
          RETURNS
              decimal(11, 6) AS
          BEGIN
          
              IF @Lat1 = @Lat2 AND @Lon1 = @Lon2
                  RETURN 0 /* same lat/long points, 0 distance = */
          
              DECLARE @x decimal(18,13)
              SET @x = 0.0
          
              /* degrees -> radians */
              SET @Lat1 = @Lat1 * PI() / 180
              SET @Lon1 = @Lon1 * PI() / 180
              SET @Lat2 = @Lat2 * PI() / 180
              SET @Lon2 = @Lon2 * PI() / 180
          
              /* accurate to +/- 30 feet */
              SET @x = Sin(@Lat1) * Sin(@Lat2) + Cos(@Lat1) * Cos(@Lat2) * Cos(@Lon2 - @Lon1)
              IF 1 = @x
                  RETURN 0
          
              DECLARE @EarthRad decimal(5,1)
              SET @EarthRad = 3963.1
          
              RETURN @EarthRadius * (-1 * ATAN(@x / SQRT(1 - @x * @x)) + PI() / 2)
          
          END
          

          而且,显然,您可以在单独的查询中使用它,例如:

          SELECT * FROM table WHERE [dbo].[zipDistance] < 25.0
          

          【讨论】:

          • 刚刚意识到您不需要 SQL?将其转换为其他语法应该很容易。对应的是,根据使用情况,这在我的应用程序中对我来说很合适。
          • 但仍然是我建议存储正弦和余弦而不是纬度/经度的一个很好的例子。这样做,您可以将此函数减少到每行只有一个三角函数而不是五个 - 该函数是依赖于 point1 point2的最后一个余弦项。
          • 有趣。不知何故,我必须调查一下。它可能会对我拥有的这个庞大的数据库有所帮助,该数据库可以整理 zip 区域中的数据...
          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2013-03-15
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2013-11-21
          • 2015-02-11
          相关资源
          最近更新 更多