地理距离的MySQL(Geo distance MySQL)

2019-08-04 17:32发布

要搜索最近的位置给定的位置,通过远程命令

  1. 我应该使用float或点?
  2. COS的我应该预先计算值/罪/开方http://www.movable-type.co.uk/scripts/latlong-db.html
  3. 我的搜索是一个城市内的不同位置。
  4. 许多老的帖子告诉MySQL是没有正确地缘支持,它是用最新的MySQL版本也是如此?

Answer 1:

我们使用double存储latitudelongitude 。 此外,我们precomute(由触发器)看只有一个点时是precomputable所有值。 我目前还没有获得我们所使用的公式,将在稍后添加此。 这是一个最佳的速度/精度的平衡最优化。

对于定义的区域搜索(给我X公里内的所有点),我们还存储纬度/经度值乘以与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 ;

有问题吗? 否则,有乐趣:)



文章来源: Geo distance MySQL
标签: mysql geo