Визуализация результатов в MapLibre GL JS строится вокруг концепции источников данных (sources) и слоёв (layers), где каждый результат геообработки или запроса превращается в объект геоданных, способный быть отрисованным на карте в различных визуальных представлениях. Архитектура рендеринга предполагает строгую привязку данных к стилю, что обеспечивает высокую производительность при работе с большими объёмами пространственной информации.
Любые результаты пространственного анализа или запроса приводятся к формату GeoJSON. Это универсальное представление, которое поддерживает:
Структура результата определяет дальнейшую стратегию визуализации. Например, одиночные точки часто отображаются как маркеры, линии — как маршруты или границы, полигоны — как области или зоны влияния.
Ключевым моментом является то, что MapLibre GL JS не работает напрямую с “результатами запросов” как абстракцией. Каждый результат должен быть преобразован в источник данных:
map.addSource('results', {
type: 'geojson',
data: geojsonData
});
После добавления источника данные становятся доступными для визуального отображения через слои.
Слои определяют, как именно геометрия будет визуализирована. Для результатов чаще всего используются три типа слоёв:
Простейшее отображение точечных результатов:
map.addLayer({
id: 'results-points',
type: 'circle',
source: 'results',
paint: {
'circle-radius': 6,
'circle-color': '#ff5722',
'circle-stroke-width': 2,
'circle-stroke-color': '#ffffff'
}
});
Для линейных результатов:
map.addLayer({
id: 'results-lines',
type: 'line',
source: 'results',
paint: {
'line-color': '#3f51b5',
'line-width': 3
}
});
Для полигональных результатов:
map.addLayer({
id: 'results-polygons',
type: 'fill',
source: 'results',
paint: {
'fill-color': '#4caf50',
'fill-opacity': 0.4
}
});
Такой подход обеспечивает строгую визуальную сегрегацию типов данных и позволяет комбинировать отображение различных результатов в единой сцене.
Результаты часто изменяются в ответ на пользовательские действия или новые вычисления. Вместо пересоздания слоя используется обновление источника данных:
const source = map.getSource('results');
source.setData(newGeojsonData);
Такой механизм обеспечивает минимальные накладные расходы на перерисовку и сохраняет состояние слоёв.
Динамическое обновление особенно важно при:
При отображении множества результатов возникает необходимость их различения. MapLibre GL JS предоставляет несколько стратегий стилизации на основе свойств объектов.
map.addLayer({
id: 'results-conditional',
type: 'circle',
source: 'results',
paint: {
'circle-radius': 6,
'circle-color': [
'match',
['get', 'type'],
'hospital', '#e91e63',
'school', '#2196f3',
'park', '#4caf50',
'#9e9e9e'
]
}
});
map.addLayer({
id: 'results-intensity',
type: 'circle',
source: 'results',
paint: {
'circle-radius': [
'interpolate',
['linear'],
['get', 'score'],
0, 4,
10, 10,
20, 18
],
'circle-color': '#ff9800'
}
});
Такой подход позволяет визуализировать числовые показатели прямо в геометрии слоя.
Фильтры применяются на уровне слоя и позволяют управлять видимостью подмножеств данных без изменения источника.
map.setFilter('results-points', [
'==',
['get', 'category'],
'restaurant'
]);
Более сложные выражения позволяют комбинировать условия:
map.setFilter('results-points', [
'all',
['==', ['get', 'active'], true],
['>=', ['get', 'rating'], 4]
]);
Фильтрация выполняется на GPU, что делает её эффективной даже при больших наборах данных.
Для работы с результатами в интерактивном режиме используется feature state. Этот механизм позволяет изменять визуальное состояние отдельных объектов без изменения GeoJSON.
map.setFeatureState(
{ source: 'results', id: featureId },
{ selected: true }
);
Стилизация слоя:
map.addLayer({
id: 'results-highlight',
type: 'circle',
source: 'results',
paint: {
'circle-radius': [
'case',
['boolean', ['feature-state', 'selected'], false],
10,
6
],
'circle-color': [
'case',
['boolean', ['feature-state', 'selected'], false],
'#ff0000',
'#00897b'
]
}
});
Такой механизм отделяет данные от состояния отображения и снижает необходимость пересборки источников.
Часто результаты формируются на основе текущего экстента карты. Для этого используется:
const features = map.queryRenderedFeatures({
layers: ['results-points']
});
Или с ограничением по области:
const bboxFeatures = map.queryRenderedFeatures({
geometry: map.getBounds().toArray()
});
Полученные данные могут быть преобразованы и переотображены как отдельный источник результатов.
При отображении большого количества точечных объектов используется кластеризация:
map.addSource('results', {
type: 'geojson',
data: geojsonData,
cluster: true,
clusterMaxZoom: 14,
clusterRadius: 50
});
Отдельные слои для кластеров и одиночных точек:
map.addLayer({
id: 'clusters',
type: 'circle',
source: 'results',
filter: ['has', 'point_count'],
paint: {
'circle-color': '#1976d2',
'circle-radius': 15
}
});
map.addLayer({
id: 'cluster-count',
type: 'symbol',
source: 'results',
filter: ['has', 'point_count'],
layout: {
'text-field': ['get', 'point_count_abbreviated'],
'text-size': 12
}
});
Кластеризация позволяет сохранить читаемость карты при высокой плотности результатов.
Для непрерывных распределений применяется тепловая карта:
map.addLayer({
id: 'results-heat',
type: 'heatmap',
source: 'results',
paint: {
'heatmap-intensity': 1,
'heatmap-radius': 20,
'heatmap-color': [
'interpolate',
['linear'],
['heatmap-density'],
0, 'rgba(0,0,255,0)',
0.5, 'rgba(0,255,0,0.5)',
1, 'rgba(255,0,0,0.8)'
]
}
});
Heatmap применяется к большим наборам точек, где важно показать общую структуру распределения, а не отдельные элементы.
Информационные всплывающие окна связываются с конкретными объектами слоя:
map.on('click', 'results-points', (e) => {
const coordinates = e.features[0].geometry.coordinates.slice();
const properties = e.features[0].properties;
new maplibregl.Popup()
.setLngLat(coordinates)
.setHTML(`
<div>
<strong>${properties.name}</strong><br/>
${properties.description}
</div>
`)
.addTo(map);
});
Попапы используются как слой представления дополнительных атрибутов результата, не перегружая основное отображение карты.
В реальных сценариях результаты редко ограничиваются одним типом данных. Часто одновременно отображаются:
Согласованность достигается через разделение источников:
map.addSource('search-results', { type: 'geojson', data: searchData });
map.addSource('routes', { type: 'geojson', data: routeData });
map.addSource('zones', { type: 'geojson', data: zoneData });
И соответствующие слои для каждого типа данных. Это позволяет независимо обновлять каждый набор результатов без влияния на остальные.
Порядок добавления слоёв определяет визуальную иерархию результатов. Более важные элементы размещаются выше:
map.addLayer(layerConfig, 'waterway-label');
Контроль порядка критичен при одновременном отображении плотных данных, маршрутов и подписей, так как перекрытие слоёв напрямую влияет на читаемость результатов.
При работе с большими объёмами данных ключевую роль играют:
Особое значение имеет отказ от постоянного пересоздания объектов, так как это приводит к перерасчёту стиля и снижению производительности отрисовки.
MapLibre GL JS позволяет разделять данные и визуальное состояние, что особенно важно для динамических результатов:
Такое разделение обеспечивает устойчивую работу интерфейса даже при частых изменениях отображаемых результатов.