【问题标题】:Finding Cities within 'X' Kilometers (or Miles)查找“X”公里(或英里)内的城市
【发布时间】:2009-04-30 20:43:13
【问题描述】:

这可能不清楚,也可能不清楚,如果我不在基地,请给我留言,或者您需要更多信息。也许已经有我想要的 PHP 解决方案了。

我正在寻找一个可以从经度或纬度值中添加或减去距离的函数。

原因:我有一个包含所有纬度和经度的数据库,并希望形成一个查询来提取 X 公里(或英里)内的所有城市。我的查询看起来像这样......

Select * From Cities Where (Longitude > X1 and Longitude < X2) And (Latitude > Y1 and Latitude < Y2)

 Where X1 = Longitude - (distance)
 Where X2 = Longitude + (distance)

 Where Y1 = Latitude - (distance)
 Where Y2 = Latitude + (distance)

我正在使用 PHP,使用 MySql 数据库。

也欢迎任何建议! :)

【问题讨论】:

  • 你总是可以自己推导出函数......这看起来像是高中级别的微积分,或者如果你真的简化它甚至可以触发......
  • 您会这么认为,但地球并不是一个完美的球体,赤道 1 度经度与其他地方 1 度经度之间的差异惊人地大。这绝对不像人们希望的那么简单!
  • (见下面我的回答:))
  • @Mike,你不能只添加一个标量距离来获得一个新的经度和纬度。由于地球的曲率,您还需要指定角度。你说你想找到 X 公里的城市,我的解决方案就是这样做的。

标签: mysql gis coordinates distance coordinate-systems


【解决方案1】:

这是一个 MySQL 查询,可以完全满足您的需求。请记住,这样的事情通常是近似值,因为地球不是完美的球形,也没有考虑到山脉、丘陵、山谷等。我们在AcademicHomes.com 上使用 PHP 和 MySQL 上的这段代码,它返回 $ 内的记录$latitude, $longitude 的半径英里。

