Миграция с Google Maps

Миграция с Google Maps на Mapbox GL JS обычно связана с несколькими факторами:

  • необходимостью более глубокой кастомизации карт;
  • использованием векторного рендеринга на стороне клиента;
  • снижением затрат при высоких нагрузках;
  • потребностью в собственных стилях и брендировании;
  • интеграцией сложной геовизуализации и больших объемов пространственных данных;
  • повышением производительности при отображении тысяч объектов.

Google Maps ориентирован на использование готовой картографической платформы с богатым набором сервисов. Mapbox GL JS предоставляет значительно больше возможностей по управлению визуализацией карты и процессом рендеринга.


Ключевые различия архитектуры

Google Maps

Архитектура Google Maps строится вокруг объекта карты и набора оверлеев:

const map = new google.maps.Map(
    document.getElementById('map'),
    {
        center: { lat: 55.751244, lng: 37.618423 },
        zoom: 10
    }
);

Основные сущности:

  • Map
  • Marker
  • Polyline
  • Polygon
  • InfoWindow
  • OverlayView

Каждый объект существует как отдельный экземпляр JavaScript-класса.


Mapbox GL JS

Mapbox GL JS использует концепцию:

  • источников данных (sources);
  • слоев отображения (layers);
  • стилей (styles).
const map = new mapboxgl.Map({
    container: 'map',
    style: 'mapbox://styles/mapbox/streets-v12',
    center: [37.618423, 55.751244],
    zoom: 10
});

Практически любой визуальный объект представляет собой комбинацию источника данных и слоя.


Создание карты

Google Maps

const map = new google.maps.Map(
    document.getElementById('map'),
    {
        center: {
            lat: 55.751244,
            lng: 37.618423
        },
        zoom: 12
    }
);

Mapbox GL JS

mapboxgl.accessToken = 'TOKEN';

const map = new mapboxgl.Map({
    container: 'map',
    style: 'mapbox://styles/mapbox/light-v11',
    center: [37.618423, 55.751244],
    zoom: 12
});

Отличия координат

Google Maps:

{
    lat: 55.751244,
    lng: 37.618423
}

Mapbox GL JS:

[37.618423, 55.751244]

Порядок координат отличается:

Google Maps: lat, lng
Mapbox GL JS: lng, lat

Это одна из наиболее частых причин ошибок после миграции.


Инициализация после загрузки карты

Google Maps

google.maps.event.addListenerOnce(
    map,
    'idle',
    () => {
        console.log('Map ready');
    }
);

Mapbox GL JS

map.on('load', () => {
    console.log('Map ready');
});

Событие load является аналогом полной готовности карты к работе.


Замена маркеров

Google Maps

const marker = new google.maps.Marker({
    position: {
        lat: 55.751244,
        lng: 37.618423
    },
    map
});

Mapbox GL JS

new mapboxgl.Marker()
    .setLngLat([37.618423, 55.751244])
    .addTo(map);

Пользовательские маркеры

Google Maps

const marker = new google.maps.Marker({
    position: location,
    map,
    icon: '/images/pin.png'
});

Mapbox GL JS

const element = document.createElement('div');

element.className = 'custom-marker';

new mapboxgl.Marker(element)
    .setLngLat([37.618423, 55.751244])
    .addTo(map);

CSS:

.custom-marker {
    width: 32px;
    height: 32px;
    background-image: url('/images/pin.png');
    background-size: contain;
}

Mapbox значительно упрощает создание сложных HTML-маркеров.


Информационные окна

Google Maps

const infoWindow =
    new google.maps.InfoWindow({
        content: '<h3>Москва</h3>'
    });

marker.addListener('click', () => {
    infoWindow.open(map, marker);
});

Mapbox GL JS

const popup = new mapboxgl.Popup()
    .setHTML('<h3>Москва</h3>');

new mapboxgl.Marker()
    .setLngLat([37.618423, 55.751244])
    .setPopup(popup)
    .addTo(map);

Перемещение камеры

Google Maps

map.setCenter({
    lat: 55.751244,
    lng: 37.618423
});

Mapbox GL JS

map.setCenter([
    37.618423,
    55.751244
]);

Изменение масштаба

Google Maps

map.setZoom(14);

Mapbox GL JS

map.setZoom(14);

Синтаксис практически идентичен.


Анимация перемещения

Google Maps

map.panTo({
    lat: 55.751244,
    lng: 37.618423
});

Mapbox GL JS

map.flyTo({
    center: [37.618423, 55.751244],
    zoom: 14
});

Метод flyTo() обеспечивает плавную трехмерную анимацию перемещения.


Работа с событиями

Google Maps

map.addListener('click', (event) => {
    console.log(
        event.latLng.lat(),
        event.latLng.lng()
    );
});

Mapbox GL JS

map.on('click', (event) => {
    console.log(
        event.lngLat.lat,
        event.lngLat.lng
    );
});

Структура объекта события различается, поэтому код обработки требует адаптации.


Добавление линий

Google Maps

const line =
    new google.maps.Polyline({
        path: [
            { lat: 55.7, lng: 37.5 },
            { lat: 55.8, lng: 37.7 }
        ],
        map
    });

Mapbox GL JS

Сначала создается источник:

map.addSource('route', {
    type: 'geojson',
    data: {
        type: 'Feature',
        geometry: {
            type: 'LineString',
            coordinates: [
                [37.5, 55.7],
                [37.7, 55.8]
            ]
        }
    }
});

