Перейти к основному содержимому

Интеграция поиска в веб-приложение

В этом разделе представлен пример интеграции поиска на карте в ваше веб-приложение. Интерактивный пример демонстрирует использование основных API поиска 2ГИС:

Для отображения интерактивной карты используется 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);
});
});