Използваме double
за съхраняване на latitude
и longitude
. Освен това ние предварително изчисляваме (чрез тригери) всички стойности, които са предварително изчислени, когато гледаме само в една точка. В момента нямам достъп до формулата, която използваме, ще добавя това по-късно. Това е оптимизирано за оптимален баланс скорост/прецизност.
За търсене на определени зони (дайте ми всички точки в рамките на x km) ние допълнително съхраняваме стойността на 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 ;
Въпроси? Иначе се забавлявайте :)