@deck.gl/maplibre 9.4.0-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,309 @@
1
+ // deck.gl
2
+ // SPDX-License-Identifier: MIT
3
+ // Copyright (c) vis.gl contributors
4
+
5
+ import {Deck, MapView, _GlobeView as GlobeView, _flatten as flatten} from '@deck.gl/core';
6
+
7
+ import {
8
+ getMapLibreElevation,
9
+ getMapLibreProjection,
10
+ type MapLibreRenderParameters
11
+ } from './compatibility';
12
+ import {getMapLibreLayerGroupId} from './layer-utils';
13
+
14
+ import type {DeckProps, Layer, MapViewState, Viewport} from '@deck.gl/core';
15
+ import type {Parameters} from '@luma.gl/core';
16
+ import type {Map as MapLibreMap} from 'maplibre-gl';
17
+ import type MapLibreLayerGroup from './layer-group';
18
+ import type {MapLibreLayerProps} from './layer-utils';
19
+
20
+ export const MAPLIBRE_VIEW_ID = 'maplibre';
21
+
22
+ type UserData = {
23
+ currentViewport?: Viewport | null;
24
+ };
25
+
26
+ type MapLibreDeckState = {
27
+ deck: Deck;
28
+ moveListener: () => void;
29
+ renderListener: () => void;
30
+ watchingMove: boolean;
31
+ };
32
+
33
+ const MAPLIBRE_DECK_STATES = new WeakMap<MapLibreMap, MapLibreDeckState>();
34
+
35
+ export function createMapLibreInterleavedDeck(map: MapLibreMap, props: DeckProps): Deck {
36
+ if (MAPLIBRE_DECK_STATES.has(map)) {
37
+ throw new Error('MapLibreOverlay supports one interleaved overlay per map');
38
+ }
39
+
40
+ const gl = map.getCanvas().getContext('webgl2');
41
+ if (!gl) {
42
+ throw new Error('MapLibreOverlay interleaved rendering requires WebGL2.');
43
+ }
44
+
45
+ const {device: _, ...deckProps} = props;
46
+ const deck = new Deck({...deckProps, gl});
47
+ try {
48
+ return createMapLibreDeckInstance(map, deck);
49
+ } catch (error) {
50
+ if (getMapLibreDeckInstance(map) === deck) {
51
+ removeMapLibreDeckInstance(map);
52
+ } else {
53
+ deck.finalize();
54
+ }
55
+ throw error;
56
+ }
57
+ }
58
+
59
+ export function getMapLibreDefaultParameters(_map: MapLibreMap, interleaved: boolean): Parameters {
60
+ return interleaved
61
+ ? {
62
+ depthWriteEnabled: true,
63
+ depthCompare: 'less-equal',
64
+ depthBias: 0,
65
+ blend: true,
66
+ blendColorSrcFactor: 'src-alpha',
67
+ blendColorDstFactor: 'one-minus-src-alpha',
68
+ blendAlphaSrcFactor: 'one',
69
+ blendAlphaDstFactor: 'one-minus-src-alpha',
70
+ blendColorOperation: 'add',
71
+ blendAlphaOperation: 'add'
72
+ }
73
+ : {};
74
+ }
75
+
76
+ export function getMapLibreDefaultView(map: MapLibreMap): GlobeView | MapView {
77
+ return getMapLibreProjection(map) === 'globe'
78
+ ? new GlobeView({id: MAPLIBRE_VIEW_ID})
79
+ : new MapView({id: MAPLIBRE_VIEW_ID});
80
+ }
81
+
82
+ export function getMapLibreViewState(map: MapLibreMap): MapViewState & {
83
+ repeat: boolean;
84
+ padding: {top: number; bottom: number; left: number; right: number};
85
+ } {
86
+ const {lng, lat} = map.getCenter();
87
+ const padding = map.getPadding();
88
+ const viewState: MapViewState & {
89
+ repeat: boolean;
90
+ padding: {top: number; bottom: number; left: number; right: number};
91
+ } = {
92
+ longitude: ((lng + 540) % 360) - 180,
93
+ latitude: lat,
94
+ zoom: map.getZoom(),
95
+ bearing: map.getBearing(),
96
+ pitch: map.getPitch(),
97
+ padding: {
98
+ top: padding.top ?? 0,
99
+ bottom: padding.bottom ?? 0,
100
+ left: padding.left ?? 0,
101
+ right: padding.right ?? 0
102
+ },
103
+ repeat: map.getRenderWorldCopies()
104
+ };
105
+
106
+ const elevation = getMapLibreElevation(map);
107
+ if (typeof elevation === 'number' && Number.isFinite(elevation)) {
108
+ viewState.position = [0, 0, elevation];
109
+ }
110
+
111
+ return viewState;
112
+ }
113
+
114
+ export function createMapLibreDeckInstance(map: MapLibreMap, deck: Deck): Deck {
115
+ const existingState = MAPLIBRE_DECK_STATES.get(map);
116
+ if (existingState) {
117
+ throw new Error('MapLibreOverlay supports one interleaved overlay per map');
118
+ }
119
+
120
+ const customRender = deck.props._customRender;
121
+ const onLoad = deck.props.onLoad;
122
+ const state: MapLibreDeckState = {
123
+ deck,
124
+ watchingMove: false,
125
+ moveListener: () => {
126
+ if (deck.isInitialized) {
127
+ onMapLibreMove(deck, map);
128
+ } else {
129
+ stopWatchingMove(map, state);
130
+ }
131
+ },
132
+ renderListener: () => {
133
+ if (deck.isInitialized) {
134
+ afterMapLibreRender(deck, map);
135
+ }
136
+ }
137
+ };
138
+ MAPLIBRE_DECK_STATES.set(map, state);
139
+
140
+ const deckProps: any = {
141
+ ...deck.props,
142
+ _customRender: () => {
143
+ map.triggerRepaint();
144
+ customRender?.('');
145
+ }
146
+ };
147
+ deckProps.views ||= getMapLibreDefaultView(map);
148
+ Object.assign(deckProps, {
149
+ width: null,
150
+ height: null,
151
+ touchAction: 'unset',
152
+ viewState: getMapLibreViewState(map)
153
+ });
154
+
155
+ if (deck.isInitialized) {
156
+ startWatchingMove(map, state);
157
+ } else {
158
+ deckProps.onLoad = () => {
159
+ onLoad?.();
160
+ if (MAPLIBRE_DECK_STATES.get(map) === state) {
161
+ startWatchingMove(map, state);
162
+ }
163
+ };
164
+ }
165
+
166
+ deck.setProps(deckProps);
167
+ map.on('render', state.renderListener);
168
+ return deck;
169
+ }
170
+
171
+ export function getMapLibreDeckInstance(map: MapLibreMap): Deck | undefined {
172
+ return MAPLIBRE_DECK_STATES.get(map)?.deck;
173
+ }
174
+
175
+ export function removeMapLibreDeckInstance(map: MapLibreMap): void {
176
+ const state = MAPLIBRE_DECK_STATES.get(map);
177
+ if (!state) {
178
+ return;
179
+ }
180
+
181
+ stopWatchingMove(map, state);
182
+ map.off('render', state.renderListener);
183
+ state.deck.finalize();
184
+ MAPLIBRE_DECK_STATES.delete(map);
185
+ }
186
+
187
+ export function drawMapLibreLayerGroup(
188
+ deck: Deck,
189
+ map: MapLibreMap,
190
+ group: MapLibreLayerGroup,
191
+ renderParameters: MapLibreRenderParameters
192
+ ): void {
193
+ if (!deck.isInitialized) {
194
+ return;
195
+ }
196
+
197
+ let {currentViewport} = deck.userData as UserData;
198
+ let clearStack = false;
199
+ if (!currentViewport) {
200
+ currentViewport = getMapLibreViewport(deck, map, renderParameters);
201
+ (deck.userData as UserData).currentViewport = currentViewport;
202
+ clearStack = true;
203
+ }
204
+
205
+ if (!currentViewport) {
206
+ return;
207
+ }
208
+
209
+ deck._drawLayers('maplibre-repaint', {
210
+ viewports: [currentViewport],
211
+ layerFilter: params => {
212
+ if (deck.props.layerFilter && !deck.props.layerFilter(params)) {
213
+ return false;
214
+ }
215
+
216
+ const layer = params.layer as Layer<MapLibreLayerProps>;
217
+ return layer.props.beforeId === group.beforeId;
218
+ },
219
+ clearStack,
220
+ clearCanvas: false
221
+ });
222
+ }
223
+
224
+ function startWatchingMove(map: MapLibreMap, state: MapLibreDeckState): void {
225
+ if (!state.watchingMove) {
226
+ state.watchingMove = true;
227
+ map.on('move', state.moveListener);
228
+ }
229
+ }
230
+
231
+ function stopWatchingMove(map: MapLibreMap, state: MapLibreDeckState): void {
232
+ if (state.watchingMove) {
233
+ state.watchingMove = false;
234
+ map.off('move', state.moveListener);
235
+ }
236
+ }
237
+
238
+ function getMapLibreViewport(
239
+ deck: Deck,
240
+ map: MapLibreMap,
241
+ renderParameters?: MapLibreRenderParameters
242
+ ): Viewport | null {
243
+ const viewState = getMapLibreViewState(map);
244
+ const view = (deck.getView(MAPLIBRE_VIEW_ID) || getMapLibreDefaultView(map)) as
245
+ | MapView
246
+ | GlobeView;
247
+
248
+ if (renderParameters) {
249
+ view.props.nearZMultiplier = 0.2;
250
+ const height = map.getCanvas().clientHeight;
251
+ if (
252
+ height > 0 &&
253
+ Number.isFinite(renderParameters.nearZ) &&
254
+ Number.isFinite(renderParameters.farZ)
255
+ ) {
256
+ viewState.nearZ = renderParameters.nearZ / height;
257
+ viewState.farZ = renderParameters.farZ / height;
258
+ }
259
+ }
260
+
261
+ return view.makeViewport({
262
+ width: deck.width,
263
+ height: deck.height,
264
+ viewState
265
+ });
266
+ }
267
+
268
+ function afterMapLibreRender(deck: Deck, map: MapLibreMap): void {
269
+ const deckLayers = flatten(deck.props.layers, Boolean) as Layer<MapLibreLayerProps>[];
270
+ const hasNonMapLibreLayers = deckLayers.some(
271
+ layer => layer && !map.getLayer(getMapLibreLayerGroupId(layer))
272
+ );
273
+ let viewports = deck.getViewports();
274
+ const mapLibreViewportIndex = viewports.findIndex(viewport => viewport.id === MAPLIBRE_VIEW_ID);
275
+ const hasNonMapLibreViews = viewports.length > 1 || mapLibreViewportIndex < 0;
276
+
277
+ if (hasNonMapLibreLayers || hasNonMapLibreViews) {
278
+ if (mapLibreViewportIndex >= 0) {
279
+ viewports = viewports.slice();
280
+ const mapLibreViewport = getMapLibreViewport(deck, map);
281
+ if (mapLibreViewport) {
282
+ viewports[mapLibreViewportIndex] = mapLibreViewport;
283
+ } else {
284
+ viewports.splice(mapLibreViewportIndex, 1);
285
+ }
286
+ }
287
+
288
+ deck._drawLayers('maplibre-repaint', {
289
+ viewports,
290
+ layerFilter: params =>
291
+ (!deck.props.layerFilter || deck.props.layerFilter(params)) &&
292
+ (params.viewport.id !== MAPLIBRE_VIEW_ID ||
293
+ !map.getLayer(getMapLibreLayerGroupId(params.layer as Layer<MapLibreLayerProps>))),
294
+ clearCanvas: false
295
+ });
296
+ } else {
297
+ const device = (deck as any).device;
298
+ const gl = device?.gl;
299
+ deck.props.onBeforeRender?.({device, gl});
300
+ deck.props.onAfterRender?.({device, gl});
301
+ }
302
+
303
+ (deck.userData as UserData).currentViewport = null;
304
+ }
305
+
306
+ function onMapLibreMove(deck: Deck, map: MapLibreMap): void {
307
+ deck.setProps({viewState: getMapLibreViewState(map)});
308
+ deck.needsRedraw({clearRedrawFlags: true});
309
+ }
package/src/index.ts ADDED
@@ -0,0 +1,6 @@
1
+ // deck.gl
2
+ // SPDX-License-Identifier: MIT
3
+ // Copyright (c) vis.gl contributors
4
+
5
+ export {default as MapLibreOverlay} from './overlay';
6
+ export type {MapLibreOverlayProps} from './overlay';
@@ -0,0 +1,62 @@
1
+ // deck.gl
2
+ // SPDX-License-Identifier: MIT
3
+ // Copyright (c) vis.gl contributors
4
+
5
+ import {assert} from '@deck.gl/core';
6
+
7
+ import {getMapLibreRenderParameters} from './compatibility';
8
+ import {drawMapLibreLayerGroup, getMapLibreDeckInstance} from './deck-utils';
9
+
10
+ import type {CustomLayerInterface, Map as MapLibreMap} from 'maplibre-gl';
11
+
12
+ export type MapLibreLayerGroupProps = {
13
+ id: string;
14
+ renderingMode?: '2d' | '3d';
15
+ beforeId?: string;
16
+ };
17
+
18
+ export default class MapLibreLayerGroup implements CustomLayerInterface {
19
+ readonly id: string;
20
+ readonly type = 'custom' as const;
21
+ readonly renderingMode: '2d' | '3d';
22
+ readonly beforeId?: string;
23
+
24
+ private map: MapLibreMap | null = null;
25
+
26
+ constructor(props: MapLibreLayerGroupProps) {
27
+ assert(props.id, 'id is required');
28
+ this.id = props.id;
29
+ this.renderingMode = props.renderingMode || '3d';
30
+ this.beforeId = props.beforeId;
31
+ }
32
+
33
+ onAdd(map: MapLibreMap): void {
34
+ this.map = map;
35
+ }
36
+
37
+ onRemove(): void {
38
+ this.map = null;
39
+ }
40
+
41
+ render(
42
+ _gl: WebGLRenderingContext | WebGL2RenderingContext,
43
+ parametersOrMatrix: unknown,
44
+ legacyParameters?: unknown
45
+ ): void {
46
+ if (!this.map) {
47
+ return;
48
+ }
49
+
50
+ const deck = getMapLibreDeckInstance(this.map);
51
+ if (!deck) {
52
+ return;
53
+ }
54
+
55
+ drawMapLibreLayerGroup(
56
+ deck,
57
+ this.map,
58
+ this,
59
+ getMapLibreRenderParameters(parametersOrMatrix, legacyParameters)
60
+ );
61
+ }
62
+ }
@@ -0,0 +1,17 @@
1
+ // deck.gl
2
+ // SPDX-License-Identifier: MIT
3
+ // Copyright (c) vis.gl contributors
4
+
5
+ import type {Layer} from '@deck.gl/core';
6
+
7
+ export type MapLibreLayerProps = {
8
+ beforeId?: string;
9
+ };
10
+
11
+ export const MAPLIBRE_LAST_LAYER_GROUP_ID = 'deck-maplibre-layer-group-last';
12
+
13
+ export function getMapLibreLayerGroupId(layer: Layer<MapLibreLayerProps>): string {
14
+ return layer.props.beforeId
15
+ ? `deck-maplibre-layer-group-before:${layer.props.beforeId}`
16
+ : MAPLIBRE_LAST_LAYER_GROUP_ID;
17
+ }