Поиск ближайшего объекта на Яндекс.Карте

При создании карты с использованием API Яндекс.Карт, может возникнуть задача определения ближайшего объекта для некоторой точки. Например, Вы находитесь по такому-то адресу и хотите узнать какие кафе находятся рядом с вами.

Как это реализовать я расскажу в данной заметке.

В начале нам понадобиться в базе данных MySQL создать таблицу markers с исходными данными(кафе в Нижнем Новгороде).

Сделать это можно с помощью утилиты phpMyAdmin и файлом с SQL-кодом markers.sql

Загрузить файл markers.sql

Для выборки меток из таблицы, которые находятся в пределах окружности заданного радиуса от центра (адрес нашего местонахождения) мы будем использовать запрос со специальной формулой определения расстояния между двумя точками на сфере, заданных значениями их широты и долготы (http://en.wikipedia.org/wiki/Haversine_formula и http://www.movable-type.co.uk/scripts/latlong.html) .

Пример SQL запроса ближайших 20 мест, которые находятся в радиусе 25 километров от точки с координатами 43.866379, 56.347038:

SELECT id, ( 6371 * acos( cos( radians(43.866379) ) * cos( radians( lat ) ) * cos( radians( lng ) — radians(56.347038) ) + sin( radians(43.866379) ) * sin( radians( lat ) ) ) ) AS distance FROM markers HAVING distance < 25 ORDER BY distance LIMIT 0 , 20;

Из найденных результатов формируется файл в формате JSON, который затем обрабатывается и метки отображаются на карте.

Приведу полный код исходных файлов примера с пояснением:

ymap-location.html — создание карты и отображение маркеров

<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <title>Поиск ближайшего объекта - API Яндекс.Карт</title>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
 <script src="http://api-maps.yandex.ru/1.1/index.xml?key=AMSxmUwBAAAA5WSyQAMAwB5YJ3rvTgSgDcPJynaIsXvmUkYAAAAAAAAAAABv1IbpHjj4ZIFUHyMRhnQtZxjXZg==" type="text/javascript"></script> 
    <script type="text/javascript">
        var map, geoResult;
 
        // Создание обработчика для события window.onLoad
        YMaps.jQuery(function () {
            // Создание экземпляра карты и его привязка к созданному контейнеру
            map = new YMaps.Map(YMaps.jQuery("#YMapsID")[0]);
 
            // Установка для карты ее центра и масштаба
            map.setCenter(new YMaps.GeoPoint(43.998779,56.316537), 12);
 
map.addControl(new YMaps.Zoom());
 
            // Добавление элементов управления
            map.addControl(new YMaps.TypeControl());
        });
 
        // Функция для отображения результата геокодирования
        // Параметр value - адрес объекта для поиска
        function showAddress (value) {
 
 
            // Удаление предыдущего результата поиска
            map.removeOverlay(geoResult);
 
            // Запуск процесса геокодирования
            var geocoder = new YMaps.Geocoder(value, {results: 1, boundedBy: map.getBounds()});
 
            // Создание обработчика для успешного завершения геокодирования
            YMaps.Events.observe(geocoder, geocoder.Events.Load, function () {
                // Если объект был найден, то добавляем его на карту
                // и центрируем карту по области обзора найденного объекта
                if (this.length()) {
                    geoResult = this.get(0);
 
                var radius = YMaps.jQuery("#radiusSelect option:selected").val();                  
 
		searchLocationsNear(this.get(0).getGeoPoint(), radius);	
                   // map.addOverlay(geoResult);
                   // map.setBounds(geoResult.getBounds());
                }else {
                    alert("Ничего не найдено")
                }
            });
 
            // Процесс геокодирования завершен неудачно
            YMaps.Events.observe(geocoder, geocoder.Events.Fault, function (geocoder, error) {
                alert("Произошла ошибка: " + error);
            })
        }
 
function searchLocationsNear(center,radius) {
 
clearLocations(); 
 
YMaps.jQuery.getJSON("poisk.php", {center:center, radius: radius}, function(data){
 
if (data.status == 'OK') { 
var geoBounds = new YMaps.GeoCollectionBounds();
 
var src_res="<p><strong>результаты поиска: </strong></p>";
src_res=src_res+'<p><strong>Найдено объектов: '+data.markers.length+'</strong></p>';
for (i = 0; i < data.markers.length; i++) {
var placemark=new YMaps.Placemark(new YMaps.GeoPoint(data.markers[i].lat,data.markers[i].lng));
placemark.description= '<div style="color:#ff0303;font-weight:bold">'+data.markers[i].name+'</div>';
placemark.description = placemark.description+'<strong>Адрес:</strong> '+data.markers[i].address;
map.addOverlay(placemark);
geoBounds.add(new YMaps.GeoPoint(data.markers[i].lat,data.markers[i].lng));
src_res=src_res+'<p><a href="#" onClick="return go_to('+data.markers[i].lat+', '+data.markers[i].lng+",'"+data.markers[i].name+"','"+data.markers[i].address+"','"+data.markers[i].id+"');"+'">'+data.markers[i].name+'</a><br>'+data.markers[i].address+'</p>';
}
map.setBounds(geoBounds);
 
}
else
{
var src_res = '<p><strong>результаты поиска</strong></p><font color="red">По Вашему запросу объектов на карте не найдено</font>';
}
YMaps.jQuery('#result').html(src_res);
 
 
	})
 
 
	}
 
function go_to(lat,lng,name,address,id){
map.setCenter(new YMaps.GeoPoint(lat,lng),17);
map.removeAllOverlays();
var placemark=new YMaps.Placemark(new YMaps.GeoPoint(lat,lng));
placemark.name='<div style="color:#ff0303;font-weight:bold">'+name+'</div>';
placemark.description = '<strong>Адрес:</strong> '+address+'</div></div>';
map.addOverlay(placemark);
placemark.openBalloon();
return false;
}
 
 
function clearLocations() {
 
map.removeAllOverlays();
 
geoResult = 0;
 
YMaps.jQuery('#result').html('');
 
}
 
 
</script>
</head>
 
<body>
<table border="0">
<tr><td style="vertical-align:top; ">
<form action="#" onsubmit="showAddress(this.address.value);return false;">
 
<input type="text" id="address" style="width:450px;" value="г. Нижний Новгород,  Студеная ул., 7" /> 
<select id="radiusSelect">
 
<option value="5" selected>5km</option>
 
<option value="10" selected>10km</option>
 
<option value="15">15km</option>
 
<option value="25">25km</option>
 
<option value="50">50km</option>
 
</select>
<input type="submit" value="Искать" />
 
<div id="YMapsID" style="width:600px;height:400px"></div>
</form>
</td>
<td style="vertical-align:top; ">
<div id="result"></div>
</td>
</tr>
</table>
 
</body>
</html>

Здесь после ввода значений в форму (адрес, радиус) вызывается функция showAddress в которой производится геокодирование адреса и полученное значение координат в месте с радиусом передаются в функцию searchLocationsNear.

В этой функции формируется AJAX-запрос к скрипту poisk.php для выборки необходимых меток.

Из ответа в формате JSON формируется коллекция меток которая отображается на карте.

При клике по ссылке с названием метки вызывается функция go_to для перемещения и масштабирования карты на метку, также открывается балун с информацией.

poisk.php — выборка данных из базы и формирование ответа в формате JSON

<?php
 
include("config.php");
 
header('Content-Type: text/html; charset=utf-8');
if($_SERVER['HTTP_X_REQUESTED_WITH'] == 'XMLHttpRequest') {
 
$center = $_GET["center"];
 
$exp_str1 = explode(",", $center);
 
$center_lat = $exp_str1[0];
$center_lng = $exp_str1[1];
$radius = $_GET["radius"];
 
$json = '{markers:['."n";
 
$query = sprintf("SELECT address, name, lat, lng, ( 6371 * acos( cos( radians('%s') ) * cos( radians( lat ) ) * cos( radians( lng ) - radians('%s') ) + sin( radians('%s') ) * sin( radians( lat ) ) ) ) AS distance FROM markers HAVING distance < '%s' ORDER BY distance LIMIT 0, 20",
  mysql_real_escape_string($center_lat),
  mysql_real_escape_string($center_lng),
  mysql_real_escape_string($center_lat),
  mysql_real_escape_string($radius));
$result = mysql_query($query);
 
if (!$result) {
  die("Invalid query: " . mysql_error());
}
if(mysql_num_rows($result)>0)
{
while ($row = mysql_fetch_array($result)){
 
$json.= "n".'{'.'"id": "'.$par1['id'].'",';
 
$json.= '"name": "'.$row['name'].'",';
 
$json.= '"address": "'.htmlspecialchars($row['address']).'",';
 
$json.= '"distance": "'.$row['distance'].'",';
 
$json.= '"lat": "'.$row['lat'].'",';
 
$json.=  '"lng": "'.$row['lng'].'"';
 
$json.= '},';
 
}
 
$json = substr($json, 0,-1);
 
echo  $json;
 
echo '], ', "n",  '"status": "OK"', "n", '}';
 
}
else
{
echo '{"status": "false"}';
}
 
}
 
?>

config.php — файл соединения с базой данных

<?php
 
$sdb_name = "localhost";
$user_name = "user";
$user_password = "user123";
$db_name = "ymaps_bd";
 
// соединение с сервером базы данных
if(!$link = mysql_connect($sdb_name, $user_name, $user_password))
{
  echo "<br>Не могу соединиться с сервером базы данных<br>";
  exit();
}
 
// выбираем базу данных
if(!mysql_select_db($db_name, $link))
{
  echo "<br>Не могу выбрать базу данных<br>";
  exit();
}
 
mysql_query('SET NAMES utf8');
 
?>

Посмотреть пример в действии

В строку поиска мы вводим адрес, где мы находимся, например, г. Нижний Новгород, ул. Маршала Рокоссовского, 8

Из выпадающего списка выбираем величину радиуса окружности от данной точки в километрах, например 10 км.

Нажимаем кнопку Искать

В правой стороне от карты появляются ссылки на найденные объекты, расположенные в порядке удаления от исходной точки.

Т. е. Астор, ООО по адресу г. Нижний Новгород, Штеменко генерала ул., 5 расположено ближе всех к точке с адресом г. Нижний Новгород, ул. Маршала Рокоссовского, 8, а

Для подготовки заметки использовалась статья «Creating a Store Locator with PHP, MySQL & Google Maps»

  • Гость: Все интернеты обошел в поисках этого решения, а оно уже у вас, Спасибо, Сергей!
  • Гость: А в MYSQL не проще использовать её функции http://dev.mysql.com/doc/refman/5.5/en/spatial-extensions.html
  • Гость: Все бы хорошо, но почему то у вас в демо работает поиск по другим городам например, подключил себе, по нижнему новгороду работает, а по остальным нет, даже не выдает результатов поиска (даже строчки типа: "не найденно"), хотя метки с другим городом, по аналогии добавил в базу
  • Гость: подскажите, как сделать, чтобы при первом попадании на страницу уже срабатывал поиск по умолчанию. или как по умолчанию показать точки в радиусе 25 км до ввода данных в поиск. спасибо
  • Гость: Нужно будет сразу вызвать функцию showAddress для нужного адреса, а значение радиуса по умолчанию сделать 25 км.
  • Гость: спасибо! но я не силён в JS, можете подсказать код?
  • Гость: Нужно добавить после строчки map.addControl(new YMaps.TypeControl()); строку showAddress("г. Нижний Новгород, Студеная ул., 7"); А в списке select : <pre lang="html"> <select id="radiusSelect"> <option value="5">5km</option> <option value="10">10km</option> <option value="15">15km</option> <option value="25" selected>25km</option> <option value="50">50km</option> </select> </pre>
  • Гость: то что надо! спасибо!
  • Гость: Создал всё как тут написано. Но при нажатии кнопки "Поиск" вообще ничего не происходит. Причём пример на вашем сайте работает, а мой нет. Другой ваш пример разместил себе - он работает. Проблема только с этим. В чём может быть проблема?
  • Гость: Заработало! Спасибо за вашу работу!
  • Гость: Ещё вопрос. А как сделать, чтобы поиск вёлся не по названию а по координатам?
  • Гость: За SQL запрос отдельное спасибо!
  • Гость: Присоединяюсь к Павлу, запрос - спас. Не совсем понятно число 6371 в запросе.
  • Гость: Число 6371 - это средний радиус Земли в километрах
  • Гость: А как бы ещё вывести балун другого цвета в месте откуда идёт поиск, а то результат поиска виден и начинаешь искать место где я нахожусь, он далеко не всегда в центре карты.
  • Гость: Добрый день. А как добавлять свои объекты лучше всего? Как-то же нужно определить координаты.
  • Гость: Здравствуйте! Пример работает неправильно. Провел эксперимент. Например, есть ТОЧКА маршрут до которой проложил яндекс и расстояние он показал до неё 140 км по дороге. Если вывести точки с радиусом 150 км, то эта точка туда не попадает, а попадает только если вывести точки с радиусом 200 км. Проверил несколько раз. Как исправить эту проблему?
  • Гость: Подскажите, пожалуйста, как на google maps сделать тоже самое? Или как сделать, чтобы показывало ближайшие N объектов на карте? Спасибо!
  • Гость: Число мест задается в SQL запросе параметром LIMIT 0 , 20 Вы можете изменить 20 на любое другое значение. По использованию в Google Maps была <a href="http://webmap-blog.ru/google-maps/ishhem-blizhajshie-organizacii-ot-ukazannogo-mesta-i-pokazyvaem-ix-na-google-maps" title="Ищем ближайшие организации от указанного места и показываем их на Google Maps" target="_blank" rel="nofollow">заметка</a>
  • Гость: Добрый день подскажите пожалуйста, я так понимаю этот вариант работает для api версии 1.1 , а как полностью на 2.0 переписать? С первой частью вроде немного понятно например для поиска по адресу переписываем на var geocoder = ymaps.geocode(value); geocoder.then( function (res) { myMap.geoObjects.add(res.geoObjects); }, function (err) { // обработка ошибки } ); а дальше интересует центровка карты по области обзора найденного объекта Заранее спасибо за ответ.
  • Гость: Например, так: var coord = res.geoObjects.get(0).geometry.getCoordinates(); myMap = new ymaps.Map ("map", { center: coord, zoom: 14 });
  • Гость: Здраствуйте, подскажите пожалуйста, как можно исправить запрос чтобы выводились обекты которые лежат между двумя точками, учитывать например только долготу, если поля с радиусом убрать, а вставить вместно него еще поля с адресом.
  • Гость: Спасибо за хороший урок! А как можно добавить к этому выборку по какому-либо параметру из бд, скажем есть список ресторанов, если средний чек 20 уе, то показывает только эти рестораны?
  • Гость: Добрый день, спасибо за тему. очень помогает.но сталкнулся с проблемой . выдается Notice на 32 строку файла Poisk.php. $json.= "n".'{'.'"id": "'.$par1['id'].'",'; не нашел откуда берется данный параметр $par1['id']?? и для чего он нужен??
  • Гость: Что то запрос не работает.
  • Гость: ошибка выполнения запроса sql если все взять с этой страницы #1064 - You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '— radians(56.347038) ) + sin( radians(43.866379) ) * sin( radians( lat ) ) ) )' at line 1 в запросе cos( radians( lng ) — radians(56.347038) — заменить на -
  • Алексей: Приветствую! Дайте пожалуйста готовые исходники, я не смог то реализовать на практике, даётся ошибка invаlid kеу
  • Алексей: И очень хотел бы Вашей помощи, как сделать такую функцию которая позволило бы показать все объекты рядом находящиеся (например имеется меню, и ты нажимаешь на школу и в карте показывается вся школа рядом находящееся). (Тоесть без набора в адресную строку , а всего лишь нажимая на меню объектов) Что то вроде картинки.