При создании карты с использованием 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 :
то что надо! спасибо!
Создал всё как тут написано. Но при нажатии кнопки «Поиск» вообще ничего не происходит. Причём пример на вашем сайте работает, а мой нет.
Другой ваш пример разместил себе — он работает. Проблема только с этим. В чём может быть проблема?
Заработало! Спасибо за вашу работу!
Ещё вопрос. А как сделать, чтобы поиск вёлся не по названию а по координатам?
За SQL запрос отдельное спасибо!
Присоединяюсь к Павлу, запрос — спас.
Не совсем понятно число 6371 в запросе.
Число 6371 — это средний радиус Земли в километрах
А как бы ещё вывести балун другого цвета в месте откуда идёт поиск, а то результат поиска виден и начинаешь искать место где я нахожусь, он далеко не всегда в центре карты.
Добрый день.
А как добавлять свои объекты лучше всего? Как-то же нужно определить координаты.
Здравствуйте! Пример работает неправильно. Провел эксперимент. Например, есть ТОЧКА маршрут до которой проложил яндекс и расстояние он показал до неё 140 км по дороге. Если вывести точки с радиусом 150 км, то эта точка туда не попадает, а попадает только если вывести точки с радиусом 200 км. Проверил несколько раз. Как исправить эту проблему?
Подскажите, пожалуйста, как на google maps сделать тоже самое?
Или как сделать, чтобы показывало ближайшие N объектов на карте?
Спасибо!
Число мест задается в SQL запросе параметром LIMIT 0 , 20
Вы можете изменить 20 на любое другое значение.
По использованию в Google Maps была заметка
Добрый день подскажите пожалуйста, я так понимаю этот вариант работает для 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’]?? и для чего он нужен??
Что то запрос не работает.