From f8460878385c5820286c5f853b3913a88e4ee907 Mon Sep 17 00:00:00 2001 From: Patrick Browne Date: Mon, 2 May 2022 10:43:17 +0200 Subject: [PATCH] feat: Support for custom mapbox layer Instead of using DeckGl as the container for Mapbox, we use Mapbox as the container for DeckGl layer. This lets us insert the deckgl layer between the mapbox layers, which is very handy when dealing with labels. We use DeckGL's custom Mapbox layer here. Since DeckGL's custom layer need method on the prototype, we can copy the layer props with spreading, otherwise we lose the methods. This is why the `layer` prop is added on the Layer so that we can pass the custom layer without any copying. --- examples/deckgl-overlay/src/app.tsx | 54 ++++++++++-------- examples/deckgl-overlay/src/control-panel.tsx | 32 +++++++++++ src/components/layer.ts | 57 +++++++++++++------ 3 files changed, 103 insertions(+), 40 deletions(-) create mode 100644 examples/deckgl-overlay/src/control-panel.tsx diff --git a/examples/deckgl-overlay/src/app.tsx b/examples/deckgl-overlay/src/app.tsx index 1c0046c99..7dc5a25af 100644 --- a/examples/deckgl-overlay/src/app.tsx +++ b/examples/deckgl-overlay/src/app.tsx @@ -1,34 +1,42 @@ import * as React from 'react'; import {render} from 'react-dom'; -import DeckGL, {ArcLayer} from 'deck.gl'; -import Map from 'react-map-gl'; +import {ArcLayer} from 'deck.gl'; +import {MapboxLayer} from '@deck.gl/mapbox'; +import Map, {Layer, MapboxMap} from 'react-map-gl'; +import ControlPanel from './control-panel'; const TOKEN = ''; // Set your mapbox token here export default function App() { - const arcLayer = new ArcLayer({ - data: 'https://raw.githubusercontent.com/visgl/deck.gl-data/master/website/bart-segments.json', - getSourcePosition: d => d.from.coordinates, - getTargetPosition: d => d.to.coordinates, - getSourceColor: [255, 200, 0], - getTargetColor: [0, 140, 255], - getWidth: 12 - }); + const [overLabels, setOverLabels] = React.useState(true); + const layer = React.useMemo(() => { + return new MapboxLayer({ + id: 'arcs', + type: ArcLayer, + data: 'https://raw.githubusercontent.com/visgl/deck.gl-data/master/website/bart-segments.json', + getSourcePosition: d => d.from.coordinates, + getTargetPosition: d => d.to.coordinates, + getSourceColor: [255, 200, 0], + getTargetColor: [0, 140, 255], + getWidth: 12 + }); + }, []); return ( - - - + <> + + + + + ); } diff --git a/examples/deckgl-overlay/src/control-panel.tsx b/examples/deckgl-overlay/src/control-panel.tsx new file mode 100644 index 000000000..48d1f1e96 --- /dev/null +++ b/examples/deckgl-overlay/src/control-panel.tsx @@ -0,0 +1,32 @@ +import React from 'react'; + +function Checkbox({name, value, onChange}) { + return ( +
+ + onChange(name, evt.target.checked)} /> +
+ ); +} + +function ControlPanel({ + overLabels, + setOverLabels +}: { + overLabels: boolean; + setOverLabels: (v: boolean) => void; +}) { + return ( +
+

Deck.gl layer

+

A deck.gl overlay can be used and inserted inside the Mapbox layers.

+ setOverLabels(v)} + /> +
+ ); +} + +export default ControlPanel; diff --git a/src/components/layer.ts b/src/components/layer.ts index 5c17c224e..4d343ec75 100644 --- a/src/components/layer.ts +++ b/src/components/layer.ts @@ -5,14 +5,21 @@ import {deepEqual} from '../utils/deep-equal'; import type {MapboxMap, AnyLayer} from '../types'; -export type LayerProps = AnyLayer & { +export type DeprecatedLayerProps = AnyLayer & { id?: string; /** If set, the layer will be inserted before the specified layer */ beforeId?: string; }; +export type LayerProps = + | { + layer: AnyLayer; + beforeId?: string; + } + | DeprecatedLayerProps; + /* eslint-disable complexity, max-statements */ -function updateLayer(map: MapboxMap, id: string, props: LayerProps, prevProps: LayerProps) { +function updateLayer(map: MapboxMap, id: string, props: AnyLayer, prevProps: AnyLayer) { assert(props.id === prevProps.id, 'layer id changed'); assert(props.type === prevProps.type, 'layer type changed'); @@ -20,11 +27,8 @@ function updateLayer(map: MapboxMap, id: string, props: LayerProps, prevProps: L return; } - const {layout = {}, paint = {}, filter, minzoom, maxzoom, beforeId} = props; + const {layout = {}, paint = {}, filter, minzoom, maxzoom} = props; - if (beforeId !== prevProps.beforeId) { - map.moveLayer(id, beforeId); - } if (layout !== prevProps.layout) { const prevLayout = prevProps.layout || {}; for (const key in layout) { @@ -59,14 +63,16 @@ function updateLayer(map: MapboxMap, id: string, props: LayerProps, prevProps: L } } -function createLayer(map: MapboxMap, id: string, props: LayerProps) { +function isMapStyleLoaded(map: MapboxMap) { // @ts-ignore - if (map.style && map.style._loaded && (!('source' in props) || map.getSource(props.source))) { - const options: LayerProps = {...props, id}; - delete options.beforeId; + return map.style && map.style._loaded; +} +function createLayer(map: MapboxMap, id: string, layerProps: LayerProps, beforeId: string) { + // @ts-ignore + if (isMapStyleLoaded(map) && (!('source' in layerProps) || map.getSource(layerProps.source))) { // @ts-ignore - map.addLayer(options, props.beforeId); + map.addLayer(layerProps, beforeId); } } @@ -74,12 +80,23 @@ function createLayer(map: MapboxMap, id: string, props: LayerProps) { let layerCounter = 0; -function Layer(props: LayerProps) { +function Layer(props: LayerProps & {layer?: AnyLayer}) { const map: MapboxMap = useContext(MapContext).map.getMap(); - const propsRef = useRef(props); + + const layerProps = useMemo(() => { + if (props.layer) { + return props.layer; + } + const res = {...props}; + delete res.beforeId; + return res as DeprecatedLayerProps; + }, [props.layer, props]); + + const layerPropsRef = useRef(layerProps); const [, setStyleLoaded] = useState(0); + const beforeId = props.beforeId; - const id = useMemo(() => props.id || `jsx-layer-${layerCounter++}`, []); + const id = useMemo(() => layerProps.id || `jsx-layer-${layerCounter++}`, []); useEffect(() => { if (map) { @@ -102,16 +119,22 @@ function Layer(props: LayerProps) { const layer = map && map.style && map.getLayer(id); if (layer) { try { - updateLayer(map, id, props, propsRef.current); + updateLayer(map, id, layerProps, layerPropsRef.current); } catch (error) { console.warn(error); // eslint-disable-line } } else { - createLayer(map, id, props); + createLayer(map, id, layerProps, beforeId); } + useEffect(() => { + if (beforeId && isMapStyleLoaded(map)) { + map.moveLayer(id, beforeId); + } + }, [beforeId]); + // Store last rendered props - propsRef.current = props; + layerPropsRef.current = layerProps; return null; }