使用空间分析函数和数据类型在MySQL中按距离排序

时间:2018-01-28 19:12:21

标签: php mysql geospatial laravel-eloquent geo

我正在使用Laravel 5.5构建一个php web应用程序,我需要显示按用户指定位置的距离排序的地点列表(例如商店)。 这些地方将存储在MySQL数据库中,并应作为Eloquent ORM模型实例进行检索。

做了一些研究我发现了很多关于这个主题的帖子和问题(提出了不同的解决方案),但是,由于对数据库和地理定位/地理空间分析的经验很少,他们大多困惑我,我想知道什么遵循的方法以及在这种情况下的最佳做法。

我读过的大多数答案建议使用SQL查询中的haversine formulaspherical law of cosines,这看起来像是(this answer中的示例):

$sf = 3.14159 / 180; // scaling factor
$sql = "SELECT * FROM table 
    WHERE lon BETWEEN '$minLon' AND '$maxLon' 
      AND lat BETWEEN '$minLat' AND '$maxLat'
    ORDER BY ACOS(SIN(lat*$sf)*SIN($lat*$sf) + COS(lat*$sf)*COS($lat*$sf)*COS((lon-$lon)*$sf))";

This post指出这样一个事实,即在短距离内,假设地球平坦并计算一个简单的欧氏距离是一个很好的近似值,并且比使用半正弦公式更快。<​​br/> 由于我一次只需要在一个城市中对进行排序,这似乎是一个很好的解决方案。

然而,大多数这些帖子和SO答案都已有几年了,我想知道现在(MySQL 5.7)是否有更好的解决方案。

例如,这些帖子中没有一个使用任何MySQL“空间分析函数”,例如ST_Distance_SphereST_Distance,它们似乎正是为了这个目的。
是否有任何理由(例如,性能,精度)来使用这些函数而不是在查询中编写公式? (我不知道哪些算法在内部用于这些功能)

我也不知道应该如何存储每个地方的坐标。 我见过的大多数示例都假设坐标存储在单独的latlon列中作为双精度或FLOAT(10,6)(如this example by google中),但MySQL POINT数据类型似乎也适合存储地理坐标 这两种方法的优点和缺点是什么?

如何使用索引来加速这类查询?例如,我已经阅读了“spatial indexes”,但我认为它们只能用MBRContains()来限制结果,而不是按距离实际排序结果。

那么,我应该如何存储地点的坐标以及如何查询它们按距离排序?

3 个答案:

答案 0 :(得分:5)

除了ST_Distance_Sphere之外,5.7不会为表带来任何额外的内容。 (SPATIAL已经实施。)

千万&#39;点数,你拥有的代码可能是最好的。包括

INDEX(lat, lng),
INDEX(lng, lat)

除非你伸展数千英里(公里),否则我不会担心地球的曲率。即使这样,代码和该功能也应该足够好。

请勿使用FLOAT(m,n),仅使用FLOAT。下面的链接提供了FLOAT和其他表示的可用精度。

如果你有这么多积分,你不能完全缓存表及其索引(数百万点),你可以使用this,它使用一些技巧来避免冗长的扫描,如以上解决方案。由于PARTITION限制,lat / lng表示为缩放整数。 (但这很容易在输入/输出中进行转换。)地球的曲率,极点和日期线都被处理。

答案 1 :(得分:1)

我使用的是一张有lat&amp; amp;与我发现的邮政编码长期关联。我使用hasrsine公式来查找特定范围内的所有zipcodes。然后,我使用从该查询返回的邮政编码列表,并查找具有这些邮政编码的所有业务。也许这个解决方案对你有用。这很容易实现。只要您知道邮政编码,这也消除了您必须知道每个企业的纬度和长度。

答案 2 :(得分:1)

使用ST_DISTANCE_SPHEREMBRContains来获取边界内点或点之间的距离 - 比使用不能使用索引的Haversine公式快得多,并且不是为查询距离而构建的,因为MySql很慢与范围查询。请参阅mysql documentation.

Haversine公式可能适用于小型应用程序,大部分较旧的答案都是指该解决方案,因为旧版本的MySql innodb没有空间索引。

这样做的主要方法如下 - 以下是我在Java中的工作代码 - 希望您可以根据需要为PHP定制

  1. 首先将传入数据保存为数据库中的点(请注意,坐标公式使用经度,纬度约定)

        GeometryFactory factory = new GeometryFactory();
        Point point = factory.createPoint(new Coordinate(officeDto.getLongitude(), officeDto.getLatitude()));//IMP:Longitude,Latitude
        officeDb.setLocation(point);
    
  2. 使用mysql中的以下内容创建空间索引

      

    在办公室(位置)创建空间索引位置;

  3. 您可能会收到错误“SPATIAL索引的所有部分必须为NOT NULL”。这是因为只有在字段为NOT NULL时才能创建空间索引 - 在这种情况下,将字段转换为非空

    1. 最后,从代码中调用自定义函数ST_DISTANCE_SPHERE,如下所示。

      SELECT st_distance_sphere( office.getLocation ,  project.getLocation) 
       as distance FROM ....
      
    2. 注意:office.getLocation和project.getLocation都返回POINT类型。本机SQL方法如下文档

      ST_Distance_Sphere(g1, g2 [, radius]) 
      

      返回球体上两点和/或多点之间的最小球面距离,以米为单位,如果任何几何参数为NULL或为空,则返回NULL。