mysql - 地理距离 MySQL

标签 mysql geo

要搜索距给定位置最近的位置,请按距离排序

  1. 我应该使用 float 还是点数?
  2. 我应该预先计算 cos/sin/sqrt 的值吗 http://www.movable-type.co.uk/scripts/latlong-db.html
  3. 我的搜索是一个城市内的多个地点。
  4. 许多旧帖子告诉 mysql 没有适当的地理支持,最新的 MySQL 版本也是如此吗?

最佳答案

我们使用double 来存储latitudelongitude。此外,我们预先计算(通过触发器)所有仅在查看一个点时可预先计算的值。我目前无法访问我们正在使用的公式,稍后会添加。这针对最佳速度/精度平衡进行了优化。

对于定义的区域搜索(给我 x 公里内的所有点),我们还存储了 lat/lng 值乘以 1e6 (1,000,000),因此我们可以通过比较整数范围将其限制为正方形快如闪电

lat BETWEEN 1290000 AND 2344000
AND
lng BETWEEN 4900000 AND 4910000
AND
distformularesult < 20

编辑:

这是 PHP 中当前位置值的公式和预计算。

WindowSize 是一个您必须考虑的值,它是度数因子 1e6,用于缩小围绕中心的正方形中的可能结果,加快结果查找速度 - 不要忘记这应该是至少 您的搜索半径大小。

$paramGeoLon = 35.0000 //my center longitude
$paramGeoLat = 12.0000 //my center latitude

$windowSize = 35000;   

$geoLatSinRad = sin( deg2rad( $paramGeoLat ) );
$geoLatCosRad = cos( deg2rad( $paramGeoLat ) );
$geoLonRad    = deg2rad( $paramGeoLon );

$minGeoLatInt = intval( round( ( $paramGeoLat * 1e6 ), 0 ) ) - $windowSize;
$maxGeoLatInt = intval( round( ( $paramGeoLat * 1e6 ), 0 ) ) + $windowSize;
$minGeoLonInt = intval( round( ( $paramGeoLon * 1e6 ), 0 ) ) - $windowSize;
$maxGeoLonInt = intval( round( ( $paramGeoLon * 1e6 ), 0 ) ) + $windowSize;

搜索中心特定范围内的所有行

SELECT
          `e`.`id`
        , :earthRadius * ACOS ( :paramGeoLatSinRad * `e`.`geoLatSinRad` + :paramGeoLatCosRad * `m`.`geoLatCosRad` * COS( `e`.`geoLonRad` - :paramGeoLonRad ) ) AS `geoDist`

FROM
          `example` `e`
WHERE
        `e`.`geoLatInt` BETWEEN :paramMinGeoLatInt AND :paramMaxGeoLatInt
        AND
        `e`.`geoLonInt` BETWEEN :paramMinGeoLonInt AND :paramMaxGeoLonInt
HAVING `geoDist` < 20
ORDER BY 
        `geoDist`

该公式具有相当好的精度(低于一米,具体取决于您所在的位置以及点之间的距离)

我在我的数据库表 example

中预先计算了以下值
CREATE TABLE `example` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `geoLat` double NOT NULL DEFAULT '0',
  `geoLon` double NOT NULL DEFAULT '0',

  # below is precalculated with a trigger
  `geoLatInt` int(11) NOT NULL DEFAULT '0',
  `geoLonInt` int(11) NOT NULL DEFAULT '0',
  `geoLatSinRad` double NOT NULL DEFAULT '0',
  `geoLatCosRad` double NOT NULL DEFAULT '0',
  `geoLonRad` double NOT NULL DEFAULT '0',
  PRIMARY KEY (`id`),
  KEY `example_cIdx_geo` (`geoLatInt`,`geoLonInt`,`geoLatSinRad`,`geoLatCosRad`,`geoLonRad`)  
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci ROW_FORMAT=DYNAMIC

示例触发器

DELIMITER $
CREATE TRIGGER 'example_before_insert' BEFORE INSERT ON `example` FOR EACH ROW
BEGIN
    SET NEW.`geoLatInt` := CAST( ROUND( NEW.`geoLat` * 1e6, 0 ) AS SIGNED INTEGER );
    SET NEW.`geoLonInt` := CAST( ROUND( NEW.`geoLon` * 1e6, 0 ) AS SIGNED INTEGER );
    SET NEW.`geoLatSinRad` := SIN( RADIANS( NEW.`geoLat` ) );
    SET NEW.`geoLatCosRad` := COS( RADIANS( NEW.`geoLat` ) );
    SET NEW.`geoLonRad` := RADIANS( NEW.`geoLon` );
END$

CREATE TRIGGER 'example_before_update' BEFORE UPDATE ON `example` FOR EACH ROW
BEGIN
    IF NEW.geoLat <> OLD.geoLat OR NEW.geoLon <> OLD.geoLon
    THEN
        SET NEW.`geoLatInt` := CAST( ROUND( NEW.`geoLat` * 1e6, 0 ) AS SIGNED INTEGER );
        SET NEW.`geoLonInt` := CAST( ROUND( NEW.`geoLon` * 1e6, 0 ) AS SIGNED INTEGER );
        SET NEW.`geoLatSinRad` := SIN( RADIANS( NEW.`geoLat` ) );
        SET NEW.`geoLatCosRad` := COS( RADIANS( NEW.`geoLat` ) );
        SET NEW.`geoLonRad` := RADIANS( NEW.`geoLon` );
    END IF;
END$
DELIMITER ;

有问题吗?否则玩得开心:)

关于mysql - 地理距离 MySQL,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/14041104/

相关文章:

javascript - 如何在 map 上显示/隐藏?

mysql - 为什么 GPS 报告疯狂的经度数字?

forms - 如何在 Phoenix 中生成 Geo 自定义类型的表单

android - 将 ng-href 与 geo : Uri in Cordova does not work 一起使用

mysql - 在 mysql 服务器上触发两个查询会出现错误

mysql - 使用 SQL 子查询或 JOIN 子句进行选择

java - 使用 Hibernate @ManyToOne 关系将数据保存到 MySQL DB

javascript - Web 应用程序中的地理位置欺骗保护

php - 如何根据 MySQL 和 PHP 中显示的行更新值

php - Yii2:是否可以使用 JOIN 同时查询 2 个数据库?