我正在使用Laravel 5.5构建一个php web应用程序,我需要显示按用户指定位置的距离排序的地点列表(例如商店)。 这些地方将存储在MySQL数据库中,并应作为Eloquent ORM模型实例进行检索。
做了一些研究我发现了很多关于这个主题的帖子和问题(提出了不同的解决方案),但是,由于对数据库和地理定位/地理空间分析的经验很少,他们大多困惑我,我想知道什么遵循的方法以及在这种情况下的最佳做法。
我读过的大多数答案建议使用SQL查询中的haversine formula或spherical 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_Sphere
和ST_Distance
,它们似乎正是为了这个目的。
是否有任何理由(例如,性能,精度)不来使用这些函数而不是在查询中编写公式? (我不知道哪些算法在内部用于这些功能)
我也不知道应该如何存储每个地方的坐标。
我见过的大多数示例都假设坐标存储在单独的lat
,lon
列中作为双精度或FLOAT(10,6)
(如this example by google中),但MySQL POINT
数据类型似乎也适合存储地理坐标
这两种方法的优点和缺点是什么?
如何使用索引来加速这类查询?例如,我已经阅读了“spatial indexes”,但我认为它们只能用MBRContains()
来限制结果,而不是按距离实际排序结果。
那么,我应该如何存储地点的坐标以及如何查询它们按距离排序?
答案 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_SPHERE
或MBRContains
来获取边界内点或点之间的距离 - 比使用不能使用索引的Haversine公式快得多,并且不是为查询距离而构建的,因为MySql很慢与范围查询。请参阅mysql documentation.
Haversine公式可能适用于小型应用程序,大部分较旧的答案都是指该解决方案,因为旧版本的MySql innodb没有空间索引。
这样做的主要方法如下 - 以下是我在Java中的工作代码 - 希望您可以根据需要为PHP定制
首先将传入数据保存为数据库中的点(请注意,坐标公式使用经度,纬度约定)
GeometryFactory factory = new GeometryFactory();
Point point = factory.createPoint(new Coordinate(officeDto.getLongitude(), officeDto.getLatitude()));//IMP:Longitude,Latitude
officeDb.setLocation(point);
使用mysql中的以下内容创建空间索引
在办公室(位置)创建空间索引位置;
您可能会收到错误“SPATIAL索引的所有部分必须为NOT NULL”。这是因为只有在字段为NOT NULL时才能创建空间索引 - 在这种情况下,将字段转换为非空
最后,从代码中调用自定义函数ST_DISTANCE_SPHERE,如下所示。
SELECT st_distance_sphere( office.getLocation , project.getLocation)
as distance FROM ....
注意:office.getLocation和project.getLocation都返回POINT类型。本机SQL方法如下文档
ST_Distance_Sphere(g1, g2 [, radius])
返回球体上两点和/或多点之间的最小球面距离,以米为单位,如果任何几何参数为NULL或为空,则返回NULL。