We gebruiken double
om latitude
op te slaan en longitude
. Daarnaast berekenen we vooraf (door triggers) alle waarden die vooraf kunnen worden berekend als we naar slechts één punt kijken. Ik heb momenteel geen toegang tot de formule die we gebruiken, zal dit later toevoegen. Dit is geoptimaliseerd voor een optimale balans tussen snelheid en precisie.
Voor zoekopdrachten in gedefinieerde gebieden (geef me alle punten binnen x km) slaan we bovendien de lat/lng-waarde op vermenigvuldigd met 1e6
(1.000.000) zodat we kunnen beperken tot een vierkant door gehele reeksen te vergelijken, wat razendsnel is, bijvoorbeeld
lat BETWEEN 1290000 AND 2344000
AND
lng BETWEEN 4900000 AND 4910000
AND
distformularesult < 20
BEWERKEN:
Hier is de formule en voorberekening van de waarden van de huidige plaats in PHP.
WindowSize is een waarde waarmee je moet spelen, het is gradenfactor 1e6, gebruikt om de mogelijke resultaten in een vierkant rond het midden te verkleinen, versnelt het vinden van resultaten - vergeet niet dat dit minstens moet zijn uw zoekradiusgrootte.
$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;
Zoeken in alle rijen binnen een specifiek bereik van mijn centrum
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`
De formule heeft een vrij goede nauwkeurigheid (minder dan een meter, afhankelijk van waar je bent en wat de afstand tussen het punt is)
Ik heb de volgende waarden vooraf berekend in mijn 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
Voorbeeld 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 ;
Vragen? Anders veel plezier :)