EDIT
Этот искатель местоположения появляется достаточно часто, и я написал об этом статью.
http://www.plumislandmedia.net/mysql/haversine-mysql-nearest-loc/
Исходное сообщение
Давайте начнем с того, что раз и навсегда разберемся с формулой haversine, поместив ее в хранимую функцию, чтобы мы могли забыть о ее мрачных деталях. ПРИМЕЧАНИЕ: все это решение в уставных милях.
DELIMITER $$
CREATE
FUNCTION distance(lat1 FLOAT, long1 FLOAT, lat2 FLOAT, long2 FLOAT)
RETURNS FLOAT
DETERMINISTIC NO SQL
BEGIN
RETURN (3959 * ACOS(COS(RADIANS(lat1))
* COS(RADIANS(lat2))
* COS(RADIANS(long1) - RADIANS(long2))
+ SIN(RADIANS(lat1))
* SIN(RADIANS(lat2))
));
END$$
DELIMITER ;
Теперь давайте составим запрос, который ищет в ограничительной рамке, а затем уточнит поиск с помощью нашей функции расстояния и порядков по расстоянию
На основе PHP-кода в вашем вопросе:
Предположим, $radius
- ваш радиус, $center_lat
, $center_lng
- ваша контрольная точка.
$sqlsquareradius = "
SELECT post_id, lat, lng
FROM
(
SELECT post_id, lat, lng,
distance(lat, lng, " . $center_lat . "," . $center_lng . ") AS distance
FROM wp_geodatastore
WHERE lat >= " . $center_lat . " -(" . $radius . "/69)
AND lat <= " . $center_lat . " +(" . $radius . "/69)
AND lng >= " . $center_lng . " -(" . $radius . "/69)
AND lng <= " . $center_lng . " +(" . $radius . "/69)
)a
WHERE distance <= " . $radius . "
ORDER BY distance
";
Обратите внимание на несколько вещей.
Во-первых, он выполняет вычисления ограничивающего прямоугольника в SQL, а не в PHP. Для этого нет веской причины, кроме как хранить все вычисления в одной среде. (radius / 69)
- это количество градусов в radius
статутных милях.
Во-вторых, он не зависит от размера продольной ограничительной рамки в зависимости от широты. Вместо этого он использует более простую, но немного слишком большую ограничивающую рамку. Эта ограничительная рамка захватывает несколько дополнительных записей, но измерение расстояния избавляет от них. Для вашего типичного приложения поиска почтового индекса / магазина разница в производительности незначительна. Если бы вы искали еще много записей (например, в базе данных всех полюсов электропитания), это могло бы быть не так тривиально.
В-третьих, он использует вложенный запрос для устранения расстояния, чтобы избежать необходимости запускать функцию расстояния более одного раза для каждого элемента.
В-четвертых, он заказывает по расстоянию по возрастанию. Это означает, что ваши результаты с нулевым расстоянием должны отображаться первыми в наборе результатов. Обычно имеет смысл сначала перечислить ближайшие вещи.
В-пятых, используется FLOAT
вместо DOUBLE
. Для этого есть веская причина. Формула расстояния хаверсин не идеальна, потому что она приближается к тому, что земля - идеальная сфера. Это приближение нарушается примерно с тем же уровнем точности, что и эпсилон для FLOAT
чисел. Таким образом, DOUBLE
является обманчивым численным перебором для этой проблемы. (Не используйте эту формулу haversine для выполнения строительных работ, таких как дренаж на стоянке, или вы получите большие лужи, пару эпсилон, глубиной несколько дюймов, обещаю.) Это хорошо для приложений поиска магазинов.
В-шестых, вы определенно захотите создать индекс для столбца lat
. Если ваша таблица местоположений меняется не очень часто, это поможет также создать индекс для вашего столбца lng
. Но ваш индекс lat
даст вам большую часть прироста производительности запросов.
Наконец, я протестировал хранимую процедуру и SQL, но не PHP.
Ссылка: http://www.scribd.com/doc/2569355/Geo-Distance-Search-with-MySQL
Также мой опыт работы с кучей бесконтактных искателей для медицинских учреждений.
--------------- РЕДАКТИРОВАТЬ --------------------
Если у вас нет пользовательского интерфейса, позволяющего определить хранимую процедуру, это неприятно. В любом случае, PHP позволяет использовать пронумерованные параметры в вызове sprintf, поэтому вы можете сгенерировать целое вложенное выражение, как это. ПРИМЕЧАНИЕ. Вам может потребоваться% $ 1f и т. Д. Вам нужно будет поэкспериментировать с этим.
$sql_stmt = sprintf ("
SELECT post_id, lat, lng
FROM
(
SELECT post_id, lat, lng,
(3959 * ACOS(COS(RADIANS(lat))
* COS(RADIANS(%$1s))
* COS(RADIANS(lng) - RADIANS(%$2s))
+ SIN(RADIANS(lat))
* SIN(RADIANS(%$1s))
))
AS distance
FROM wp_geodatastore
WHERE lat >= %$1s -(%$3s/69)
AND lat <= %$1s +(%$3s/69)
AND lng >= %$2s -(%$3s/69)
AND lng <= %$2s +(%$3s/69)
)a
WHERE distance <= %$3s
ORDER BY distance
",$center_lat,$center_lng, $radius);