Затем слой:

map.addLayer({
    id: 'route-line',
    type: 'line',
    source: 'route',
    paint: {
        'line-color': '#ff0000',
        'line-width': 4
    }
});

Добавление полигонов

Google Maps

new google.maps.Polygon({
    paths: coordinates,
    map
});

Mapbox GL JS

map.addSource('polygon', {
    type: 'geojson',
    data: polygonGeoJSON
});

map.addLayer({
    id: 'polygon-fill',
    type: 'fill',
    source: 'polygon',
    paint: {
        'fill-color': '#0080ff',
        'fill-opacity': 0.5
    }
});

Переход на GeoJSON

Подход Google Maps

Google Maps часто использует собственные структуры данных:

[
    {
        lat: 55.7,
        lng: 37.5
    }
]

Подход Mapbox

Основной формат данных:

{
    type: 'FeatureCollection',
    features: [
        {
            type: 'Feature',
            geometry: {
                type: 'Point',
                coordinates: [
                    37.618423,
                    55.751244
                ]
            }
        }
    ]
}

Во время миграции рекомендуется максимально перевести пространственные данные в формат GeoJSON.

Преимущества:

  • единый стандарт;
  • совместимость с большинством GIS-инструментов;
  • высокая производительность;
  • простая интеграция со слоями.

Кластеризация маркеров

Google Maps

Обычно требуется дополнительная библиотека:

new MarkerClusterer({
    map,
    markers
});

Mapbox GL JS

Кластеризация встроена в движок.

map.addSource('points', {
    type: 'geojson',
    data: geojson,
    cluster: true,
    clusterRadius: 50
});

Создаются специальные слои кластеров и отдельных точек.

Такой подход лучше масштабируется при работе с десятками и сотнями тысяч объектов.


Замена стилей карты

Google Maps

Настройка выполняется через массив описаний:

styles: [
    {
        featureType: 'road',
        stylers: [
            {
                color: '#000000'
            }
        ]
    }
]

Mapbox GL JS

Используется полноценный стиль.

style:
'mapbox://styles/mapbox/dark-v11'

Либо собственный JSON-стиль:

style: customStyle

Стиль управляет:

  • дорогами;
  • зданиями;
  • водоемами;
  • подписями;
  • трехмерными объектами;
  • слоями данных.

Работа с большим количеством объектов

Google Maps

Каждый маркер является отдельным DOM-объектом.

new google.maps.Marker(...)

При большом количестве объектов производительность начинает снижаться.


Mapbox GL JS

Рекомендуется использовать слой символов:

map.addLayer({
    id: 'points',
    type: 'circle',
    source: 'points'
});

Все объекты рендерятся через WebGL.

Это позволяет отображать десятки тысяч элементов одновременно.


Замена Heatmap

Google Maps

new google.maps.visualization.HeatmapLayer({
    data: points
});

Mapbox GL JS

map.addLayer({
    id: 'heatmap',
    type: 'heatmap',
    source: 'points'
});

Тепловые карты являются встроенным типом слоя.


Работа с геолокацией

Google Maps

navigator.geolocation.getCurrentPosition(
    position => {
        console.log(position.coords);
    }
);

Mapbox GL JS

Используется тот же браузерный API либо готовый контрол:

map.addControl(
    new mapboxgl.GeolocateControl({
        trackUserLocation: true
    })
);

Полноэкранный режим

Google Maps

Обычно требуется дополнительная реализация.


Mapbox GL JS

map.addControl(
    new mapboxgl.FullscreenControl()
);

Навигационные элементы

Google Maps

zoomControl: true

Mapbox GL JS

map.addControl(
    new mapboxgl.NavigationControl()
);

Контрол включает:

  • масштабирование;
  • вращение карты;
  • изменение наклона.

Работа с трехмерными зданиями

Одно из важных преимуществ Mapbox GL JS — встроенная поддержка 3D-визуализации.

map.addLayer({
    id: 'buildings',
    source: 'composite',
    'source-layer': 'building',
    type: 'fill-extrusion',
    paint: {
        'fill-extrusion-height': [
            'get',
            'height'
        ]
    }
});

В Google Maps подобная функциональность существенно ограничена и зависит от используемых сервисов.


Частые проблемы при миграции

Перепутанный порядок координат

Неверно:

[55.751244, 37.618423]

Верно:

[37.618423, 55.751244]

Попытка создавать тысячи Marker

Неверно:

for (const point of points) {
    new mapboxgl.Marker()
        .setLngLat(point)
        .addTo(map);
}

Правильнее использовать:

map.addSource(...)
map.addLayer(...)

Добавление слоев до загрузки карты

Неверно:

map.addLayer(layer);

сразу после создания карты.

Верно:

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

Использование старой модели Google Maps

Распространенная ошибка — переносить архитектуру один-в-один:

Marker → Marker
Polyline → Polyline
Polygon → Polygon

Mapbox GL JS требует другого подхода:

Данные → Source → Layer → Style

Наиболее успешная миграция достигается не прямой заменой API-вызовов, а перестройкой приложения вокруг концепций GeoJSON, источников данных и слоев. Именно такая архитектура позволяет в полной мере использовать преимущества WebGL-рендеринга, масштабируемости и гибкой визуализации, лежащие в основе Mapbox GL JS.