Skip to content

Commit f846087

Browse files
committed
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.
1 parent b2bdb91 commit f846087

File tree

3 files changed

+103
-40
lines changed

3 files changed

+103
-40
lines changed

examples/deckgl-overlay/src/app.tsx

+31-23
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,42 @@
11
import * as React from 'react';
22
import {render} from 'react-dom';
3-
import DeckGL, {ArcLayer} from 'deck.gl';
4-
import Map from 'react-map-gl';
3+
import {ArcLayer} from 'deck.gl';
4+
import {MapboxLayer} from '@deck.gl/mapbox';
5+
import Map, {Layer, MapboxMap} from 'react-map-gl';
6+
import ControlPanel from './control-panel';
57

68
const TOKEN = ''; // Set your mapbox token here
79

810
export default function App() {
9-
const arcLayer = new ArcLayer({
10-
data: 'https://raw.githubusercontent.com/visgl/deck.gl-data/master/website/bart-segments.json',
11-
getSourcePosition: d => d.from.coordinates,
12-
getTargetPosition: d => d.to.coordinates,
13-
getSourceColor: [255, 200, 0],
14-
getTargetColor: [0, 140, 255],
15-
getWidth: 12
16-
});
11+
const [overLabels, setOverLabels] = React.useState(true);
12+
const layer = React.useMemo(() => {
13+
return new MapboxLayer({
14+
id: 'arcs',
15+
type: ArcLayer,
16+
data: 'https://raw.githubusercontent.com/visgl/deck.gl-data/master/website/bart-segments.json',
17+
getSourcePosition: d => d.from.coordinates,
18+
getTargetPosition: d => d.to.coordinates,
19+
getSourceColor: [255, 200, 0],
20+
getTargetColor: [0, 140, 255],
21+
getWidth: 12
22+
});
23+
}, []);
1724

1825
return (
19-
<DeckGL
20-
initialViewState={{
21-
longitude: -122.45,
22-
latitude: 37.75,
23-
zoom: 11,
24-
bearing: 0,
25-
pitch: 60
26-
}}
27-
controller={true}
28-
layers={[arcLayer]}
29-
>
30-
<Map mapStyle="mapbox://styles/mapbox/light-v9" mapboxAccessToken={TOKEN} />
31-
</DeckGL>
26+
<>
27+
<ControlPanel overLabels={overLabels} setOverLabels={setOverLabels} />
28+
<Map
29+
initialViewState={{
30+
zoom: 9,
31+
longitude: -122.431297,
32+
latitude: 37.787994
33+
}}
34+
mapStyle="mapbox://styles/mapbox/light-v9"
35+
mapboxAccessToken={TOKEN}
36+
>
37+
<Layer layer={layer} beforeId={overLabels ? 'water-label' : 'country-label-lg'} />
38+
</Map>
39+
</>
3240
);
3341
}
3442

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import React from 'react';
2+
3+
function Checkbox({name, value, onChange}) {
4+
return (
5+
<div key={name} className="input">
6+
<label>{name}</label>
7+
<input type="checkbox" checked={value} onChange={evt => onChange(name, evt.target.checked)} />
8+
</div>
9+
);
10+
}
11+
12+
function ControlPanel({
13+
overLabels,
14+
setOverLabels
15+
}: {
16+
overLabels: boolean;
17+
setOverLabels: (v: boolean) => void;
18+
}) {
19+
return (
20+
<div className="control-panel">
21+
<h3>Deck.gl layer</h3>
22+
<p>A deck.gl overlay can be used and inserted inside the Mapbox layers.</p>
23+
<Checkbox
24+
name="Arcs over labels"
25+
value={overLabels}
26+
onChange={(name: string, v: boolean) => setOverLabels(v)}
27+
/>
28+
</div>
29+
);
30+
}
31+
32+
export default ControlPanel;

src/components/layer.ts

+40-17
Original file line numberDiff line numberDiff line change
@@ -5,26 +5,30 @@ import {deepEqual} from '../utils/deep-equal';
55

66
import type {MapboxMap, AnyLayer} from '../types';
77

