Работа с REST API в контексте геоинформационных веб-приложений
строится вокруг принципа потоковой загрузки пространственных данных и их
преобразования в источники слоёв. В OpenLayers REST-запросы не являются
отдельным уровнем абстракции — они интегрируются в источники
(source), которые управляют жизненным циклом данных, их
кешированием, повторными запросами и привязкой к текущему экстенту
карты.
Основные типы REST-взаимодействий в OpenLayers:
Базовая модель REST-загрузки векторных данных строится вокруг
ol/source/Vector, который поддерживает динамический
загрузчик через loader.
Типовой сценарий: сервер отдаёт GeoJSON по HTTP-запросу.
import VectorSource fr om 'ol/source/Vector.js';
import GeoJSON fr om 'ol/format/GeoJSON.js';
const vectorSource = new VectorSource({
format: new GeoJSON(),
loader: function (extent, resolution, projection) {
const url = `/api/features?bbox=${extent.join(',')}`;
fetch(url)
.then(response => response.json())
.then(data => {
const features = new GeoJSON().readFeatures(data, {
featureProjection: projection
});
vectorSource.addFeatures(features);
});
}
});
Ключевой момент: extent передаётся автоматически и
позволяет реализовать серверную фильтрацию по bounding box, что критично
для производительности.
Практически все геосервисы используют фильтрацию по границам:
bbox=minx,miny,maxx,maxygeometry=intersectslayer, type, categoryПример запроса:
const url = new URL('/api/objects', window.location.origin);
url.searchParams.set('bbox', extent.join(','));
url.searchParams.set('lim it', 500);
url.searchParams.set('layer', 'roads');
На стороне сервера это позволяет:
loader в OpenLayers может управлять сложной логикой
запросов, включая отмену и повтор.
const controller = new AbortController();
const source = new VectorSource({
loader: (extent) => {
controller.abort();
const localController = new AbortController();
fetch(`/api/data?bbox=${extent.join(',')}`, {
signal: localController.signal
})
.then(r => r.json())
.then(json => {
const features = new GeoJSON().readFeatures(json);
source.addFeatures(features);
});
controller.signal = localController.signal;
}
});
Такая схема важна при:
REST-сервисы часто возвращают данные порциями:
Пример с пагинацией:
let page = 0;
function loadNext(extent) {
fetch(`/api/features?bbox=${extent.join(',')}&page=${page}`)
.then(r => r.json())
.then(data => {
source.addFeatures(new GeoJSON().readFeatures(data));
page += 1;
});
}
При геозапросах пагинация часто комбинируется с bbox-фильтрацией, что требует серверной оптимизации индексов.
OGC WFS (Web Feature Service) представляет REST-подобный интерфейс для пространственных запросов.
Пример запроса:
/geoserver/wfs?
service=WFS&
version=2.0.0&
request=GetFeature&
typeName=roads&
outputFormat=application/json&
bbox=...
В OpenLayers это часто оборачивается через
VectorSource:
const source = new VectorSource({
format: new GeoJSON(),
url: (extent) => {
return `/geoserver/wfs?service=WFS&version=2.0.0&request=GetFeature&` +
`typeName=roads&outputFormat=application/json&bbox=${extent.join(',')}`;
}
});
REST не ограничивается GeoJSON. Тайловые серверы используют шаблоны URL:
import TileLayer from 'ol/layer/Tile.js';
import XYZ from 'ol/source/XYZ.js';
const layer = new TileLayer({
source: new XYZ({
url: '/tiles/{z}/{x}/{y}.png'
})
});
Здесь REST-структура выражается через path parameters:
{z} — zoom{x}, {y} — координаты тайлаREST-сервисы часто возвращают данные в разных CRS (Coordinate Reference System). OpenLayers требует согласованного преобразования.
import {transform} from 'ol/proj.js';
const wgs84 = 'EPSG:4326';
const webMercator = 'EPSG:3857';
const coord = transform([71.4304, 51.1282], wgs84, webMercator);
При загрузке REST-данных важно учитывать:
featureProjectionМногие геосервисы защищены токенами:
fetch('/api/features', {
headers: {
'Authorization': 'Bearer TOKEN_VALUE'
}
});
В OpenLayers это интегрируется через loader:
const source = new VectorSource({
loader: (extent) => {
fetch(`/api/data?bbox=${extent.join(',')}`, {
headers: {
'Authorization': 'Bearer TOKEN'
}
})
.then(r => r.json())
.then(json => source.addFeatures(new GeoJSON().readFeatures(json)));
}
});
Типовые схемы:
При работе с браузерным клиентом REST API обязан поддерживать CORS:
Access-Control-Allow-Origin: *
Access-Control-Allow-Headers: Authorization
Отсутствие корректных заголовков приводит к блокировке загрузки данных даже при корректном URL.
Геоданные редко изменяются в реальном времени, поэтому применяются стратегии кеширования:
Cache-Control: max-age=3600
ETag: "abc123"
const source = new VectorSource({
cacheSize: 200
});
const cache = new Map();
function getKey(extent) {
return extent.join(',');
}
REST-запросы часто привязаны к событию изменения карты:
map.on('moveend', () => {
const extent = map.getView().calculateExtent();
vectorSource.clear();
loadData(extent);
});
Оптимизации:
Типовые сценарии:
fetch(url)
.then(r => {
if (!r.ok) throw new Error(r.status);
return r.json();
})
.catch(err => {
console.error('REST error:', err);
});
Ключевые факторы:
limit)REST-запросы становятся узким местом при:
Современный подход — замена GeoJSON на vector tiles:
import VectorTileLayer from 'ol/layer/VectorTile.js';
import VectorTileSource from 'ol/source/VectorTile.js';
const layer = new VectorTileLayer({
source: new VectorTileSource({
url: '/tiles/{z}/{x}/{y}.pbf'
})
});
Преимущества REST-архитектуры:
REST-запросы часто динамически формируются на основе UI-фильтров:
function buildUrl(extent, filters) {
const url = new URL('/api/features', window.location.origin);
url.searchParams.set('bbox', extent.join(','));
url.searchParams.set('type', filters.type);
url.searchParams.set('year', filters.year);
return url;
}
Такая модель позволяет:
Один слой может агрегировать данные из нескольких API:
Promise.all([
fetch('/api/roads'),
fetch('/api/buildings')
])
.then(([r1, r2]) => Promise.all([r1.json(), r2.json()]))
.then(([roads, buildings]) => {
source.addFeatures([...roads, ...buildings]);
});
Такая архитектура используется при:
REST API часто предоставляются крупными платформами:
Каждый источник требует:
REST-сервер может отдавать упрощённые или расширенные модели:
{
"type": "Feature",
"geometry": { ... },
"properties": {
"id": 1,
"name": "Road A",
"speed_limit": 60
}
}
На клиенте OpenLayers преобразует это через
ol/format/GeoJSON, сохраняя свойства без изменений, но
нормализуя геометрию под внутренний формат рендера.