Processing geodata | MapGL | 2GIS Documentation

Processing geodata

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 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 Working with GeoJSON example.

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 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>
            // Workaround for wellknown module
            var module = {};
        </script>
        <script src="https://unpkg.com/wellknown@0.5.0/index.js"></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>
            var wellknown = module.exports;
            const geojson = turf.geometryCollection([
                'POINT (30 10)',
                'LINESTRING (30 10, 10 30, 40 40)',
                // 'POLYGON ((30 10, 40 40, 20 40, 10 20, 30 10))',
                '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
                    }
                });
            })
        </script>
    </body>
</html>

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>

The turf.js library is recommended for working with GeoJSON. The sections below describe using this library together with MapGL JS API.


The turf.bbox method calculates a rectangular area that contains given objects or a group of objects.

const line = turf.lineString([
    [34, 40],
    [36, 41],
    [41, 37],
    [48, 42],
    [42, 35],
]);
const bbox = turf.bbox(line); // [minX, minY, maxX, maxY]

Method documentation: @turf/bbox

Usage examples

  • Position the map so that all objects are fit into the screen using the map.fitBounds method.

    map.fitBounds({ southWest: bbox.slice(0, 2), northEast: bbox.slice(2, 4) });
    
  • Request additional data. For example, request nearest construction objects when you have municipal area polygons.

Bounding box is the most common and fast way to define boundaries of objects location. The downside is that the boundaries are defined roughly. If you need higher precision, use a convex hull.


<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <title>Using @turf/bbox with MapGL</title>
        <meta name="description" content="Using @turf/bbox 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>
            const line = turf.lineString([[34, 40], [36, 41], [41, 37], [48, 42], [42, 35]]);
            const bbox = turf.bbox(line);

            const geojson = turf.featureCollection([line, turf.bboxPolygon(bbox)]);

            const map = new mapgl.Map('container', {
                center: turf.getCoord(turf.center(geojson)),
                zoom: 5,
                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
                    }
                });
            })
        </script>
    </body>
</html>

The turf.convex method takes an object or a group of objects and creates a convex hull based on them.

const line = turf.lineString([
    [34, 40],
    [36, 41],
    [41, 37],
    [48, 42],
    [42, 35],
]);
const convexHull = turf.convex(line);

Method documentation: @turf/convex

Usage example

Convex hull is a more precise (compared to the bounding box) method of defining object boundaries. The downside is high computational complexity of this method.


<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <title>Using @turf/convex with MapGL</title>
        <meta name="description" content="Using @turf/convex 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>
            const line = turf.lineString([[34, 40], [36, 41], [41, 37], [48, 42], [42, 35]]);
            const convexHull = turf.convex(line);

            const geojson = turf.featureCollection([line, convexHull]);

            const map = new mapgl.Map('container', {
                center: turf.getCoord(turf.center(geojson)),
                zoom: 5,
                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
                    }
                });
            })
        </script>
    </body>
</html>

The turf.buffer method enables enlarging or reducing an object proportionally.

const line = turf.lineString([
    [34, 40],
    [36, 41],
    [41, 37],
    [48, 42],
    [42, 35],
]);
const buffer = turf.buffer(line, 40);

Method documentation: @turf/buffer

Usage examples

  • Display various types of security zones, isolation areas, and others.

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <title>Using @turf/buffer and MapGL</title>
        <meta name="description" content="Using @turf/buffer and 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>
            const line = turf.lineString([[34, 40], [36, 41], [41, 37], [48, 42], [42, 35]]);
            const buffer = turf.buffer(line, 40);

            const geojson = turf.featureCollection([line, buffer]);

            const map = new mapgl.Map('container', {
                center: turf.getCoord(turf.center(geojson)),
                zoom: 5,
                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
                    }
                });
            })
        </script>
    </body>
</html>

  • Display places equally distant from an object on the map.

    However, this works good only for small distances (up to 50-100 m). Calculation of larger distances is affected by the method of turf.buffer calculations. Distances are calculated purely geometrically while in reality the shortest geometric way usually contains obstacles: houses, rivers, fences, which must be bypassed. In this case, you should use isochrones as a more precise and specialized tool.

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <title>Drawing buffers around points with @turf/buffer and MapGL</title>
        <meta name="description" content="Drawing buffers around points with @turf/buffer and 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>
            const points = [
                [33.63825,34.917647],
                [33.638238,34.914459],
                [33.634526,34.912492],
                [33.628846,34.914821],
                [33.628997,34.917202],
                [33.625677,34.916229]
            ];
            const geojson = turf.featureCollection([]);
            for (const pt of points) {
                const feature = turf.point(pt);
                geojson.features.push(feature);
                geojson.features.push(turf.buffer(feature, 0.1));
            }

            const map = new mapgl.Map('container', {
                center: turf.getCoord(turf.center(geojson)),
                zoom: 15.5,
                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
                    }
                });
            })
        </script>
    </body>
</html>

The turf.center, turf.centroid, and turf.centerOfMass methods enable getting a particular center of an object or a group of objects.

