Работа с результатами пространственных данных в MapLibre GL JS строится вокруг связки источников данных (sources), слоёв (layers) и механизмов динамического обновления состояния карты. Архитектура ориентирована на декларативное описание визуализации, при котором данные отделены от их отображения, а рендеринг выполняется через GPU.
Любое отображение результатов начинается с добавления источника данных. В большинстве сценариев используется формат GeoJSON, позволяющий описывать геометрии и связанные свойства объектов.
map.addSource('results', {
type: 'geojson',
data: {
type: 'FeatureCollection',
features: []
}
});
Источник выступает контейнером, который может динамически обновляться без пересоздания слоя. При изменении данных применяется метод:
map.getSource('results').setData(newGeojson);
Такой подход позволяет отделить бизнес-логику получения результатов (поиск, фильтрация, агрегация) от визуализации.
Для отображения результатов поиска или аналитики чаще всего
используется слой circle или symbol.
map.addLayer({
id: 'results-points',
type: 'circle',
source: 'results',
paint: {
'circle-radius': 6,
'circle-color': '#3b82f6',
'circle-stroke-width': 1,
'circle-stroke-color': '#ffffff'
}
});
Каждый объект GeoJSON преобразуется в отдельный визуальный элемент. При необходимости можно использовать стилизацию на основе свойств:
'circle-color': [
'match',
['get', 'category'],
'restaurant', '#ef4444',
'hotel', '#3b82f6',
'#10b981'
]
Такой механизм позволяет визуально разделять типы результатов без изменения структуры данных.
При большом объёме данных используется кластеризация на уровне источника:
map.addSource('results', {
type: 'geojson',
data: geojson,
cluster: true,
clusterMaxZoom: 14,
clusterRadius: 50
});
Отображение кластеров реализуется через отдельные слои:
map.addLayer({
id: 'clusters',
type: 'circle',
source: 'results',
filter: ['has', 'point_count'],
paint: {
'circle-color': '#2563eb',
'circle-radius': 18
}
});
map.addLayer({
id: 'cluster-count',
type: 'symbol',
source: 'results',
filter: ['has', 'point_count'],
layout: {
'text-field': '{point_count_abbreviated}',
'text-size': 12
}
});
Отдельный слой используется для отображения единичных объектов:
map.addLayer({
id: 'unclustered-point',
type: 'circle',
source: 'results',
filter: ['!', ['has', 'point_count']],
paint: {
'circle-color': '#f59e0b',
'circle-radius': 6
}
});
Результаты пользовательских запросов обычно формируются динамически. После получения данных выполняется преобразование в GeoJSON и обновление источника.
function updateResults(items) {
const geojson = {
type: 'FeatureCollection',
features: items.map(item => ({
type: 'Feature',
geometry: {
type: 'Point',
coordinates: [item.lon, item.lat]
},
properties: {
id: item.id,
title: item.title,
type: item.type
}
}))
};
map.getSource('results').setData(geojson);
}
Фильтрация может выполняться как на уровне данных, так и через выражения слоя:
map.setFilter('results-points', [
'==',
['get', 'type'],
'restaurant'
]);
Такой подход уменьшает необходимость перегенерации GeoJSON при каждом изменении UI-фильтров.
Для обработки кликов используется механизм событий карты:
map.on('click', 'results-points', (e) => {
const feature = e.features[0];
});
Полученные свойства объекта позволяют формировать динамические интерфейсы, такие как всплывающие окна:
new maplibregl.Popup()
.setLngLat(feature.geometry.coordinates)
.setHTML(`<div>${feature.properties.title}</div>`)
.addTo(map);
При необходимости можно реализовать выделение активного объекта через изменение состояния слоя:
map.setPaintProperty('results-points', 'circle-radius', [
'case',
['==', ['get', 'id'], activeId],
10,
6
]);
После обновления данных часто выполняется подгонка области видимости карты:
const bounds = new maplibregl.LngLatBounds();
geojson.features.forEach(f => {
bounds.extend(f.geometry.coordinates);
});
map.fitBounds(bounds, {
padding: 50
});
Такой механизм обеспечивает визуальную фокусировку на текущем наборе результатов.
При использовании векторных тайлов результаты могут поступать не в
формате GeoJSON, а как тайловые слои сервера. В этом случае фильтрация и
стилизация выполняются через source-layer:
map.addLayer({
id: 'vector-results',
type: 'fill',
source: 'tiles',
'source-layer': 'results_layer',
paint: {
'fill-color': '#60a5fa',
'fill-opacity': 0.5
}
});
Такой подход снижает нагрузку на клиентскую сторону и переносит обработку на сервер генерации тайлов.
Состояние визуализации может изменяться без перезагрузки источника. Используются методы:
setData — полная замена данныхsetFilter — фильтрация слояsetPaintProperty — изменение визуального стиляsetLayoutProperty — изменение структуры
отображенияПример переключения режима отображения:
function toggleView(mode) {
if (mode === 'heatmap') {
map.setLayoutProperty('results-points', 'visibility', 'none');
} else {
map.setLayoutProperty('results-points', 'visibility', 'visible');
}
}
Выражения MapLibre позволяют создавать контекстно-зависимую визуализацию результатов:
'circle-opacity': [
'interpolate',
['linear'],
['zoom'],
5, 0.2,
10, 0.8
]
Также возможно комбинирование свойств объекта:
'circle-stroke-width': [
'+',
['get', 'importance'],
1
]
Такая модель обеспечивает гибкую адаптацию визуализации под масштаб и данные.
При работе с тысячами объектов критическим становится снижение нагрузки на рендеринг:
setDataДополнительно применяется фильтрация по зуму:
'filter': ['>=', ['zoom'], 8]
В интерфейсах с панелями поиска и фильтрации карта выступает как визуальный слой состояния. Любое изменение UI должно транслироваться в обновление источника данных или фильтров слоёв.
Пример синхронизации:
searchResults.subscribe(items => {
map.getSource('results').setData(toGeoJSON(items));
});
Такой подход обеспечивает реактивное обновление визуализации без прямой привязки к DOM.
Одновременное использование разных слоёв позволяет создавать многослойную визуализацию:
circle — точки результатовsymbol — подписиheatmap — плотность распределенияfill — области покрытияПример heatmap-слоя:
map.addLayer({
id: 'heatmap',
type: 'heatmap',
source: 'results',
paint: {
'heatmap-intensity': 1,
'heatmap-radius': 20
}
});
Комбинация слоёв позволяет одновременно отображать детали и агрегированную картину распределения данных.