Интеграция поиска в веб-приложение
В этом разделе представлен пример интеграции поиска на карте в ваше веб-приложение. Интерактивный пример демонстрирует использование основных API поиска 2ГИС:
- Geocoder API для определения адреса по координатам клика по карте;
- Markers API для поиска мест в заданном радиусе и отображения их на карте;
- Places API для получения дополнительной информации о месте на карте;
- Suggest API для отображения подсказок при вводе категории.
Для отображения интерактивной карты используется MapGL JS API.
В примере используются две переменные для API-ключей: API_KEY для доступа к API поиска и MAPGL_KEY для API карт. Если вы используете другую конфигурацию ключей (один ключ для всех сервисов или отдельные ключи для каждого API), отредактируйте использование переменных соответственно.
Основные блоки сценария
Инициализация карты
С помощью MapGL JS API создаётся экземпляр карты с основными параметрами её отображения и ключом доступа к Map Tiles API. Также задаётся обработчик кликов по карте, который запускает основной сценарий поиска.
function initMap() {
map = new mapgl.Map('map', {
center: DEFAULT_CENTER, // Координаты центра карты (Москва)
zoom: DEFAULT_ZOOM, // Начальный масштаб
key: MAPGL_KEY, // Ключ доступа к Map Tiles API
enableTrackResize: true,
zoomControl: 'centerRight',
});
// Клик по карте для исследования области
map.on('click', handleMapClick);
}
Когда пользователь кликает по карте, вокруг места клика рисуется круг (радиус поиска).
function drawSearchRadius(lngLat) {
// Удалить ранее созданный круг
if (searchCircle) {
searchCircle.destroy();
}
searchCircle = new mapgl.Circle(map, {
coordinates: lngLat, // Центр круга — координаты клика
radius: searchRadius, // Радиус круга — радиус поиска
color: '#0066cc22',
strokeColor: '#0066cc',
strokeWidth: 2,
interactive: false,
});
}
Обратное геокодирование
Формируется запрос к Geocoder API с указанием необходимых параметров поиска и ключа доступа.
async function reverseGeocode(lngLat) {
const [lon, lat] = lngLat;
const url = new URL('https://catalog.api.2gis.com/3.0/items/geocode');
url.searchParams.set('lat', lat);
url.searchParams.set('lon', lon);
url.searchParams.set('key', API_KEY); // Ключ доступа к Geocoder API
url.searchParams.set('fields', 'items.point,items.address'); // Получить в ответе координаты и адрес
url.searchParams.set('locale', 'ru_RU');
try {
const response = await fetch(url);
const data = await response.json();
const result = checkApiResponse(data, 'Geocoder API');
if (result?.items?.length) {
const item = result.items[0];
const address =
item.full_name || item.address_name || item.name || 'Address not found';
addressDisplay.textContent = address;
} else {
addressDisplay.textContent = 'Address not found';
}
} catch (error) {
console.error('Geocoder API error:', error);
showError(`Geocoding error: ${error.message}`);
addressDisplay.textContent = 'Failed to get address';
}
}
Запрос к Geocoder API выполняется при каждом клике по карте, внутри обработчика handleMapClick().
async function handleMapClick(e) {
const { lngLat } = e; // Координаты клика по карте
currentLocation = lngLat;
clearMarkers();
drawSearchRadius(lngLat);
// 1. Geocoder API - получить адрес для выбранной точки
await reverseGeocode(lngLat);
if (selectedCategory) {
await loadNearbyMarkers(lngLat, selectedCategory);
}
}
Полученный в ответе адрес отображается в виджете на карте.
Поиск и отображение мест на карте
Формируется запрос к Markers API с указанием параметров поиска и ключа доступа. Также задаётся ограничение на количество результатов в ответе (до 10).
async function loadNearbyMarkers(lngLat, query) {
if (!query || !query.trim()) {
showError('No category selected');
return;
}
const [lon, lat] = lngLat;
const url = new URL('https://catalog.api.2gis.com/3.0/markers');
url.searchParams.set('q', query); // Строка запроса (категория мест)
url.searchParams.set('point', `${lon},${lat}`); // Центр поиска (координаты клика по карте)
url.searchParams.set('radius', searchRadius); // Радиус поиска в метрах
url.searchParams.set('sort', 'distance'); // Отсортировать результаты по расстоянию от центра поиска
url.searchParams.set('key', API_KEY); // Ключ доступа к Markers API
url.searchParams.set('locale', 'ru_RU');
url.searchParams.set('limit', 10); // До 10 результатов в ответе
try {
const response = await fetch(url);
const data = await response.json();
const result = checkApiResponse(data, 'Markers API');
if (result?.items?.length) {
renderMarkers(result.items);
updateStats(result.items.length, result.total);
} else {
updateStats(0, 0);
showError(`Nothing found within ${searchRadius}m`);
}
} catch (error) {
console.error('Markers API error:', error);
showError(`Failed to load markers: ${error.message}`);
updateStats(0, 0);
}
}
Запрос к Markers API выполняется в следующих случаях:
-
Внутри вызова функции
handleMapClick()— после клика по карте, если категория уже выбрана:// 2. Markers API - загрузить ближайшие места, если категория выбрана
if (selectedCategory) {
await loadNearbyMarkers(lngLat, selectedCategory);
} -
Внутри функции
applyFilter()— когда пользователь подтвердил категорию (выбрал из списка подсказок или нажал Enter), и точка на карте уже выбрана:function applyFilter(category) {
selectedCategory = category;
if (currentLocation) {
loadNearbyMarkers(currentLocation, category);
}
} -
Внутри обработчика слайдера радиуса
radiusSlider.addEventListener()— когда радиус поиска изменился, а точка и категория уже заданы:if (currentLocation) {
drawSearchRadius(currentLocation);
if (selectedCategory) {
loadNearbyMarkers(currentLocation, selectedCategory);
}
}
Полученные элементы отрисовываются на карте с помощью объекта mapgl.Marker. На каждый маркер добавляется обработчик клика, который запускает запрос к Places API для получения подробной информации о месте.
const marker = new mapgl.Marker(map, {
coordinates: [item.lon, item.lat],
});
marker.on('click', () => {
fetchPlaceDetails(item.id, [item.lon, item.lat]);
});
Получение данных о месте
Формируется запрос к Places API с указанием идентификатора места, необходимых полей для получения в ответе и ключа доступа.
async function fetchPlaceDetails(placeId, coordinates) {
// Показать состояние загрузки
showLoadingPopup(coordinates);
const url = new URL('https://catalog.api.2gis.com/3.0/items/byid');
url.searchParams.set('id', placeId); // Идентификатор места из Markers API
url.searchParams.set('key', API_KEY); // Ключ доступа к Places API
url.searchParams.set(
'fields',
'items.point,items.address,items.rubrics,items.schedule',
); // Какие данные о месте получить в ответе
url.searchParams.set('locale', 'ru_RU');
try {
const response = await fetch(url);
const data = await response.json();
const result = checkApiResponse(data, 'Places API');
if (result?.items?.length) {
const item = result.items[0];
showPlacePopup(item, coordinates);
} else {
showError('Place information not found');
closePopup();
}
} catch (error) {
console.error('Places API error:', error);
showError(`Failed to load details: ${error.message}`);
closePopup();
}
}
Запрос отправляется при клике по одному из маркеров, который был отрисован на карте после получения данных из Markers API.
marker.on('click', () => {
fetchPlaceDetails(item.id, [item.lon, item.lat]);
});
Полученные данные используются в функции showPlacePopup() для отображения информации о месте в всплывающей подсказке на карте с помощью mapgl.HtmlMarker.
function formatTodayWorkingHours(schedule) {
if (!schedule) return '';
const days = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];
const todayKey = days[new Date().getDay()];
const today = schedule[todayKey];
if (!today) return '';
const ranges = (today.working_hours || [])
.map((h) => {
if (!h) return '';
const from = h.from || '';
const to = h.to || '';
if (from && to) return `${from}–${to}`;
return from || to || '';
})
.filter(Boolean);
if (ranges.length) {
return `Время работы сегодня: ${ranges.join(', ')}`;
}
if (today.day_off || today.is_day_off) {
return 'Время работы сегодня: выходной';
}
return '';
}
function showPlacePopup(item, coordinates) {
closePopup();
const name = item.name || 'Unnamed';
const address = item.address_name || item.address?.name || item.full_address_name || '';
const category = item.rubrics?.[0]?.name || '';
const workingHoursToday = formatTodayWorkingHours(item.schedule);
const content = `
<div class="popup">
<div class="popup__title">${escapeHtml(name)}</div>
${address ? `<div class="popup__address">${escapeHtml(address)}</div>` : ''}
${workingHoursToday ? `<div class="popup__address">${escapeHtml(workingHoursToday)}</div>` : ''}
${category ? `<div class="popup__category">${escapeHtml(category)}</div>` : ''}
</div>
`;
currentPopup = new mapgl.HtmlMarker(map, {
coordinates,
html: content,
anchor: [0.5, 1],
});
}
Отображение подсказок при вводе категории
Формируется запрос к Suggest API с указанием текста запроса, локации, типа подсказок и ключа доступа.
async function fetchSuggestions(query) {
if (!query.trim()) {
hideSuggestions();
return;
}
// Использовать текущую локацию или центр карты
const [lon, lat] = currentLocation || map.getCenter();
const url = new URL('https://catalog.api.2gis.com/3.0/suggests');
url.searchParams.set('q', query); // Текст, который вводит пользователь
url.searchParams.set('key', API_KEY); // Ключ доступа к Suggest API
url.searchParams.set('location', `${lon},${lat}`); // Текущая локация или центр карты
url.searchParams.set('locale', 'ru_RU');
url.searchParams.set('type', 'rubric'); // Предлагать подсказки только по категориям
try {
const response = await fetch(url);
const data = await response.json();
const result = checkApiResponse(data, 'Suggest API');
if (result?.items?.length) {
renderSuggestions(result.items);
} else {
hideSuggestions();
}
} catch (error) {
console.error('Suggest API error:', error);
showError(`Suggestions error: ${error.message}`);
hideSuggestions();
}
}
Запрос отправляется, когда пользователь начинает вводить текст в поле поиска категории. Запрос отправляется с задержкой 300 мс, чтобы не перегружать API при быстром вводе.
const handleInput = debounce((e) => {
fetchSuggestions(e.target.value);
}, 300);
searchInput.addEventListener('input', handleInput);
Полученные подсказки отображаются в виде выпадающего списка. При клике по подсказке её название устанавливается в поле ввода, список скрывается, и запускается поиск маркеров по выбранной категории.
suggestionsContainer.querySelectorAll('.suggestion-item').forEach((el) => {
el.addEventListener('click', () => {
const name = el.dataset.name;
searchInput.value = name;
hideSuggestions();
applyFilter(name);
});
});