sql >> Databasteknik >  >> RDS >> Mysql

Geo distans MySQL

Vi använder double för att lagra latitude och longitude . Dessutom förberäknar vi (genom utlösare) alla värden som är förberäknade när vi tittar på endast en punkt. Jag har för närvarande inte tillgång till formeln vi använder, kommer att lägga till detta senare. Detta är optimerat för en optimal balans mellan hastighet och precision.

För definierade områdessökningar (ge mig alla poäng inom x km) lagrar vi dessutom lat/lng-värdet multiplicerat med 1e6 (1 000 000) så vi kan begränsa till en kvadrat genom att jämföra heltalsintervall som är blixtsnabb, t.ex.

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

EDIT:

Här är formeln och förberäkningen av värden för den aktuella platsen i PHP.

WindowSize är ett värde du måste leka med, det är graders faktor 1e6, används för att begränsa de möjliga resultaten i en ruta runt mitten, snabbar upp resultatsökningen - glöm inte att detta bör vara minst din sökradiestorlek.

$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;

Söker efter alla rader inom ett specifikt område i mitt center

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`

Formeln har en ganska bra noggrannhet (under en meter, beroende på var du är och vilket avstånd som är mellan punkten)

Jag har förberäknat följande värden i min databastabell 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

Exempel på trigger

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 ;

Frågor? Ha det annars bra :)




  1. mysql, dump, databasåterställning

  2. MySql-bitkolumner returnerar konstigt stort antal i PHP 7.1 (inte i tidigare versioner)

  3. Välj endast det sista värdet med hjälp av group by at mysql

  4. Konstigt Mysql-fel 1111, förmodligen fungerat tidigare