$res = mysql_query("SELECT
    * 
FROM
    your_table
WHERE
    (
        (69.1 * (latitude - " . $latitude . ")) * 
        (69.1 * (latitude - " . $latitude . "))
    ) + ( 
        (69.1 * (longitude - " . $longitude . ") * COS(" . $latitude . " / 57.3)) * 
        (69.1 * (longitude - " . $longitude . ") * COS(" . $latitude . " / 57.3))
    ) < " . pow($radius, 2) . " 
ORDER BY 
    (
        (69.1 * (latitude - " . $latitude . ")) * 
        (69.1 * (latitude - " . $latitude . "))
    ) + ( 
        (69.1 * (longitude - " . $longitude . ") * COS(" . $latitude . " / 57.3)) * 
        (69.1 * (longitude - " . $longitude . ") * COS(" . $latitude . " / 57.3))
    ) ASC");

【讨论】:

  • 如果你不介意我问基思,你的索引设置是什么,在这个查询中?这很好用,但是它使用 Lat/Long 上的键扫描数据库中的每一行
  • 嘿,迈克,如果性能很重要,我在“纬度”上设置了一个索引,在“经度”上设置了一个索引,并通过使用 BETWEEN 子句将查询限制为较小的子集来调整上述查询的记录。添加类似: WHERE latitude BETWEEN $latitude - ($radius / 70) AND $latitude + ($radius / 70) AND longitude BETWEEN $longitude - ($radius / 70) AND $longitude + ($radius / 70) 。 .. 这允许数据库使用纬度和经度的索引。常数 70 是因为 1 度纬度或经度所跨越的最大距离约为 70 英里。
  • P.S.在我们老化的开发服务器上,其中包含一个包含全球 290 万个城市/城镇的表,查询时间从以下变化: 没有纬度/经度索引:10.9 秒 纬度/经度索引:0.52 秒
  • 使用空间索引快速定位附近城市。这种工作比普通索引快得多。
  • 嘿基思,我错过了这篇文章(因为看到我使用的原始答案 - 效果很好)。我们的数据库现在有大约 2.8~ 个 Mill 记录,然后回来添加: $longitude_rectangle1 = $longitude - $distance / abs(cos(deg2rad($latitude))*69); $longitude_rectangle2 = $longitude + $distance / abs(cos(deg2rad($latitude))*69); $latitude_rectangle1 = $latitude - ($distance/69); $latitude_rectangle2 = $latitude + ($distance/69);我将它们用于中间 - 看起来对吗?
【解决方案2】:

编辑:如果您在某个地方拥有世界上所有城市及其纬度的列表。并且很长。值,您可以进行查找。在这种情况下,请参阅下面我的第一个链接,以获取在纬度 处计算一个经度的宽度的公式:

老实说,这个问题背后的复杂性在于,您最好使用 Google 地图等服务来获取数据。具体来说,地球不是一个完美的球体,两度之间的距离会随着您离赤道的近/远而变化。

请参阅http://en.wikipedia.org/wiki/Geographic_coordinate_system 了解我的意思的示例,并查看the Google Maps API

【讨论】:

  • 这基本上就是我建议他派生的功能。我不认为地球的轻微非球面性在任何合理的距离上都无关紧要...... IIRC,两极之间的距离和穿过赤道的距离相差 50 英里,这在地球大小方面算不了什么。
  • 但是在计算表面上的位置时会有很大的不同。
【解决方案3】:

根据您包含的城市数量,您可以预先计算列表。我们在这里为一个内部应用程序执行此操作,其中 +100m 的不准确度对于我们的设置来说太大了。它的工作原理是有一个 location1、location2、distance 的两个键表。然后,我们可以非常快速地从 location1 拉回位置 x 距离。

此外,由于计算可以离线完成,它不会影响系统的运行。用户还可以获得更快的结果。

【讨论】:

    【解决方案4】:

    我尝试使用上面的代码,但当点之间的距离在 20-30 英里范围内时,答案偏离了太多,我可以接受几英里的错误。与我的一位制图伙伴交谈,我们想出了这个。代码是 python,但你可以很容易地翻译它。为了避免不断地转换为弧度,我重新编写了我的数据库,将纬度/经度点从度数转换为弧度。这样做的好处是大部分数学运算都只完成一次。

    ra = 3963.1906 # radius @ equator in miles, change to km  if you want distance in km
    rb = 3949.90275  # radius @ poles in miles, change to km  if you want distance in km
    ra2 = ra * ra
    rb2 = rb * rb
    
    phi = self.lat
    
    big_ol_constant = (math.pow(ra2*math.cos(phi), 2) + pow(rb2*math.sin(phi), 2))/ (pow(ra*math.cos(phi), 2) + pow(rb*math.sin(phi), 2))
    
    sqlWhere = "%(distance)g > sqrt((power(lat - %(lat)g,2) + power(lng-%(lng)g,2)) * %(big_ol_constant)g)" % {
        'big_ol_constant': big_ol_constant, 'lat': self.lat, 'lng': self.lng, 'distance': distance}
    
    # This is the Django portion of it, where the ORM kicks in.  sqlWhere is what you would put after the WHERE part of your SQL Query.
    qs = ZipData.objects.extra(where=[sqlWhere]);
    

    当距离很小时似乎非常准确,并且在距离增加到 200 英里时在 10 英里左右,(当然,到那时,你会遇到“乌鸦飞翔”与“铺好的道路”的问题) .

    这是我上面提到的模型 ZipData。

    class ZipData(models.Model):
        zipcode = ZipCodeField(null=False, blank=False, verbose_name="ZipCode", primary_key=True)
        city = models.CharField(max_length=32, null=False, blank=False)
        state = models.CharField(max_length=2)
        lat = models.FloatField(null=False, blank=False)
        lng = models.FloatField(null=False, blank=False)
    

    另外需要注意的是,您可以在GeoNames.org 获得大量与邮政编码相关的地理数据,他们甚至还有一些您可以使用的 Web 服务 API。

    【讨论】:

    • 这是很棒的 Mark0978。你有完整的代码或 django-app 可以作为即插即用模块共享吗?
    • 我现在已经包含了模型对象。您可以通过电子邮件将我的用户名@gmail.com 发送给我,我会将构成该应用程序的文件发送给您,但我不认为它们是可重复使用的应用程序。
    【解决方案5】:

    有很多(不好的选择)

    • 使用数学公式(将 X1-X2 和 Y1-Y2 视为向量)计算距离。

    • 提前创建一个包含所有组合的查找表并保持距离。

    • 考虑使用 MySQL 的 GIS 特定扩展。这是one article我发现的。

    【讨论】:

      【解决方案6】:

      lessthandot.com 实际上有 3 种不同的方法来做到这一点。您必须稍微滚动浏览博客,但它们就在那里。 http://blogs.lessthandot.com/

      【讨论】:

        【解决方案7】:

        下面的函数来自nerddinner的(ASP.NET MVC示例应用程序available on codeplex)数据库(MSSQL)。

        ALTER FUNCTION [dbo].[DistanceBetween] (@Lat1 as real,
                        @Long1 as real, @Lat2 as real, @Long2 as real)
        RETURNS real
        AS
        BEGIN
        
        DECLARE @dLat1InRad as float(53);
        SET @dLat1InRad = @Lat1 * (PI()/180.0);
        DECLARE @dLong1InRad as float(53);
        SET @dLong1InRad = @Long1 * (PI()/180.0);
        DECLARE @dLat2InRad as float(53);
        SET @dLat2InRad = @Lat2 * (PI()/180.0);
        DECLARE @dLong2InRad as float(53);
        SET @dLong2InRad = @Long2 * (PI()/180.0);
        
        DECLARE @dLongitude as float(53);
        SET @dLongitude = @dLong2InRad - @dLong1InRad;
        DECLARE @dLatitude as float(53);
        SET @dLatitude = @dLat2InRad - @dLat1InRad;
        /* Intermediate result a. */
        DECLARE @a as float(53);
        SET @a = SQUARE (SIN (@dLatitude / 2.0)) + COS (@dLat1InRad)
                         * COS (@dLat2InRad)
                         * SQUARE(SIN (@dLongitude / 2.0));
        /* Intermediate result c (great circle distance in Radians). */
        DECLARE @c as real;
        SET @c = 2.0 * ATN2 (SQRT (@a), SQRT (1.0 - @a));
        DECLARE @kEarthRadius as real;
        /* SET kEarthRadius = 3956.0 miles */
        SET @kEarthRadius = 6376.5;        /* kms */
        
        DECLARE @dDistance as real;
        SET @dDistance = @kEarthRadius * @c;
        return (@dDistance);
        END

        我猜这可能会有所帮助。

        【讨论】:

        • 地球没有一个恒定的半径——这会让你靠近,但(至少对于我之前的应用)还不够近。如果您不担心几(最多几百)英里的差异,这将是一个好方法:)
        【解决方案8】:

        您可以使用毕达哥拉斯定理来计算两对纬度/经度点的接近度。

        如果您有两个位置(Alpha 和 Beta),您可以计算它们之间的距离:

        SQRT( POW(Alpha_lat - Beta_lat,2) + POW(Alpha_lon - Beta_lon,2) )
        

        【讨论】:

        • 对于短距离来说这很好,但我们的星球并不平坦
        【解决方案9】:

        使用来自以下 URL 的设置,我构建了下面的查询。 (请注意我使用codeIgnitor查询数据库)

        http://howto-use-mysql-spatial-ext.blogspot.com/2007/11/using-circular-area-selection.html

        function getRadius($point="POINT(-29.8368 30.9096)", $radius=2)
        {
            $km = 0.009;
            $center = "GeomFromText('$point')";
            $radius = $radius*$km;
            $bbox = "CONCAT('POLYGON((',
                X($center) - $radius, ' ', Y($center) - $radius, ',',
                X($center) + $radius, ' ', Y($center) - $radius, ',',
                X($center) + $radius, ' ', Y($center) + $radius, ',',
                X($center) - $radius, ' ', Y($center) + $radius, ',',
                X($center) - $radius, ' ', Y($center) - $radius, '
            ))')";
        
            $query = $this->db->query("
            SELECT id, AsText(latLng) AS latLng, (SQRT(POW( ABS( X(latLng) - X({$center})), 2) + POW( ABS(Y(latLng) - Y({$center})), 2 )))/0.009 AS distance
            FROM crime_listing
            WHERE Intersects( latLng, GeomFromText($bbox) )
            AND SQRT(POW( ABS( X(latLng) - X({$center})), 2) + POW( ABS(Y(latLng) - Y({$center})), 2 )) < $radius
            ORDER BY distance
                ");
        
            if($query->num_rows()>0){
                return($query->result());
            }else{
                return false;
            }
        }
        

        【讨论】:

          【解决方案10】:

          不要重新发明轮子。这是一个空间查询。使用MySQL's built-in spatial extensions将经纬度坐标数据存储在native MySQL geometry column type中。然后使用Distance 函数查询彼此之间指定距离内的点。

          免责声明:这是基于阅读文档,我自己没有尝试过。

          【讨论】:

          • 从您链接的页面顶部: 目前,MySQL 没有按照规范实现这些功能。那些实现的返回与相应的基于 MBR 的函数相同的结果。这包括以下列表中除 Distance() 和 Related() 之外的函数。
          • @Mark0978 该页面确实包含该免责声明,但在我的回答中,我推荐Distance 函数。您的评论中引用的免责声明不适用于 Distance 函数。因此,免责声明与我的回答无关。
          猜你喜欢
          • 2011-07-29
          • 2011-12-11
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2021-12-17
          • 2017-06-12
          • 2013-08-21
          相关资源
          最近更新 更多