Deck.gl это платформа на базе WEB API WebGL для визуального исследовательского анализа больших наборов данных (не только для визуализации географических данных). Создана и поддерживается Uber. Изначально разработана с использованием Mapbox. Также поддерживаются Google Maps, но возможности будут ограничены.
Теперь мы можем переходить к практике, которую разделим на несколько пунктов. Напомню, что для реализации поставленных задач будем использовать React.
1. Отображение карты
Для начала нам нужно зарегистироваться на сайте Mapbox и в личном кабинете получить токен. Он понадобится для работы с картами в проекте.
Переходим к установке зависимостей: yarn add mapbox-gl @urbica/react-map-gl
Итоговый код для простого отображения карты на весь экран будет выглядеть следующим образом:
import * as React from "react"; import MapGL from "@urbica/react-map-gl"; import "mapbox-gl/dist/mapbox-gl.css"; const App = () => { const viewport = { latitude: 0, longitude: 0, zoom: 1, }; return ( <MapGL style={{ width: "100vw", height: "100vh" }} accessToken={TOKEN} {...viewport} /> ); };
2. Отображение точек
Будем использовать Deck.gl слой IconLayer.
Подробнее о нем можно почитать здесь.
Установим зависимости: yarn add @deck.gl/mapbox @deck.gl/layers
Атлас с иконками возьмем по ссылке.
В объекте ICON_MAPPING описаны все виды иконок и их координаты в атласе выше.
Массив для двух точек будет выглядеть следующим образом. Обращаю внимание, что нужно в ключе icon указать вид иконки из объекта ICON_MAPPING.
const iconsData = [ { id: 1, name: "First Point", size: 5, icon: "marker", coordinates: [-100.12097640000002, 35.449965], color: [0, 0, 128], }, { id: 2, name: "Second Point", size: 5, icon: "marker", coordinates: [-100.0893059, 40.39611790000001], color: [255, 0, 0], }, ];
Итоговый код:
import * as React from "react"; import MapGL, { CustomLayer } from "@urbica/react-map-gl"; import { MapboxLayer } from "@deck.gl/mapbox"; import { IconLayer } from "@deck.gl/layers"; import "mapbox-gl/dist/mapbox-gl.css"; import Atlas from "../src/img/icon-atlas.png"; import iconsData from "./layersData/iconsData"; const ICON_MAPPING = { marker: { x: 0, y: 0, width: 140, height: 148, mask: true }, circle: { x: 0, y: 130, width: 120, height: 120, mask: true }, }; const App = () => { const [viewport, setViewport] = React.useState({ latitude: 0, longitude: 0, zoom: 1, }); const iconsLayer = new MapboxLayer({ id: "icon-layer", type: IconLayer, data: iconsData, iconAtlas: Atlas, sizeScale: 10, iconMapping: ICON_MAPPING, getIcon: (d) => d.icon, getPosition: (d) => d.coordinates, getSize: (d) => d.size, getColor: (d) => d.color, }); return ( <MapGL style={{ width: "100vw", height: "100vh" }} accessToken={TOKEN} onViewportChange={setViewport} {...viewport} > <CustomLayer layer={iconsLayer} /> </MapGL> ); };

