Vi bruger double
for at gemme latitude
og longitude
. Derudover forberegner vi (ved triggere) alle værdier, som er forudberegnelige, når vi kun ser på ét punkt. Jeg har i øjeblikket ikke adgang til den formel, vi bruger, tilføjer dette senere. Dette er optimeret til en optimal balance mellem hastighed og præcision.
For definerede områdesøgninger (giv mig alle punkter inden for x km) gemmer vi desuden lat/lng-værdien ganget med 1e6
(1.000.000), så vi kan begrænse til en firkant ved at sammenligne heltalsområder, som er lynhurtige f.eks.
lat BETWEEN 1290000 AND 2344000
AND
lng BETWEEN 4900000 AND 4910000
AND
distformularesult < 20
EDIT:
Her er formlen og forudberegningen af værdier for det aktuelle sted i PHP.
WindowSize er en værdi, du skal lege med, det er graders faktor 1e6, bruges til at indsnævre de mulige resultater i en firkant rundt om midten, fremskynder resultatfinding - glem ikke, at dette skal være mindst din søgeradiusstørrelse.
$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øger alle rækker inden for et specifikt område af mit 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`
Formularen har en ganske god nøjagtighed (under en meter, afhængigt af hvor du er og hvilken afstand der er mellem punktet)
Jeg har forudberegnet følgende værdier i min databasetabel 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
Eksempel 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 ;
Spørgsmål? Ellers god fornøjelse :)