При создании карты с использованием 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’]?? и для чего он нужен??
Что то запрос не работает.