Работа с GeoJSON в MapLibre GL JS строится вокруг концепции
источников данных (sources) и слоёв (layers).
GeoJSON выступает универсальным форматом, позволяющим описывать
геометрические объекты и их свойства, а библиотека обеспечивает их
рендеринг на WebGL-карте через декларативные стили.
Ключевой элемент интеграции — источник типа geojson,
который может быть как статически заданным объектом, так и динамически
загружаемым через API.
Загрузка данных из внешнего API чаще всего реализуется через
комбинацию fetch и метода setData, либо через
инициализацию источника с URL.
map.on('load', () => {
map.addSource('points-source', {
type: 'geojson',
data: 'https://example.com/api/points'
});
map.addLayer({
id: 'points-layer',
type: 'circle',
source: 'points-source',
paint: {
'circle-radius': 6,
'circle-color': '#2E86AB'
}
});
});
В этом варианте MapLibre самостоятельно выполняет HTTP-запрос к указанному URL. Однако такой подход ограничен отсутствием контроля над процессом загрузки и обработки данных.
Более гибкая модель предполагает явную загрузку данных приложением с последующей передачей в источник:
map.on('load', async () => {
const response = await fetch('https://example.com/api/points');
const geojson = await response.json();
map.addSource('points-source', {
type: 'geojson',
data: geojson
});
map.addLayer({
id: 'points-layer',
type: 'circle',
source: 'points-source',
paint: {
'circle-radius': 5,
'circle-color': '#E74C3C'
}
});
});
Такой подход позволяет внедрять предварительную обработку данных, фильтрацию и нормализацию структуры GeoJSON до передачи в MapLibre.
Одним из ключевых механизмов работы с динамическими API является
метод setData, позволяющий обновлять содержимое источника
без удаления слоя.
async function updateData() {
const response = await fetch('https://example.com/api/points?ts=' + Date.now());
const geojson = await response.json();
const source = map.getSource('points-source');
source.setData(geojson);
}
Этот механизм критически важен для:
Корректный серверный ответ должен строго соответствовать спецификации GeoJSON:
{
"type": "FeatureCollection",
"features": [
{
"type": "Feature",
"geometry": {
"type": "Point",
"coordinates": [73.367, 49.806]
},
"properties": {
"id": 1,
"name": "Object A"
}
}
]
}
Нарушение структуры приводит к тихим ошибкам рендеринга без явных сообщений в UI.
Пример минимального API на Node.js (Express), возвращающего GeoJSON:
import express from 'express';
const app = express();
app.get('/api/points', (req, res) => {
res.json({
type: 'FeatureCollection',
features: [
{
type: 'Feature',
geometry: {
type: 'Point',
coordinates: [73.367, 49.806]
},
properties: {
id: 1,
label: 'Node A'
}
}
]
});
});
app.listen(3000);
Важным аспектом является установка корректных заголовков:
res.setHeader('Access-Control-Allow-Origin', '*');
res.setHeader('Content-Type', 'application/json');
Без корректного CORS браузер блокирует загрузку источника.
Типичный сценарий — загрузка объектов только в пределах текущего viewport. Это снижает нагрузку и ускоряет рендеринг.
map.on('moveend', async () => {
const bounds = map.getBounds();
const url = new URL('https://example.com/api/points');
url.searchParams.append('minLng', bounds.getWest());
url.searchParams.append('minLat', bounds.getSouth());
url.searchParams.append('maxLng', bounds.getEast());
url.searchParams.append('maxLat', bounds.getNorth());
const response = await fetch(url);
const geojson = await response.json();
map.getSource('points-source').setData(geojson);
});
Такая модель соответствует подходу “bounding box queries”, часто используемому в геосервисах.
При частых перемещениях карты необходимо избегать дублирующих запросов. Используются стратегии:
moveПример debounce:
function debounce(fn, delay) {
let timer;
return (...args) => {
clearTimeout(timer);
timer = setTimeout(() => fn(...args), delay);
};
}
map.on('move', debounce(updateData, 300));
API может возвращать некорректные данные или быть временно недоступным. В таких случаях необходимо предотвращать разрушение источника:
async function safeUpdate() {
try {
const response = await fetch('https://example.com/api/points');
if (!response.ok) {
return;
}
const geojson = await response.json();
const source = map.getSource('points-source');
if (source) {
source.setData(geojson);
}
} catch (e) {
console.error('GeoJSON load failed', e);
}
}
API часто возвращает данные в нестандартной структуре, требующей преобразования:
function normalize(data) {
return {
type: 'FeatureCollection',
features: data.items.map(item => ({
type: 'Feature',
geometry: {
type: 'Point',
coordinates: [item.lon, item.lat]
},
properties: {
id: item.id,
title: item.title
}
}))
};
}
При работе с тысячами объектов используется встроенная кластеризация MapLibre:
map.addSource('points-source', {
type: 'geojson',
data: 'https://example.com/api/points',
cluster: true,
clusterMaxZoom: 14,
clusterRadius: 50
});
Дополнительно создаются слои для кластеров:
map.addLayer({
id: 'clusters',
type: 'circle',
source: 'points-source',
filter: ['has', 'point_count'],
paint: {
'circle-radius': 10,
'circle-color': '#F39C12'
}
});
При работе с API-источниками критически важны оптимизации:
Для сценариев трекинга (например, движение транспорта) используется частичное обновление данных:
setInterval(async () => {
const response = await fetch('https://example.com/api/live');
const geojson = await response.json();
map.getSource('points-source').setData(geojson);
}, 2000);
При высокой частоте обновлений важно избегать полной перерисовки сложных слоёв и использовать минимальный набор объектов.
Если данные защищены, запросы дополняются токенами:
const response = await fetch('https://example.com/api/points', {
headers: {
'Authorization': 'Bearer TOKEN'
}
});
При этом необходимо учитывать, что MapLibre не управляет авторизацией
при использовании URL-источника напрямую, поэтому предпочтителен ручной
fetch.
Эффективная архитектура строится вокруг принципа:
MapLibre GL JS выступает исключительно как слой визуализации, не выполняя бизнес-логику фильтрации или трансформации сложных данных, кроме базовой поддержки GeoJSON.