const polygon = turf.polygon([
    [
        [34, 40],
        [36, 41],
        [41, 37],
        [48, 42],
        [42, 35],
        [34, 40],
    ],
]);
const center = turf.center(polygon);
const centroid = turf.centroid(polygon);
const centerOfMass = turf.centerOfMass(polygon);

Method documentation:

Usage examples

You can get a center in three ways:

  • center gets the center of a bounding box of an object or a group.
  • centroid calculates a barycenter of an object or a group.
  • centerOfMass gets the center of mass of a figure.

On convex and symmetric polygons, these methods show almost no difference. In case of non-convex or other irregular polygons, centerOfMass works best.

Using these methods is convenient for placing object captions.


<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <title>Using @turf/[center,centroid,centerOfMass] and MapGL</title>
        <meta name="description" content="Using @turf/[center,centroid,centerOfMass] and 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>
            const polygon = turf.polygon([[[34, 40], [36, 41], [41, 37], [48, 42], [42, 35], [34, 40]]]);
            const center = turf.center(polygon, { properties: { name: 'Center' } });
            const centroid = turf.centroid(polygon, { properties: { name: 'Centeroid' } });
            const centerOfMass = turf.centerOfMass(polygon, { properties: { name: 'Center of mass' } });

            const geojson = turf.featureCollection([polygon, center, centroid, centerOfMass]);

            const map = new mapgl.Map('container', {
                center: turf.getCoord(turf.center(geojson)),
                zoom: 7.5,
                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,
                        textFont: 'Noto_Sans',
                        textFontSize: 12,
                        textField: ['get', 'name'],
                        textPriority: 100
                    }
                });
            })
        </script>
    </body>
</html>

The turf.nearestPoint method gets the nearest point from the set of points.

const line = turf.lineString([
    [34, 40],
    [36, 41],
    [41, 37],
    [48, 42],
    [42, 35],
]);
const target = turf.point([38, 38]);

const points = turf.explode(line);
const nearest = turf.nearestPoint(target, points);

Method documentation: @turf/nearestPoint

The method feature is working only with a set of points. To transform an object into a set of points, use the @turf/explode method.

Usage example

Find the nearest object to the given one.


The turf.nearestPointOnLine method gets the nearest point to the given one on a line or a group of lines.

const line = turf.lineString([
    [34, 40],
    [36, 41],
    [41, 37],
    [48, 42],
    [42, 35],
]);
const target = turf.point([38, 38], { name: 'Target' });

const nearestOnLine = turf.nearestPointOnLine(line, target);

Method documentation: @turf/nearestPointOnLine

Usage example

Find the nearest point on a route or a road. Like the turf.buffer method, it works in the simplest cases only when considering obstacles is not required. In other cases, use the Routing API.


<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <title>Finding nearest with Turf.js and MapGL</title>
        <meta name="description" content="Finding nearest with Turf.js and 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>
            const line = turf.lineString([[34, 40], [36, 41], [41, 37], [48, 42], [42, 35]]);
            const target = turf.point([38, 38], { name: 'Target' });

            const nearestOnLine = turf.nearestPointOnLine(line, target);
            nearestOnLine.properties = { name: 'Nearest Point On Line' };

            const points = turf.explode(line);
            const nearest = turf.nearestPoint(target, points);
            nearest.properties = { name: 'Nearest Point' };

            const geojson = turf.featureCollection([line, target, nearest, nearestOnLine]);

            const map = new mapgl.Map('container', {
                center: turf.getCoord(turf.center(geojson)),
                zoom: 7,
                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,
                        textFont: 'Noto_Sans',
                        textFontSize: 12,
                        textField: ['get', 'name'],
                        textPriority: 100
                    }
                });
            })
        </script>
    </body>
</html>

The turf.union method combines multiple figures into one.

const points = [
    [10, 10],
    [12, 10],
];
const circleA = turf.buffer(turf.point([10, 10]), 300);
const circleB = turf.buffer(turf.point([12, 10]), 300);
const united = turf.union(circleA, circleB);

Method documentation: @turf/union

Usage examples

  • Display an area formed by multiple objects as one object on the map.
  • Assemble overly fragmented objects, which often happens during geodata handling.

The turf.intersect method find a figure formed by an intersection of given figures.

const points = [
    [10, 10],
    [12, 10],
];
const circleA = turf.buffer(turf.point([10, 10]), 300);
const circleB = turf.buffer(turf.point([12, 10]), 300);
const intersected = turf.intersect(circleA, circleB);

Method documentation: @turf/intersect

Usage example

Display an area formed by an intersection of other areas on the map. For example, a free parking lot within a given radius from an object.


<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <title>Using @turf/union and @turf/intersect with MapGL</title>
        <meta name="description" content="Using @turf/union and @turf/intersect 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>
            const points = [[10, 10], [12, 10]];
            const circleA = turf.buffer(turf.point([10, 10]), 300);
            const circleB = turf.buffer(turf.point([12, 10]), 300);
            const united = turf.transformTranslate(turf.union(circleA, circleB), 1200, 90);
            const intersected = turf.transformTranslate(turf.intersect(circleA, circleB), 1200, -90);

            const geojson = turf.featureCollection([circleA, circleB, united, intersected]);

            const map = new mapgl.Map('container', {
                center: turf.getCoord(turf.center(geojson)),
                zoom: 5,
                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
                    }
                });
            })
        </script>
    </body>
