Для одного из проектов потребовалось реализовать программный функционал поиска ближайших объектов на карте в определенном радиусе от заданной точки с координатами широты и долготы. Такие задачи как правило требуют решения Прямой и Обратной геодезических задач, а поскольку с геодезией я совершенно не дружу, то обратил свое внимание на готовые решения, одним из который стал Sphinx.
Наверняка многие воспринимают Sphinx исключительно как полнотекстовый поисковый движок, я же хотел бы обратить внимание на магическую функцию @geodist, которую можно использовать для нахождения в индексе объектов на заданном расстоянии по их координатам (широте и долготе). Для нахождения ближайших объектов требуется не много:
- База данных поддерживаемая Sphinx’ом с координатами объектов поиска
- Установленный Sphinx Search
- А также PHP + sphinxapi (или любой другой язык для которого есть sphinxapi)
База данных
Я не буду описывать процесс установки Sphinx’а на хостинг, мануалов в инете полно да и сам процесс установки банален и не требует каких-то особенных умений. Ниже привожу примерный файл конфига который понадобится для индексации таблицы с координатами объектов
# Источник данных для поиска
source main
{
# параметры подключения к mysql
type = mysql
sql_host = localhost
sql_user = user
sql_pass = password
sql_db = database
sql_port = 3306
# запросы после установки соединения
sql_query_pre = SET NAMES utf8
sql_query_pre = SET SESSION query_cache_type=OFF
# не засыпать между шагами индексации
sql_ranged_throttle = 0
}
# Источник данных для гео-поиска (наследует блок main)
source geo : main
{
# запрос выборки широты и долготы в радианах для индексации
sql_query = \
SELECT `id`,
radians(`latitude`), \
radians(`longitude`) \
FROM table_with_objects
# обрабатывать поля как float
sql_attr_float = latitude
sql_attr_float = longitude
}
# настройка индекса
index geo
{
# использовать соответствующий блок из source
source = geo
# путь для хранения файлов индекса
path = /usr/home/www/sphinx/indexes/closest
docinfo = extern
mlock = 0
min_word_len = 1
charset_type = utf-8
}
# настройки демона
searchd
{
# прослушивание порта
listen = 9312
# хранение логов
log = /usr/home/www/sphinx/log/searchd.log
query_log = /usr/home/www/sphinx/log/query.log
pid_file = /usr/home/www/sphinx/log/searchd.pid
}
Индексация
При первоначальном запуске просто скармливаем созданных конфиг индексатору
indexer --config /usr/home/www/sphinx/sphinx.conf --all
Для переиндексации текущего индекса необходимо использовать параметр «—rotate», он добавит к созданному индексу новые данные
indexer --config /usr/home/www/sphinx/sphinx.conf --rotate
Запускаем демона
searchd --config /usr/home/www/sphinx/sphinx.conf
PHP + sphinxapi
Для проверки работы создадим простую форму
<form action="" method="get">
Долгота <input name="longitude" size="40" value="<?=$_GET['longitude']?>"><br>
Широта <input name="latitude" size="40" value="<?=$_GET['latitude']?>" /><br>
Радиус <input name="radius" size="40" value="<?=$_GET['radius']?>"><br>
<input type="submit" value="Искать!">
</form>
<?php
if(isset($_GET['longitude']) and strlen($_GET['longitude']) > 2)
{
require_once('sphinxapi.php');
function collectIds($arr)
{
return $arr['id'];
}
$_longitude = $_GET['longitude'];
$_latitude = $_GET['latitude'];
$_radius = (intval($_GET['radius']) > 0) ? intval($_GET['radius']) : 10;
$search = new SphinxClient(); // Создание экземпляра клиента
$search->SetServer("localhost", 9312); // Подсключаемся
$search->SetMatchMode(SPH_MATCH_ALL); //
$search->SetArrayResult(true); // Не использовать id документов в качестве ключей
$search->SetLimits(0, 5); // Ограничить поиск 5 объектами
$search->SetSortMode ( SPH_SORT_EXTENDED, "@geodist asc"); // Сортировать объекты по расстоянию от исходной точки поиска
$search->SetGeoAnchor('latitude', 'longitude', deg2rad($_latitude), deg2rad($_longitude));
$circle = (float) $_radius;
$search->SetFilterFloatRange('@geodist', 0.0, $circle);
$result = $search->Query('', 'closestStores');
if($result AND $result['total'] > 0)
{
print_r($result);
$ids = array_map('collectIds', $result['matches']);
print_r($ids);
}
}
?>
Если все настроено правильно, то под формой отобразится массив с результатами выборки.