Работа с GeoJSON | MapGL | 2GIS Documentation

Работа с GeoJSON

Для показа GeoJSON данных на карте нужно:

  1. подключить источник этих данных в карту;
  2. добавить слой в стиль карты, который объяснит, как рисовать эти данные.

Возьмем простейший пример данных в формате GeoJSON:

const data = {
    type: 'FeatureCollection',
    features: [
        {
            type: 'Feature',
            properties: {},
            geometry: {
                type: 'Polygon',
                coordinates: [
                    [
                        [55.41, 25.34],
                        [55.44, 25.34],
                        [55.43, 25.37],
                        [55.41, 25.34],
                    ],
                ],
            },
        },
    ],
};

Для их подключения в качестве источника данных карты нужно использовать класс GeoJsonSource:

const source = new mapgl.GeoJsonSource(map, {
    data,
});

Так мы добавили полигон только в данные карты. Однако, отрисовать этот полигон не получится, поскольку его нельзя связать с конкретным слоем стиля карты. Полигон добавился в общий массив данных карты, а там его невозможно отличить от других полигонов.

Чтобы полигон отличался от других, можно добавить ему уникальное свойство в properties:

const data = {
    type: 'FeatureCollection',
    features: [
        {
            type: 'Feature',
            properties: {
                foo: 'qwe', // Уникальное свойство
            },
            geometry: {
                type: 'Polygon',
                coordinates: [
                    [
                        [55.41, 25.34],
                        [55.44, 25.34],
                        [55.43, 25.37],
                        [55.41, 25.34],
                    ],
                ],
            },
        },
    ],
};

Либо добавить уникальное свойство всему источнику данных через поле attributes:

const source = new mapgl.GeoJsonSource(map, {
    data,
    attributes: {
        bar: 'asd', // Уникальное свойство
    },
});

Теперь такой полигон можно связать с новым слоем стилем карты, а значит и отрисовать.

Стиль карты — это конфиг, который описывает как рисовать данные карты. Стиль состоит из слоев. Каждый слой содержит:

  • в поле filter — логику фильтрации данных карты для определения, какие именно данные будут рисоваться этим слоем;
  • в поле style — описание внешнего вида объектов отрисовки (цвет, ширина, шрифт и пр.).

Подробнее про стиль карты можно узнать в Спецификации стиля.

Выше мы добавили полигон в качестве источника данных карты, теперь его нужно связать с новым слоем стиля.

Для начала создадим новый слой:

const layer = {
    id: 'my-polygons-layer', // ID каждого слоя должен быть уникальным

    // Логика фильтрации или выбора данных для этого слоя
    filter: [
        'match',
        ['sourceAttr', 'bar'],
        ['asd'],
        true, // Значение при совпадении атрибута bar источника cо значением "asd"
        false, // Значение при несовпадении
    ],

    // Тип объекта отрисовки
    type: 'polygon',

    // Стиль объекта отрисовки
    style: {
        color: '#0000ff',
    },
};

Здесь в filter используются:

  • SourceAttrExpression — для получения значений из свойства bar источника данных;
  • MatchExpression — для сопоставления полученного свойства из атрибута bar со строковым значением "asd".

Если при добавлении данных был выбран вариант не со свойством источника данных, а с добавление свойства только одному объекту GeoJSON в properties, то вместо SourceAttrExpression нужно использовать GetExpression.

В конце созданный слой нужно добавить в стиль карты с помощью map.addLayer(layer), и карта перерисуется уже вместе с новым полигоном:

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <title>2GIS Map API</title>
        <meta name="description" content="GeoJSON polygon example" />
        <style>
            html,
            body,
            #container {
                margin: 0;
                width: 100%;
                height: 100%;
                overflow: hidden;
            }
        </style>
    </head>
    <body>
        <div id="container"></div>
        <script src="https://mapgl.2gis.com/api/js/v1"></script>
        <script>
            const map = new mapgl.Map('container', {
                center: [55.425, 25.355],
                zoom: 13,
                key: 'Your API access key',
            });

            const data = {
                type: 'FeatureCollection',
                features: [
                    {
                        type: 'Feature',
                        properties: {},
                        geometry: {
                            type: 'Polygon',
                            coordinates: [
                                [
                                    [55.41, 25.34],
                                    [55.44, 25.34],
                                    [55.43, 25.37],
                                    [55.41, 25.34],
                                ],
                            ],
                        },
                    },
                ],
            };

            const source = new mapgl.GeoJsonSource(map, {
                data,
                attributes: {
                    bar: 'asd',
                },
            });

            const layer = {
                id: 'my-polygons-layer', // Each layer ID must be unique

                // Data filtering logic
                filter: [
                    'match',
                    ['sourceAttr', 'bar'],
                    ['asd'],
                    true, // Result if value of bar source attribute equals "asd"
                    false, // Result if not
                ],

                // Drawing object type
                type: 'polygon',

                // Style of drawing object
                style: {
                    color: '#0000ff',
                },
            };

            map.on('styleload', () => {
                map.addLayer(layer);
            });
        </script>
    </body>