</html>

The turf.mask method enables turning a polygon "inside out": turning its surrounding area into a polygon.

const poly = turf.polygon([
    [
        [0, 0],
        [0, 2],
        [2, 2],
        [2, 0],
        [0, 0],
    ],
]);
turf.mask(poly);

Method documentation: @turf/mask

Usage example

Draw attention to one or multiple areas on the map and obscure the remaining space.


<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <title>Using @turf/mask with MapGL</title>
        <meta name="description" content="Using @turf/mask 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>
            const aPlace = turf.polygon([[
                [105.92429031266958, -5.262701049754213],
                [106.28471551378776, -5.195701693969994],
                [106.64994638418318, -5.214845098030779],
                [107.03920560108905, -5.305768259535483],
                [107.58705190638044, -5.4158151922751046],
                [107.74083332350821, -5.721926046845098],
                [107.8369467063646, -6.371857054158838],
                [107.82252969773305, -6.558086239663282],
                [106.65475205317988, -7.569142136398696],
                [106.01079236773299, -7.5834332864075975],
                [105.86181661939993, -7.535794279073713],
                [105.49178008359819, -7.430970061978684],
                [104.78488126325243, -6.791043334337019],
                [104.8233266147578, -6.456892553102861],
                [105.0299703919453, -6.026947405124446],
                [105.16933480114257, -5.816626441233922],
                [105.30389354084993, -5.644487005567058],
                [105.92429031266958, -5.262701049754213],
            ]]);
            const bbox = turf.bbox(aPlace);

            const map = new mapgl.Map('container', {
                center: turf.getCoord(turf.center(aPlace)),
                zoom: 7.5,
                key: 'Your API access key',
            });

            new mapgl.GeoJsonSource(map, {
                data: turf.mask(aPlace),
                attributes: {
                    foo: 'bar'
                }
            });

            map.on('styleload', () => {
                map.addLayer({
                    id: 'polygons',
                    type: 'polygon',
                    filter: ['==', ['sourceAttr', 'foo'], 'bar'],
                    style: {
                        color: '#00B2DD50'
                    }
                });
            })
        </script>
    </body>
</html>



The turf.simplify method simplifies a geometry using the Ramer–Douglas–Peucker algorithm.

const gpsTrack = turf.lineString([
    [0, 0],
    [0, 0.1],
    [0.5, 0.5],
    [1, 0.5],
]);
const simplifiedTrack = turf.simplify(gpsTrack, { tolerance: 0.2 });
// { ... geometry: { ..., coordinates: [[0, 0], [0.5, 0.5], [1, 0.5]] } }

The simplification degree is defined by the tolerance parameter, which specifies the maximum error that the algorithm can make during simplification. The tolerance value has the same dimension as the source data: if geocoordinates are specified in degrees, tolerance must be in degrees too.

Method documentation: @turf/simplify

Usage examples

  • Get less complicated geometries, which are rendered faster.
  • Filter data when it is known that their precision must not exceed specific value.
  • Display precise data on a large scale: simplified geometries have more aesthetic outlook and smoother lines without visual noise.

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <title>Using @turf/simplify with MapGL</title>
        <meta name="description" content="Using @turf/simplify 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>
            const gpsTrack = turf.lineString([[0, 0], [0, 0.1], [0.5, 0.5], [1, 0.5]]);
            const simplifiedTrack = turf.simplify(gpsTrack, { tolerance: 0.2 });
            simplifiedTrack.properties.simplified = true;
            const bbox = turf.bbox(gpsTrack);

            console.log(gpsTrack, simplifiedTrack);
            const geojson = turf.featureCollection([gpsTrack, simplifiedTrack]);

            const map = new mapgl.Map('container', {
                center: turf.getCoord(turf.center(geojson)),
                zoom: 5,
                key: 'Your API access key',
            });
            map.fitBounds(
                { southWest: bbox.slice(0, 2), northEast: bbox.slice(2, 4) },
                { padding: { left: 20, top: 20, right: 20, bottom: 20 } }
            );

            new mapgl.GeoJsonSource(map, {
                data: geojson,
                attributes: {
                    foo: 'bar'
                }
            });
            map.on('styleload', () => {
                map.addLayer({
                    id: 'lines',
                    type: 'line',
                    filter: ['==', ['sourceAttr', 'foo'], 'bar'],
                    style: {
                        color: ['match', 
                            ['get', 'simplified'], 
                            [true], '#FF9387',
                            '#00B2DDA0'
                        ],
                        width: ['match', 
                            ['get', 'simplified'], 
                            [true], 3,
                            5
                        ]
                    }
                });
            })
        </script>
    </body>
</html>


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.