PHP+MySQL: поиск по координатам

izbushka
На сайте с 08.06.2007
Offline
110
7334

Нужно выбирать ближайшие объекты по заданным координатам из базы (100к записей).

Особая точность не нужна, можно выбирать из прямоугольника с диагональю двойного растояния поиска (на окружность ресурсы тратить не стоит)

Лучшее что удалось найти: это

Там предлагает два варианта:

1. использовать процедуру, которая, ограничивает прямоугольник поиска и выбирает between координат.

2. использовать spatial функции, которые по своей сути делают тоже самое.

Т.е. выбрать объекты вроде как не состовляет сложности, но если в квадрате будет их много (скажем 10к) мне кажется будет долго order by distance..

У кого есть опыт?

LEOnidUKG
На сайте с 25.11.2006
Offline
1723
#1

Ничего долгого, если у вас место по сути не ограничено. Создайте кэш для каждого объекта и не нужно каждый раз делать поиск.

✅ Мой Телеграм канал по SEO, оптимизации сайтов и серверов: https://t.me/leonidukgLIVE ✅ Качественное и рабочее размещение SEO статей СНГ и Бурж: https://getmanylinks.ru/
IL
На сайте с 20.04.2007
Offline
435
#2
LEOnidUKG:
Создайте кэш для каждого объекта и не нужно каждый раз делать поиск.

А что кэшировать? distance-то от объекта до произвольной координаты изменяется..

izbushka:
мне кажется будет долго order by distance

Если действительно будет - тогда думать..

Если не критичны незначительные ошибки при сортировке, можно другой, менее ресурсоёмкой метрикой пользоваться.. (к примеру, |x1-x2|+|y1-y2|)

... :) Облачные серверы от RegRu - промокод 3F85-3D10-806D-7224 ( http://levik.info/regru )
IK
На сайте с 08.10.2008
Offline
35
#3

В свое время сталкивался с похожей задачей когда нужно было выводить обьекты недвижимости на Яндекс картах.

Data - таблица объектов

Определяли местоположение объекта

latitude - float
longitude – float
$leftx , $lefty - левый нижний угол
$rightx, $righty – правый верхний угол

Зная точку вокруг которой нужно искать очень легко в пхп построчить верхний и нижний угол

дальше на основе этих данных строился SQL запрос

$where=' Data.latitude>="'.$lefty.'" AND Data.longitude>="'.$leftx.'" AND Data.latitude<="'.$righty.'" AND Data.longitude<="'.$rightx.'" ';

Запрос очень простой БАЗА была MySQL записей ~ 1M, но в выборке участвовали максимум 100 тыщ. Естественно latitude, longitude добавили в индекс, так как это числа то запрос довольно быстро отрабатывал и не создавал нагрузку.(тупо в лоб, без красивостей - но оно работало!!!!)

Самый геморрой начался когда выбиралась область а в ней от 5 до 30 тысяч объектов . А на карту одновременно мы выводили не более 500 объектов. Вариант когда тупо стоит LIMIT 0,500; отмели сразу. Задача стояла выводить рендомно каждый раз объекты для это области т.е чтобы не было такого что показываются одни и те же 500 последних. Пробовали order by RAND() LIMIT 0,500 -- запрос начал жрать кучу ресурсов и тормозить.

В итоге сделали костыльное решение во все записи добавили 10 новых полей RAND1, RAND….RAND10. Все эти поля заполнялись при вставке записи в базу каждое поле получало свое рендомное значение от 1 до 1000. А когда в пхп строился запрос на выборку то случайным образом выбиралось 1 поле из 10 по которому будут сортироваться этот раз записи. Решение конечно довольно колхозное – но стабильно работало. И когда единицей поиска был целый город разные люди при заходах получали разные наборы выборок.

Бесплатный онлайн-консультант Chathelp.ru (http://chathelp.ru/)
doctorpc
На сайте с 12.07.2009
Offline
112
#4

Использую комбинированный вариант.

1) Выделение максимальных/минимальных координат на нужном расстоянии от заданной точки и фильтр всех точек, которые попадают в эту область.

2) Среди них уже расчет точного расстояния до начальной точки и сортировка по этому значению.

Проблем с производительностью замечено не было. У меня в этот прямоугольник обычно может попадать до 2к точек пока. Работающий пример

Для первого этапа граничные значения расчитываются вот так:


$R = 6371; // earth's radius, km
// first-cut bounding box (in degrees)
$max_latitude = $latitude + rad2deg($max_distance/$R);
$min_latitude = $latitude - rad2deg($max_distance/$R);
// compensate for degrees longitude getting smaller with increasing latitude
$max_longitude = $longitude + rad2deg($max_distance/$R/cos(deg2rad($latitude)));
$min_longitude = $longitude - rad2deg($max_distance/$R/cos(deg2rad($latitude)));

Дальше эти значения просто в WHERE добавляются в запрос. С использованием ORM у меня так, но думаю, смысл понятен:


$this->where('address.latitude', '>', $min_latitude)
->where('address.latitude', '<', $max_latitude)
->where('address.longitude', '>', $min_longitude)
->where('address.longitude', '<', $max_longitude);

Использую встроенную процедуру для определения расстояния на втором этапе. По результату и делаю сортировку.



CREATE DEFINER=`root`@`%` FUNCTION `GetDistance`(`lat1` VARCHAR(120), `lon1` VARCHAR(120), `lat2` VARCHAR(120), `lon2` VARCHAR(120))
RETURNS varchar(120) CHARSET latin1
LANGUAGE SQL
NOT DETERMINISTIC
CONTAINS SQL
SQL SECURITY DEFINER
COMMENT ''
BEGIN
DECLARE distance DECIMAL(12,8);
select ((ACOS(SIN(lat1 * PI() / 180) * SIN(lat2 * PI() / 180) + COS(lat1 * PI() / 180) * COS(lat2 * PI() / 180) * COS((lon1 - lon2) * PI() / 180)) * 180 / PI()) * 60 * 1.1515 * 1.609344) into distance;
RETURN distance;

END

Пример расчета расстояния от точки А с координатами lat1, lon1 до точки B c координатами lat2 : GetDistance(lat1,lon1,lat2,lon2);

Если что-то не понятно описал, обращайтесь, расскажу.

Авторизуйтесь или зарегистрируйтесь, чтобы оставить комментарий