</html>

Сразу после создания карты её стиль будет пустым, до тех пор пока он не загрузится с сервера. Важно дождаться его загрузки, прежде чем добавлять новый слой, поскольку загруженный стиль полностью затрет старый. Для этого в примере выше используется событие styleload, которое всплывает каждый раз после установки нового стиля:

map.on('styleload', () => {
    map.addLayer(layer);
});

Если вы меняете стиль карты с помощью метода map.setStyleById(), то вместо события вы также можете использовать Promise, который возвращает метод:

map.setStyleById('e05ac437-fcc2-4845-ad74-b1de9ce07555').then(() => {
    map.addLayer(layer);
});

Слои стиля упорядочены относительно друг друга. Порядок слоев определяет то, в какой последовательности они будут рисоваться на карте.

По умолчанию метод map.addLayer(layer) добавляет слой в конец списка, т.е. новый слой будет рисоваться последним. Если нужно добавить новый слой не в конец, а перед другим слоем, то нужно в методе указать вторым параметром ID другого слоя:

map.addLayer(anotherLayer, 'my-polygons-layer');

Вставить новый слой можно также перед слоями, которые настраиваются в Редакторе стилей:

map.addLayer(layer, 'other roads tunnel');
<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <title>2GIS Map API</title>
        <meta name="description" content="GeoJSON polygon under roads example" />
        <style>
            html,
            body,
            #container {
                margin: 0;
                width: 100%;
                height: 100%;
                overflow: hidden;
            }
        </style>
    </head>
    <body>
        <div id="container"></div>
        <script src="https://mapgl.2gis.com/api/js/v1"></script>
        <script>
            const map = new mapgl.Map('container', {
                center: [55.425, 25.355],
                zoom: 13,
                key: 'Your API access key',
            });

            const data = {
                type: 'FeatureCollection',
                features: [
                    {
                        type: 'Feature',
                        properties: {},
                        geometry: {
                            type: 'Polygon',
                            coordinates: [
                                [
                                    [55.41, 25.34],
                                    [55.44, 25.34],
                                    [55.43, 25.37],
                                    [55.41, 25.34],
                                ],
                            ],
                        },
                    },
                ],
            };

            const source = new mapgl.GeoJsonSource(map, {
                data,
                attributes: {
                    bar: 'asd',
                },
            });

            const layer = {
                id: 'my-polygons-layer', // Each layer ID must be unique

                // Data filtering logic
                filter: [
                    'match',
                    ['sourceAttr', 'bar'],
                    ['asd'],
                    true, // Result if value of bar source attribute equals "asd"
                    false, // Result if not
                ],

                // Drawing object type
                type: 'polygon',

                // Style of drawing object
                style: {
                    color: '#0000ff',
                },
            };

            map.on('styleload', () => {
                map.addLayer(layer, 'other roads tunnel');
            });
        </script>
    </body>
</html>

Для отображения источника данных необязательно добавлять слой через API, можно также добавить его в Редакторе стилей в свой стиль карты.

Для этого там при создании нового слоя на этапе выбора данных нужно нажать на кнопку JSON — добавить вручную и добавить MatchExpression, который нужно было бы указать в filter при добавлении слоя через API:

GeoJSON styles from editor

Ниже представлен сложный пример использования GeoJSON, в нем одновременно:

  • подключаются разные типы данных GeoJSON (Point, Polygon, LineString);
  • показываются разные объекты отрисовки (line, dashedLine, point, polygon);
  • используются составные выражения в filter;
  • используются выражения внутри стилевых свойств.