8-
export type LayerProps = AnyLayer & {
8+
export type DeprecatedLayerProps = AnyLayer & {
99
id?: string;
1010
/** If set, the layer will be inserted before the specified layer */
1111
beforeId?: string;
1212
};
1313

14+
export type LayerProps =
15+
| {
16+
layer: AnyLayer;
17+
beforeId?: string;
18+
}
19+
| DeprecatedLayerProps;
20+
1421
/* eslint-disable complexity, max-statements */
15-
function updateLayer(map: MapboxMap, id: string, props: LayerProps, prevProps: LayerProps) {
22+
function updateLayer(map: MapboxMap, id: string, props: AnyLayer, prevProps: AnyLayer) {
1623
assert(props.id === prevProps.id, 'layer id changed');
1724
assert(props.type === prevProps.type, 'layer type changed');
1825

1926
if (props.type === 'custom' || prevProps.type === 'custom') {
2027
return;
2128
}
2229

23-
const {layout = {}, paint = {}, filter, minzoom, maxzoom, beforeId} = props;
30+
const {layout = {}, paint = {}, filter, minzoom, maxzoom} = props;
2431

25-
if (beforeId !== prevProps.beforeId) {
26-
map.moveLayer(id, beforeId);
27-
}
2832
if (layout !== prevProps.layout) {
2933
const prevLayout = prevProps.layout || {};
3034
for (const key in layout) {
@@ -59,27 +63,40 @@ function updateLayer(map: MapboxMap, id: string, props: LayerProps, prevProps: L
5963
}
6064
}
6165

62-
function createLayer(map: MapboxMap, id: string, props: LayerProps) {
66+
function isMapStyleLoaded(map: MapboxMap) {
6367
// @ts-ignore
64-
if (map.style && map.style._loaded && (!('source' in props) || map.getSource(props.source))) {
65-
const options: LayerProps = {...props, id};
66-
delete options.beforeId;
68+
return map.style && map.style._loaded;
69+
}
6770

71+
function createLayer(map: MapboxMap, id: string, layerProps: LayerProps, beforeId: string) {
72+
// @ts-ignore
73+
if (isMapStyleLoaded(map) && (!('source' in layerProps) || map.getSource(layerProps.source))) {
6874
// @ts-ignore
69-
map.addLayer(options, props.beforeId);
75+
map.addLayer(layerProps, beforeId);
7076
}
7177
}
7278

7379
/* eslint-enable complexity, max-statements */
7480

7581
let layerCounter = 0;
7682

77-
function Layer(props: LayerProps) {
83+
function Layer(props: LayerProps & {layer?: AnyLayer}) {
7884
const map: MapboxMap = useContext(MapContext).map.getMap();
79-
const propsRef = useRef(props);
85+
86+
const layerProps = useMemo(() => {
87+
if (props.layer) {
88+
return props.layer;
89+
}
90+
const res = {...props};
91+
delete res.beforeId;
92+
return res as DeprecatedLayerProps;
93+
}, [props.layer, props]);
94+
95+
const layerPropsRef = useRef(layerProps);
8096
const [, setStyleLoaded] = useState(0);
97+
const beforeId = props.beforeId;
8198

82-
const id = useMemo(() => props.id || `jsx-layer-${layerCounter++}`, []);
99+
const id = useMemo(() => layerProps.id || `jsx-layer-${layerCounter++}`, []);
83100

84101
useEffect(() => {
85102
if (map) {
@@ -102,16 +119,22 @@ function Layer(props: LayerProps) {
102119
const layer = map && map.style && map.getLayer(id);
103120
if (layer) {
104121
try {
105-
updateLayer(map, id, props, propsRef.current);
122+
updateLayer(map, id, layerProps, layerPropsRef.current);
106123
} catch (error) {
107124
console.warn(error); // eslint-disable-line
108125
}
109126
} else {
110-
createLayer(map, id, props);
127+
createLayer(map, id, layerProps, beforeId);
111128
}
112129

130+
useEffect(() => {
131+
if (beforeId && isMapStyleLoaded(map)) {
132+
map.moveLayer(id, beforeId);
133+
}
134+
}, [beforeId]);
135+
113136
// Store last rendered props
114-
propsRef.current = props;
137+
layerPropsRef.current = layerProps;
115138

116139
return null;
117140
}

0 commit comments

Comments
 (0)