Форматы геоданных
При работе с геоданными вы можете столкнуться с тем, что их невозможно сразу отобразить на карте из-за их разнообразия, больших объёмов и не всегда оптимального представления. Часто необходимо выполнить предварительные расчёты или преобразования, например:
- рассчитать границы расположения геоданных так, чтобы спозиционировать на них карту;
- упростить геометрии, чтобы получить более плавные контуры и увеличить производительность отрисовки;
- преобразовать полигон в точки или наоборот.
Карта 2ГИС сама по себе не содержит функциональности по обработке геоданных. Однако вы можете использовать сторонние библиотеки с открытым исходным кодом, чтобы восполнить этот пробел. Базовым обменным форматом, своеобразным lingua franca, между этими библиотеками и MapGL JS API выступает GeoJSON.
Ниже приведены основные сведения об основных форматах геоданных, о наиболее распространенных проблемах, возникающих при обработке геоданных, и способах их решения.
GeoJSON
Формат GeoJSON (подмножество формата JSON) поддерживается картой напрямую. Геоданные в нём представлены в виде объектов Feature
, каждый из которых содержит геометрию в поле geometry
и, опционально, метаданные в поле properties
.
{
"type": "FeatureCollection",
"features": [
{
"geometry": {
"type": "LineString",
"coordinates": [
[100.0, 0.0],
[101.0, 1.0]
],
},
"properties": {
"title": "A sample line"
}
},
]
}
Подробнее о работе с этим форматом в карте вы можете узнать в разделе GeoJSON.
Формат GeoJSON описывается стандартом RFC 7946.
Так как формат является подмножеством JSON, он не требует каких-либо дополнительных библиотек для кодирования/декодирования.
Well-known text (WKT)
Well-known text представляет векторные геометрии в виде строки. Формат хранит только геометрии, без метаданных.
Примеры геометрий в данном формате:
POINT (30 10)
LINESTRING (30 10, 10 30, 40 40)
POLYGON ((30 10, 40 40, 20 40, 10 20, 30 10))
POLYGON ((35 10, 45 45, 15 40, 10 20, 35 10), (20 30, 35 35, 30 20, 20 30))
В вебе вы можете работать с данным форматом при помощи библиотеки wellknown.
const wktGeometries = [
'POINT (30 10)',
'LINESTRING (30 10, 10 30, 40 40)',
'POLYGON ((40 10, 60 40, 30 40, 20 20, 40 10))',
'POLYGON ((35 10, 45 45, 15 40, 10 20, 35 10), (20 30, 35 35, 30 20, 20 30))',
];
const geojsonGeometries = wktGeometries.map((wkt) => wellknown.parse(wkt));
Формат WKT описан в стандарте OGC Simple Feature Access.
Широко используется в API 2ГИС, например в Routing API и Places API (см. поля geometry
).
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Display Wkt geometries with MapGL</title>
<meta name="description" content="Display Wkt geometries with MapGL" />
<style>
html,
body,
#container {
margin: 0;
width: 100%;
height: 100%;
overflow: hidden;
}
</style>
</head>
<body>
<div id="container"></div>
<script src="https://unpkg.com/@turf/turf@6.5.0/turf.min.js"></script>
<script src="https://mapgl.2gis.com/api/js/v1"></script>
<script>
// Workaround for wellknown module
const module = {};
const wellknownScript = document.createElement('script');
wellknownScript.src = 'https://unpkg.com/wellknown@0.5.0/index.js';
wellknownScript.onload = function () {
const wellknown = module.exports;
const geojson = turf.geometryCollection([
'POINT (30 10)',
'LINESTRING (30 10, 10 30, 40 40)',
'POLYGON ((40 10, 60 40, 30 40, 20 20, 40 10))',
'POLYGON ((35 10, 45 45, 15 40, 10 20, 35 10), (20 30, 35 35, 30 20, 20 30))'
].map(wellknown.parse));
const center = turf.center(geojson);
const map = new mapgl.Map('container', {
center: turf.getCoord(center),
zoom: 3,
key: 'Your API access key',
});
new mapgl.GeoJsonSource(map, {
data: geojson,
attributes: {
foo: 'bar'
}
});
map.on('styleload', () => {
map.addLayer({
id: 'polygons',
type: 'polygon',
filter: ['==', ['sourceAttr', 'foo'], 'bar'],
style: {
color: '#00B2DD50'
}
});
map.addLayer({
id: 'lines',
type: 'line',
filter: ['==', ['sourceAttr', 'foo'], 'bar'],
style: {
color: '#00B2DDA0',
width: 3
}
});
map.addLayer({
id: 'points',
type: 'point',
filter: ['==', ['sourceAttr', 'foo'], 'bar'],
style: {
iconImage: 'ent_i',
iconWidth: 16,
iconPriority: 100
}
});
});
};
document.head.appendChild(wellknownScript);
</script>
</body>
</html>
Shapefile (shp)
Shapefile содержит геоданные в бинарном формате, что позволяет хранить данные компактно. Формат поддерживает хранение как геометрий, так и метаданных.
let source = null;
shapefile
.open('/geodata/mapgl/ne_110m_coastline.shp')
.then(async (source) => {
const features = [];
let result = await source.read();
while (result && !result.done) {
features.push(result.value);
result = await source.read();
}
source = new mapgl.GeoJsonSource(map, {
data: { type: 'FeatureCollection', features },
});
})
.catch((error) => console.error(error.stack));
Формат поддерживается компанией Esri и описан в стандарте ESRI Shapefile Technical Description.
В вебе вы можете работать с данным форматом при помощи библиотеки shapefile.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Display Shp geometries with MapGL</title>
<meta name="description" content="Display Shp geometries with MapGL" />
<style>
html,
body,
#container {
margin: 0;
width: 100%;
height: 100%;
overflow: hidden;
}
button {
position: absolute;
top: 10px;
left: 10px;
z-index: 2;
}
</style>
</head>
<body>
<div id="container"></div>
<script src="https://unpkg.com/shapefile@0.6"></script>
<script src="https://unpkg.com/@turf/turf@6.5.0/turf.min.js"></script>
<script src="https://mapgl.2gis.com/api/js/v1"></script>
<script>
shapefile.open('https://disk.2gis.com/digital-twin/naturalearth/ne_110m_coastline.shp')
.then(async (source) => {
const features = [];
let result = await source.read();
while (result && !result.done) {
features.push(result.value);
result = await source.read();
}
new mapgl.GeoJsonSource(map, {
data: { type: 'FeatureCollection', features },
attributes: { foo: 'bar' }
});
})
.catch(error => console.error(error.stack));
const map = new mapgl.Map('container', {
center: [108.9219, -6.8670],
zoom: 4,
key: 'Your API access key',
});
map.on('styleload', () => {
map.addLayer({
id: 'lines',
type: 'line',
filter: ['==', ['sourceAttr', 'foo'], 'bar'],
style: {
color: '#FF9387',
width: 2
}
});
})
</script>
</body>
</html>
О производительности
Карта позволяет работать с достаточно большим объёмом данных. Для работы с GeoJSON она использует библиотеку geojson-vt, которая обеспечивает приемлемое быстродействие на GeoJSON объёмом 100 Мб и содержащем 5.4 млн точек.
Пример ниже загружает и отображает на карте дорожную сеть из данных проекта Natural Earth Data. Данные представлены в виде Shp-файла объемом 15 Мб, содержащего порядка 700 тыс. точек.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Display road network from large shapefile with MapGL</title>
<meta name="description" content="Display road network from large shapefile with MapGL" />
<style>
html,
body,
#container {
margin: 0;
width: 100%;
height: 100%;
overflow: hidden;
}
button {
position: absolute;
top: 10px;
left: 10px;
z-index: 2;
font-family: sans-serif;
font-size: 10pt;
font-weight: bolder;
background: white;
border: none;
border-radius: 4px;
box-shadow: 0 1px 3px 0 rgba(38, 38, 38, 0.5);
color: #505050;
padding: 8px;
margin: 0;
box-sizing: border-box;
}
#status {
font-family: sans-serif;
font-size: 10pt;
position: absolute;
display: inline-block;
background-color: white;
padding: 2px;
border-radius: 2px;
top: 10px;
left: 170px;
z-index: 2;
}
</style>
</head>
<body>
<button onclick="loadShp()">Load Roads Network</button> <span id="status"></span>
<div id="container"></div>
<script src="https://unpkg.com/shapefile@0.6"></script>
<script src="https://unpkg.com/@turf/turf@6.5.0/turf.min.js"></script>
<script src="https://mapgl.2gis.com/api/js/v1"></script>
<script>
const statusEl = document.querySelector('#status');
function loadShp () {
statusEl.innerText = 'Loading...';
shapefile.open(
'https://raw.githubusercontent.com/nvkelso/natural-earth-vector/master/10m_cultural/ne_10m_roads.shp'
)
.then(async (source) => {
const features = [];
let vertices = 0;
let result = await source.read();
while (result && !result.done) {
features.push(result.value);
vertices += turf.coordAll(result.value).length;
result = await source.read();
}
statusEl.innerText = `${features.length} features, ${vertices} points loaded.`;
new mapgl.GeoJsonSource(map, {
data: { type: 'FeatureCollection', features },
attributes: { foo: 'bar' }
});
})
.catch(error => console.error(error.stack));
}
const map = new mapgl.Map('container', {
center: [18.687, 26.081],
zoom: 2,
key: 'Your API access key',
});
map.on('styleload', () => {
map.addLayer({
id: 'polygons',
type: 'polygon',
filter: ['==', ['sourceAttr', 'foo'], 'bar'],
style: {
color: '#00B2DD90'
}
});
map.addLayer({
id: 'lines',
type: 'line',
filter: ['==', ['sourceAttr', 'foo'], 'bar'],
style: {
color: ['match',
['get', 'type'],
['Major Highway'], '#FF5733',
['Secondary Highway'], '#F39C12',
['Ferry Route', 'Ferry, seasonal'], '#A569BD',
'#85929E'
],
width: ['interpolate',
['exponential', 1.5], ['zoom'],
4, 1,
9, 3
]
}
});
})
</script>
</body>
</html>
Таким образом, объем данных обычно оказываются неприемлем не с точки зрения скорости их обработки/отрисовки, а с точки зрения скорости сети: передача данных объемом 20-100 Мб может занимать долгое время, особенно если используется мобильный интернет в отдаленных регионах.
Тем не менее, несколько миллионов точек — далеко не предел для геоданных. Чтобы эффективно обрабатывать любые их объёмы, данные следует преобразовать в тайловое представление: разбить на небольшие фрагменты, которые можно загружать для визуализации по отдельности. Для этих целей предназначен инструмент 2ГИС Про.