Интерактивные веб-карты редко используют только статические данные. Основной объём информации поступает динамически: точки интереса, слои с сервера, результаты фильтрации, обновляемые геоданные.
Асинхронная загрузка (AJAX) обеспечивает подгрузку данных без перезагрузки страницы, позволяя обновлять карту по событиям интерфейса, изменению масштаба или перемещению области просмотра.
Под AJAX в современных приложениях обычно понимается набор технологий:
fetch API как основной инструмент HTTP-запросовXMLHttpRequest как устаревающий, но поддерживаемый
механизмВ контексте картографических приложений ключевым форматом выступает GeoJSON.
GeoJSON представляет географические объекты в JSON-структуре:
Point — точечные объектыLineString — линииPolygon — полигоныFeatureCollection — набор объектовСтруктура Feature:
{
"type": "Feature",
"geometry": {
"type": "Point",
"coordinates": [76.8865, 43.2389]
},
"properties": {
"name": "Объект",
"category": "poi"
}
}
AJAX в связке с GeoJSON позволяет динамически формировать слои без перерисовки всей карты.
Современный подход к получению данных строится на
fetch.
fetch('/api/points')
.then(response => response.json())
.then(data => {
L.geoJSON(data).addTo(map);
})
.catch(error => {
console.error('Ошибка загрузки данных:', error);
});
Ключевой элемент — L.geoJSON, который автоматически
интерпретирует структуру и создаёт соответствующие слои.
GeoJSON-слой в Leaflet поддерживает функцию стилизации:
fetch('/api/zones')
.then(r => r.json())
.then(data => {
L.geoJSON(data, {
style: feature => {
return {
color: feature.properties.color || '#3388ff',
weight: 2,
fillOpacity: 0.4
};
}
}).addTo(map);
});
Для точечных объектов используется pointToLayer:
L.geoJSON(data, {
pointToLayer: (feature, latlng) => {
return L.circleMarker(latlng, {
radius: 6,
color: '#ff0000'
});
}
});
Несмотря на устаревание, XHR встречается в legacy-системах.
const xhr = new XMLHttpRequest();
xhr.open('GET', '/api/points');
xhr.onl oad = function () {
if (xhr.status === 200) {
const data = JSON.parse(xhr.responseText);
L.geoJSON(data).addTo(map);
}
};
xhr.oner ror = function () {
console.error('Ошибка запроса');
};
xhr.send();
XHR важен в окружениях без поддержки fetch.
Карта часто инициирует загрузку при изменении viewport.
map.on('moveend', function () {
const bounds = map.getBounds();
fetch(`/api/points?bbox=${bounds.toBBoxString()}`)
.then(r => r.json())
.then(data => {
L.geoJSON(data).addTo(map);
});
});
Использование bbox позволяет ограничить объём данных
текущей областью.
При повторных запросах требуется управление слоями:
let layerGroup = L.layerGroup().addTo(map);
function updateData() {
fetch('/api/points')
.then(r => r.json())
.then(data => {
layerGroup.clearLayers();
L.geoJSON(data).addTo(layerGroup);
});
}
L.layerGroup обеспечивает централизованное управление
объектами.
При частых перемещениях карты возникает проблема гонки запросов.
let controller;
function loadData() {
if (controller) controller.abort();
controller = new AbortController();
fetch('/api/points', { signal: controller.signal })
.then(r => r.json())
.then(data => {
L.geoJSON(data).addTo(map);
})
.catch(err => {
if (err.name !== 'AbortError') {
console.error(err);
}
});
}
Механизм предотвращает наложение устаревших ответов.
При работе с картографическими слоями критично уменьшение нагрузки:
Подходы:
bboxПример дебаунса:
function debounce(fn, delay) {
let timer;
return (...args) => {
clearTimeout(timer);
timer = setTimeout(() => fn(...args), delay);
};
}
map.on('move', debounce(loadData, 300));
Серверные параметры позволяют уменьшить объём GeoJSON:
function loadFiltered(category) {
fetch(`/api/points?category=${category}`)
.then(r => r.json())
.then(data => {
L.geoJSON(data).addTo(map);
});
}
Фильтрация может включать:
Сетевые ошибки требуют явной обработки:
fetch('/api/points')
.then(r => {
if (!r.ok) throw new Error('HTTP error');
return r.json();
})
.then(data => {
L.geoJSON(data).addTo(map);
})
.catch(err => {
console.error('Ошибка получения данных:', err);
});
Данные могут поступать из различных источников:
Пример подключения внешнего API:
fetch('https://example.com/geodata')
.then(r => r.json())
.then(data => {
L.geoJSON(data, {
onEachFeature: (feature, layer) => {
layer.bindPopup(feature.properties.name);
}
}).addTo(map);
});
При увеличении объёма данных ключевым фактором становится контроль рендеринга:
Leaflet.markercluster)Пример кластеризации:
const markers = L.markerClusterGroup();
fetch('/api/points')
.then(r => r.json())
.then(data => {
L.geoJSON(data, {
onEachFeature: (feature, layer) => {
markers.addLayer(layer);
}
});
map.addLayer(markers);
});
Вместо полной перезагрузки слоя возможна инкрементальная модель:
function addNewPoints() {
fetch('/api/points?since=timestamp')
.then(r => r.json())
.then(data => {
L.geoJSON(data).addTo(map);
});
}
Такой подход снижает нагрузку и ускоряет обновление интерфейса.
Несколько источников данных могут загружаться одновременно:
Promise.all([
fetch('/api/roads').then(r => r.json()),
fetch('/api/buildings').then(r => r.json())
]).then(([roads, buildings]) => {
L.geoJSON(roads).addTo(map);
L.geoJSON(buildings).addTo(map);
});
Параллельная загрузка уменьшает общее время ожидания.
При работе с динамическими слоями требуется контроль:
Пример хранения состояния:
const state = {
layers: {},
lastUpdate: null
};
Автоматическая синхронизация:
setInterval(() => {
fetch('/api/updates')
.then(r => r.json())
.then(data => {
L.geoJSON(data).addTo(map);
});
}, 5000);
Подобная схема используется в мониторинговых системах и трекинге объектов.