3. Построение маршрутов
Для этой задачи используем TripsLayer слой.
В качестве данных я создаю объект с уже готовыми массивами, поэтому мне не нужно дополнительно использовать метод map при создании слоя и передачи координат с временем как в примере по ссылке. Хотелось бы отметить, что каждой координате соответствует свое время (unix timestamp).
const tripsData = [ { coordinates: [ [-100.149639, 35.440481], [-100.151832, 35.439649], [-100.152752, 35.439323], [-100.154222, 35.439699], [-100.154293, 35.439301], [-100.15539, 35.438506], [-100.155745, 35.43833], [-100.156138, 35.4382], [-100.157342, 35.43783], [-100.157729, 35.437787], [-100.15931, 35.438335], [-100.159402, 35.438002], [-100.159333, 35.437877], [-100.159702, 35.437776], [-100.160215, 35.437766], [-100.160195, 35.43783], [-100.160439, 35.43806], [-100.160269, 35.437808], [-100.160124, 35.438099], [-100.143509, 35.441997], [-100.142375, 35.442401], [-100.141645, 35.442632], [-100.141291, 35.442684], [-100.141841, 35.442592], [-100.139913, 35.443157], [-100.139481, 35.443198], [-100.138995, 35.443288], [-100.138686, 35.443415], ], timestamps: [ 1556009520, 1556009520, 1556009520, 1556009520, 1556009520, 1556009520, 1556009520, 1556009520, 1556009520, 1556009520, 1556009880, 1556009880, 1556010060, 1556011440, 1556011440, 1556011500, 1556011500, 1556011500, 1556011680, 1556012100, 1556012160, 1556012160, 1556012160, 1556012160, 1556012160, 1556012160, 1556012160, 1556012160, ], color: [18, 83, 2], }, ];
Итоговый код. Обращаю внимание, что в ключе currentTime данного слоя я указал для примера последнее время unix timestamp из массива выше.
import * as React from "react"; import MapGL, { CustomLayer } from "@urbica/react-map-gl"; import { MapboxLayer } from "@deck.gl/mapbox"; import { TripsLayer } from "@deck.gl/geo-layers"; import "mapbox-gl/dist/mapbox-gl.css"; import tripsData from "./layersData/tripsData"; const App = () => { const [viewport, setViewport] = React.useState({ latitude: 0, longitude: 0, zoom: 1, }); const tripsLayer = new MapboxLayer({ id: "trips-layer", type: TripsLayer, data: tripsData, widthMinPixels: 3, rounded: true, trailLength: 200, currentTime: 1556012340, getPath: (d) => d.coordinates, getTimestamps: (d) => d.timestamps, getColor: (d) => d.color, }); return ( <MapGL style={{ width: "100vw", height: "100vh" }} accessToken={TOKEN} onViewportChange={setViewport} {...viewport} > <CustomLayer layer={tripsLayer} /> </MapGL> ); };

4. Кластеризация
Для ее реализации нам потребуется установить зависимости: yarn add supercluster @urbica/react-map-gl-cluster
Подробнее можно почитать здесь.
Данные для точек запишем следующим образом:
const clusterData = [ { id: 1, name: "First Point", coordinates: [-110.12097640000002, 35.449965], }, { id: 2, name: "Second Point", coordinates: [-112.0893059, 35.39611790000001], }, ];
Нам понадобится написать компоненту, которая будет описывать точку, содержащую в себе несколько других. Например на скриншоте точка содержит в себе две обычные единичные и при приближении распадается.

import React from "react"; import { Marker } from "@urbica/react-map-gl"; import { pointStyle } from "./App"; const ClusterMarker = (props) => ( <Marker longitude={props.longitude} latitude={props.latitude}> <div style={pointStyle}>{props.pointCount}</div> </Marker> );
Главная компонента выглядит так. Для надежности добавил дополнительную проверку координат:
import * as React from "react"; import MapGL, { Marker } from "@urbica/react-map-gl"; import Cluster from "@urbica/react-map-gl-cluster"; import "mapbox-gl/dist/mapbox-gl.css"; import ClusterMarker from "./ClusterMarker"; import clusterData from "./layersData/clusterData"; export const pointStyle = { display: "flex", justifyContent: "center", alignItems: "center", width: "32px", height: "32px", borderRadius: "50%", border: "1px solid black", backgroundColor: "white", }; const App = () => { const [viewport, setViewport] = React.useState({ latitude: 0, longitude: 0, zoom: 1, }); const clusterLayerData = React.useMemo( () => clusterData.map((point) => { const [fCoordinate, sCoordinate] = point.coordinates; const lng = sCoordinate > -90 && sCoordinate < 90 ? fCoordinate : sCoordinate; const lat = lng === fCoordinate ? sCoordinate : fCoordinate; return ( <Marker key={point.id} longitude={lng} latitude={lat}> <div style={pointStyle}>1</div> </Marker> ); }), [] ); return ( <MapGL style={{ width: "100vw", height: "100vh" }} accessToken={TOKEN} onViewportChange={setViewport} {...viewport} > <Cluster radius={40} extent={512} nodeSize={64} component={ClusterMarker} children={clusterLayerData} /> </MapGL> ); };


Заключение
На сайте Deck.gl еще собрано множество разнообразных слоев. Но как вы могли заметить, с помощью Deck.gl и Mapbox можно с легкостью реализовывать достаточно сложный на первый взгляд функционал. Успехов!
ссылка на оригинал статьи https://habr.com/ru/post/553126/
Добавить комментарий