Geodata formats
When working with geodata, you might face situations when you cannot display it on the map immediately due to the diversity, high volumes, and not always optimal representation of the data. You often need to perform preliminary calculations and transformations, for example:
- calculate boundaries of geodata location to position a map accordingly
- simplify geometries to get smoother outlines and increase rendering performance
- transform a polygon into points and vice versa
Urbi map on its own does not have functionality for handling geodata. However, you can use third-party open-source libraries to fill this gap. The basic exchange format (namely, a lingua franca) between these libraries and MapGL GS API is GeoJSON.
This chapter contains primary information about main geodata formats, most common problems of geodata handling, and solutions to them.
GeoJSON
GeoJSON format (JSON subset) is supported by the map directly. In this format, geodata is presented as Feature
objects with each containing geometry in the geometry
field and (optionally) metadata in the properties
field.
{
"type": "FeatureCollection",
"features": [
{
"geometry": {
"type": "LineString",
"coordinates": [
[100.0, 0.0],
[101.0, 1.0]
],
},
"properties": {
"title": "A sample line"
}
},
]
}
For more information on working with this format in the map, see the GeoJSON chapter.
GeoJSON format is described by the RFC 7946 standard.
As this format is a JSON subset, it does not require any additional libraries for encoding/decoding.
Well-known text (WKT)
Well-known text represents vector geometries as a string. The format stores geometries only, no metadata.
Example of geometries in this format:
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))
To work with this standard in the web, you can use the wellknown library.
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 format is described by the OGC Simple Feature Access standard.
The format is widely used in Urbi APIs: for example, Routing API and Places API (see geometry
fields).
<!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 contains geodata in binary format, which enables storing data densely. The format supports storing both geometries and metadata.
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));
The format is supported by the Esri company and is described by the ESRI Shapefile Technical Description standard.
To work with this standard in the web, you can use the shapefile library.
<!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>
Performance note
The map enables working with a relatively big volume of data. For working with GeoJSON, it uses the geojson-vt library. The library provides acceptable performance speed for GeoJSON with 100 MB of volume and 5.4 million points.
The example below loads and displays a roads network from the Natural Earth Data project. Data is represented as a Shp file with 15 MB of volume and around 700 thousands points.
<!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>
The volume of data becomes unacceptable due to not the speed of handling/rendering but the network speed: sending data of 200-100 MB of volume may take long, especially if the mobile network in remote regions is used.
However, several millions of points is far not the limit for geodata. To efficiently handle any volumes of geodata, it should be transformed into tile representation: divided into fragments that can be loaded for visualization separately. Urbi Pro is a tool designed for these tasks.