Chúng tôi đang sử dụng double
để lưu trữ latitude
và longitude
. Ngoài ra, chúng tôi báo trước (bằng cách kích hoạt) tất cả các giá trị có thể tính toán trước khi chỉ nhìn vào một điểm. Tôi hiện không có quyền truy cập vào công thức chúng tôi đang sử dụng, sẽ thêm điều này sau. Điều này được tối ưu hóa để cân bằng tốc độ / độ chính xác tối ưu.
Đối với các tìm kiếm khu vực đã xác định (cho tôi tất cả các điểm trong phạm vi x km), chúng tôi cũng lưu trữ giá trị vĩ độ / lng nhân với 1e6
(1.000.000) để chúng tôi có thể giới hạn thành một hình vuông bằng cách so sánh các phạm vi số nguyên nhanh như chớp, ví dụ:
lat BETWEEN 1290000 AND 2344000
AND
lng BETWEEN 4900000 AND 4910000
AND
distformularesult < 20
CHỈNH SỬA:
Đây là công thức và tính toán trước các giá trị của vị trí hiện tại trong PHP.
WindowSize là một giá trị mà bạn phải tuân theo, đó là hệ số độ 1e6, được sử dụng để thu hẹp các kết quả có thể có trong một hình vuông xung quanh trung tâm, tăng tốc độ tìm kiếm kết quả - đừng quên điều này phải là ít nhất kích thước bán kính tìm kiếm của bạn.
$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;
Tìm kiếm tất cả các hàng trong một phạm vi cụ thể của trung tâm của tôi
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`
Công thức có độ chính xác khá tốt (dưới một mét, tùy thuộc vào vị trí của bạn và khoảng cách giữa các điểm)
Tôi đã tính toán trước các giá trị sau trong bảng cơ sở dữ liệu của mình 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
Trình kích hoạt mẫu
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 ;
Câu hỏi? Nếu không, hãy vui vẻ :)