<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <title>2GIS Map API</title>
        <meta name="description" content="GeoJSON complex example" />
        <style>
            html,
            body,
            #container {
                margin: 0;
                width: 100%;
                height: 100%;
                overflow: hidden;
            }
        </style>
    </head>
    <body>
        <div id="container"></div>
        <script src="https://mapgl.2gis.com/api/js/v1"></script>
        <script>
            const map = new mapgl.Map('container', {
                center: [55.4218, 25.3357],
                zoom: 17,
                pitch: 45,
                key: 'Your API access key',
            });

            const data = {
                type: 'FeatureCollection',
                features: [
                    {
                        type: 'Feature',
                        properties: {
                            type: 'topLine',
                        },
                        geometry: {
                            type: 'LineString',
                            coordinates: [
                                [55.41612267494202, 25.3373063569028],
                                [55.418890714645386, 25.33572577372391],
                                [55.41969537734985, 25.336870001646474],
                                [55.42081117630005, 25.33621061738493],
                                [55.421626567840576, 25.337393627765234],
                                [55.42233467102051, 25.336966969617198],
                                [55.42382597923279, 25.339100245313542],
                            ],
                        },
                    },
                    {
                        type: 'Feature',
                        properties: {
                            type: 'star',
                        },
                        geometry: {
                            type: 'Polygon',
                            coordinates: [
                                [
                                    [55.422656536102316, 25.335250625051458],
                                    [55.42331099510195, 25.335570623342118],
                                    [55.42392253875736, 25.33520214038817],
                                    [55.423622131347685, 25.335774258177413],
                                    [55.42422294616701, 25.336074861355655],
                                    [55.423579216003446, 25.33610395194616],
                                    [55.42338609695437, 25.336666368655063],
                                    [55.42309641838077, 25.336123345669286],
                                    [55.422409772872946, 25.33614273938931],
                                    [55.42298913002018, 25.33580334884018],
                                    [55.422656536102316, 25.335250625051458],
                                ],
                            ],
                        },
                    },
                    {
                        type: 'Feature',
                        properties: {
                            type: 'star',
                        },
                        geometry: {
                            type: 'Polygon',
                            coordinates: [
                                [
                                    [55.419437885284445, 25.334988807638815],
                                    [55.42009234428408, 25.335308806621768],
                                    [55.42070388793948, 25.334940322870644],
                                    [55.420403480529814, 25.33551244189763],
                                    [55.42100429534914, 25.335813045726233],
                                    [55.42036056518557, 25.33584213637969],
                                    [55.420167446136496, 25.336404554305396],
                                    [55.419877767562895, 25.335861530144776],
                                    [55.419191122055075, 25.335880923906757],
                                    [55.419770479202306, 25.33554153262335],
                                    [55.419437885284445, 25.334988807638815],
                                ],
                            ],
                        },
                    },
                    {
                        type: 'Feature',
                        properties: {
                            type: 'bottomLine',
                        },
                        geometry: {
                            type: 'LineString',
                            coordinates: [
                                [55.42360067367554, 25.33460092894936],
                                [55.42147636413574, 25.335803348840205],
                                [55.42107939720153, 25.335231231188487],
                                [55.42302131652832, 25.334077290746556],
                            ],
                        },
                    },
                    {
                        type: 'Feature',
                        properties: {
                            type: 'marker',
                            label: 'Star #2',
                            color: 'blue',
                        },
                        geometry: {
                            type: 'Point',
                            coordinates: [55.42332172393799, 25.33587122702617],
                        },
                    },
                    {
                        type: 'Feature',
                        properties: {
                            type: 'marker',
                            label: 'Star #1',
                            color: 'blue',
                        },
                        geometry: {
                            type: 'Point',
                            coordinates: [55.420124530792236, 25.335590017150714],
                        },
                    },
                    {
                        type: 'Feature',
                        properties: {
                            type: 'marker',
                            color: 'gray',
                        },
                        geometry: {
                            type: 'Point',
                            coordinates: [55.4215407371521, 25.336588794091984],
                        },
                    },
                    {
                        type: 'Feature',
                        properties: {
                            type: 'marker',
                            color: 'gray',
                        },
                        geometry: {
                            type: 'Point',
                            coordinates: [55.42234539985657, 25.33490153504211],
                        },
                    },
                ],
            };

            const source = new mapgl.GeoJsonSource(map, {
                data,
                attributes: {
                    bar: 'asd',
                },
            });

            map.on('styleload', () => {
                map.addLayer(
                    {
                        type: 'polygon',
                        id: 'my-stars',
                        filter: [
                            'all',
                            ['match', ['sourceAttr', 'bar'], ['asd'], true, false],
                            ['match', ['get', 'type'], ['star'], true, false],
                        ],
                        style: {
                            color: '#fcc9ff',
                        },
                    },
                    '31871',
                );
                map.addLayer(
                    {
                        type: 'dashedLine',
                        id: 'my-dashedline',
                        filter: [
                            'all',
                            ['match', ['sourceAttr', 'bar'], ['asd'], true, false],
                            ['match', ['get', 'type'], ['topLine'], true, false],
                        ],
                        style: {
                            color: '#0baf08',
                            width: 10,
                            dashLength: 10,
                            gapLength: 5,
                        },
                    },
                    '973940',
                );
                map.addLayer(
                    {
                        type: 'line',
                        id: 'my-line',
                        filter: [
                            'all',
                            ['match', ['sourceAttr', 'bar'], ['asd'], true, false],
                            ['match', ['get', 'type'], ['bottomLine'], true, false],
                        ],
                        style: {
                            color: '#ffcd17',
                            width: 7,
                        },
                    },
                    '973940',
                );
                map.addLayer({
                    type: 'point',
                    id: 'my-marker',
                    filter: [
                        'all',
                        ['match', ['sourceAttr', 'bar'], ['asd'], true, false],
                        ['match', ['get', 'type'], ['marker'], true, false],
                    ],
                    style: {
                        iconImage: ['match', ['get', 'color'], ['blue'], 'ent_i', 'ent'],
                        iconWidth: 25,
                        textField: ['get', 'label'],
                        textFont: ['Noto_Sans'],
                        textColor: '#0098ea',
                        textHaloColor: '#fff',
                        textHaloWidth: 1,
                        iconPriority: 100,
                        textPriority: 100,
                    },
                });
            });
        </script>
    </body>
</html>