• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

visgl / deck.gl / 16250138754

13 Jul 2025 02:25PM UTC coverage: 91.678% (-0.005%) from 91.683%
16250138754

push

github

web-flow
chore: Bump to luma.gl@9.2.0-alpha (#9241)

6765 of 7433 branches covered (91.01%)

Branch coverage included in aggregate %.

107 of 112 new or added lines in 26 files covered. (95.54%)

16 existing lines in 4 files now uncovered.

55842 of 60857 relevant lines covered (91.76%)

14490.8 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

69.65
/modules/mapbox/src/mapbox-overlay.ts
1
// deck.gl
1✔
2
// SPDX-License-Identifier: MIT
1✔
3
// Copyright (c) vis.gl contributors
1✔
4

1✔
5
import {Deck, assert} from '@deck.gl/core';
1✔
6
import {
1✔
7
  getViewState,
1✔
8
  getDefaultView,
1✔
9
  getDeckInstance,
1✔
10
  removeDeckInstance,
1✔
11
  getDefaultParameters
1✔
12
} from './deck-utils';
1✔
13

1✔
14
import type {Map, IControl, MapMouseEvent, ControlPosition} from './types';
1✔
15
import type {MjolnirGestureEvent, MjolnirPointerEvent} from 'mjolnir.js';
1✔
16
import type {DeckProps} from '@deck.gl/core';
1✔
17
import {log} from '@deck.gl/core';
1✔
18

1✔
19
import {resolveLayers} from './resolve-layers';
1✔
20

1✔
21
export type MapboxOverlayProps = Omit<
1✔
22
  DeckProps,
1✔
23
  | 'width'
1✔
24
  | 'height'
1✔
25
  | 'gl'
1✔
26
  | 'parent'
1✔
27
  | 'canvas'
1✔
28
  | '_customRender'
1✔
29
  | 'viewState'
1✔
30
  | 'initialViewState'
1✔
31
  | 'controller'
1✔
32
> & {
1✔
33
  interleaved?: boolean;
1✔
34
};
1✔
35

1✔
36
/**
1✔
37
 * Implements Mapbox [IControl](https://docs.mapbox.com/mapbox-gl-js/api/markers/#icontrol) interface
1✔
38
 * Renders deck.gl layers over the base map and automatically synchronizes with the map's camera
1✔
39
 */
1✔
40
export default class MapboxOverlay implements IControl {
1✔
41
  private _props: MapboxOverlayProps;
1✔
42
  private _deck?: Deck<any>;
1✔
43
  private _map?: Map;
1✔
44
  private _container?: HTMLDivElement;
1✔
45
  private _interleaved: boolean;
1✔
46
  private _lastMouseDownPoint?: {x: number; y: number; clientX: number; clientY: number};
1✔
47

1✔
48
  constructor(props: MapboxOverlayProps) {
1✔
49
    const {interleaved = false, ...otherProps} = props;
6✔
50
    this._interleaved = interleaved;
6✔
51
    this._props = otherProps;
6✔
52
  }
6✔
53

1✔
54
  /** Update (partial) props of the underlying Deck instance. */
1✔
55
  setProps(props: MapboxOverlayProps): void {
1✔
56
    if (this._interleaved && props.layers) {
4✔
57
      resolveLayers(this._map, this._deck, this._props.layers, props.layers);
2✔
58
    }
2✔
59

4✔
60
    Object.assign(this._props, props);
4✔
61

4✔
62
    if (this._deck && this._map) {
4✔
63
      this._deck.setProps({
4✔
64
        ...this._props,
4✔
65
        parameters: {
4✔
66
          ...getDefaultParameters(this._map, this._interleaved),
4✔
67
          ...this._props.parameters
4✔
68
        }
4✔
69
      });
4✔
70
    }
4✔
71
  }
4✔
72

1✔
73
  // The local Map type is for internal typecheck only. It does not necesarily satisefy mapbox/maplibre types at runtime.
1✔
74
  // Do not restrict the argument type here to avoid type conflict.
1✔
75
  /** Called when the control is added to a map */
1✔
76
  onAdd(map: unknown): HTMLDivElement {
1✔
77
    this._map = map as Map;
7✔
78
    return this._interleaved ? this._onAddInterleaved(map as Map) : this._onAddOverlaid(map as Map);
7✔
79
  }
7✔
80

1✔
81
  private _onAddOverlaid(map: Map): HTMLDivElement {
1✔
82
    /* global document */
2✔
83
    const container = document.createElement('div');
2✔
84
    Object.assign(container.style, {
2✔
85
      position: 'absolute',
2✔
86
      left: 0,
2✔
87
      top: 0,
2✔
88
      textAlign: 'initial',
2✔
89
      pointerEvents: 'none'
2✔
90
    });
2✔
91
    this._container = container;
2✔
92

2✔
93
    this._deck = new Deck<any>({
2✔
94
      ...this._props,
2✔
95
      parent: container,
2✔
96
      parameters: {...getDefaultParameters(map, false), ...this._props.parameters},
2✔
97
      views: this._props.views || getDefaultView(map),
2✔
98
      viewState: getViewState(map)
2✔
99
    });
2✔
100

2✔
101
    map.on('resize', this._updateContainerSize);
2✔
102
    map.on('render', this._updateViewState);
2✔
103
    map.on('mousedown', this._handleMouseEvent);
2✔
104
    map.on('dragstart', this._handleMouseEvent);
2✔
105
    map.on('drag', this._handleMouseEvent);
2✔
106
    map.on('dragend', this._handleMouseEvent);
2✔
107
    map.on('mousemove', this._handleMouseEvent);
2✔
108
    map.on('mouseout', this._handleMouseEvent);
2✔
109
    map.on('click', this._handleMouseEvent);
2✔
110
    map.on('dblclick', this._handleMouseEvent);
2✔
111

2✔
112
    this._updateContainerSize();
2✔
113
    return container;
2✔
114
  }
2✔
115

1✔
116
  private _onAddInterleaved(map: Map): HTMLDivElement {
1✔
117
    // @ts-ignore non-public map property
5✔
118
    const gl = map.painter.context.gl;
5✔
119
    if (gl instanceof WebGLRenderingContext) {
5!
120
      log.warn(
×
121
        'Incompatible basemap library. See: https://deck.gl/docs/api-reference/mapbox/overview#compatibility'
×
122
      )();
×
123
    }
×
124
    this._deck = getDeckInstance({
5✔
125
      map,
5✔
126
      gl,
5✔
127
      deck: new Deck({
5✔
128
        ...this._props,
5✔
129
        gl
5✔
130
      })
5✔
131
    });
5✔
132

5✔
133
    map.on('styledata', this._handleStyleChange);
5✔
134
    resolveLayers(map, this._deck, [], this._props.layers);
5✔
135

5✔
136
    return document.createElement('div');
5✔
137
  }
5✔
138

1✔
139
  /** Called when the control is removed from a map */
1✔
140
  onRemove(): void {
1✔
141
    const map = this._map;
7✔
142

7✔
143
    if (map) {
7✔
144
      if (this._interleaved) {
7✔
145
        this._onRemoveInterleaved(map);
5✔
146
      } else {
7✔
147
        this._onRemoveOverlaid(map);
2✔
148
      }
2✔
149
    }
7✔
150

7✔
151
    this._deck = undefined;
7✔
152
    this._map = undefined;
7✔
153
    this._container = undefined;
7✔
154
  }
7✔
155

1✔
156
  private _onRemoveOverlaid(map: Map): void {
1✔
157
    map.off('resize', this._updateContainerSize);
2✔
158
    map.off('render', this._updateViewState);
2✔
159
    map.off('mousedown', this._handleMouseEvent);
2✔
160
    map.off('dragstart', this._handleMouseEvent);
2✔
161
    map.off('drag', this._handleMouseEvent);
2✔
162
    map.off('dragend', this._handleMouseEvent);
2✔
163
    map.off('mousemove', this._handleMouseEvent);
2✔
164
    map.off('mouseout', this._handleMouseEvent);
2✔
165
    map.off('click', this._handleMouseEvent);
2✔
166
    map.off('dblclick', this._handleMouseEvent);
2✔
167
    this._deck?.finalize();
2✔
168
  }
2✔
169

1✔
170
  private _onRemoveInterleaved(map: Map): void {
1✔
171
    map.off('styledata', this._handleStyleChange);
5✔
172
    resolveLayers(map, this._deck, this._props.layers, []);
5✔
173
    removeDeckInstance(map);
5✔
174
  }
5✔
175

1✔
176
  getDefaultPosition(): ControlPosition {
1✔
177
    return 'top-left';
×
178
  }
×
179

1✔
180
  /** Forwards the Deck.pickObject method */
1✔
181
  pickObject(params: Parameters<Deck['pickObject']>[0]): ReturnType<Deck['pickObject']> {
1✔
182
    assert(this._deck);
×
183
    return this._deck.pickObject(params);
×
184
  }
×
185

1✔
186
  /** Forwards the Deck.pickMultipleObjects method */
1✔
187
  pickMultipleObjects(
1✔
188
    params: Parameters<Deck['pickMultipleObjects']>[0]
×
189
  ): ReturnType<Deck['pickMultipleObjects']> {
×
190
    assert(this._deck);
×
191
    return this._deck.pickMultipleObjects(params);
×
192
  }
×
193

1✔
194
  /** Forwards the Deck.pickObjects method */
1✔
195
  pickObjects(params: Parameters<Deck['pickObjects']>[0]): ReturnType<Deck['pickObjects']> {
1✔
196
    assert(this._deck);
×
197
    return this._deck.pickObjects(params);
×
198
  }
×
199

1✔
200
  /** Remove from map and releases all resources */
1✔
201
  finalize() {
1✔
202
    if (this._map) {
1✔
203
      this._map.removeControl(this);
1✔
204
    }
1✔
205
  }
1✔
206

1✔
207
  /** If interleaved: true, returns base map's canvas, otherwise forwards the Deck.getCanvas method. */
1✔
208
  getCanvas(): HTMLCanvasElement | null {
1✔
209
    if (!this._map) {
×
210
      return null;
×
211
    }
×
212
    return this._interleaved ? this._map.getCanvas() : this._deck!.getCanvas();
×
213
  }
×
214

1✔
215
  private _handleStyleChange = () => {
1✔
216
    resolveLayers(this._map, this._deck, this._props.layers, this._props.layers);
3✔
217
  };
3✔
218

1✔
219
  private _updateContainerSize = () => {
1✔
220
    if (this._map && this._container) {
2✔
221
      const {clientWidth, clientHeight} = this._map.getContainer();
2✔
222
      Object.assign(this._container.style, {
2✔
223
        width: `${clientWidth}px`,
2✔
224
        height: `${clientHeight}px`
2✔
225
      });
2✔
226
    }
2✔
227
  };
2✔
228

1✔
229
  private _updateViewState = () => {
1✔
230
    const deck = this._deck;
2✔
231
    const map = this._map;
2✔
232
    if (deck && map) {
2✔
233
      deck.setProps({
2✔
234
        views: this._props.views || getDefaultView(map),
2✔
235
        viewState: getViewState(map)
2✔
236
      });
2✔
237
      // Redraw immediately if view state has changed
2✔
238
      if (deck.isInitialized) {
2!
UNCOV
239
        deck.redraw();
×
UNCOV
240
      }
×
241
    }
2✔
242
  };
2✔
243

1✔
244
  // eslint-disable-next-line complexity
1✔
245
  private _handleMouseEvent = (event: MapMouseEvent) => {
1✔
246
    const deck = this._deck;
×
247
    if (!deck || !deck.isInitialized) {
×
248
      return;
×
249
    }
×
250

×
251
    const mockEvent: {
×
252
      type: string;
×
253
      deltaX?: number;
×
254
      deltaY?: number;
×
255
      offsetCenter: {x: number; y: number};
×
256
      srcEvent: MapMouseEvent;
×
257
      tapCount?: number;
×
258
    } = {
×
259
      type: event.type,
×
260
      offsetCenter: event.point,
×
261
      srcEvent: event
×
262
    };
×
263

×
264
    const lastDown = this._lastMouseDownPoint;
×
265
    if (!event.point && lastDown) {
×
266
      // drag* events do not contain a `point` field
×
267
      mockEvent.deltaX = event.originalEvent.clientX - lastDown.clientX;
×
268
      mockEvent.deltaY = event.originalEvent.clientY - lastDown.clientY;
×
269
      mockEvent.offsetCenter = {
×
270
        x: lastDown.x + mockEvent.deltaX,
×
271
        y: lastDown.y + mockEvent.deltaY
×
272
      };
×
273
    }
×
274

×
275
    switch (mockEvent.type) {
×
276
      case 'mousedown':
×
277
        deck._onPointerDown(mockEvent as unknown as MjolnirPointerEvent);
×
278
        this._lastMouseDownPoint = {
×
279
          ...event.point,
×
280
          clientX: event.originalEvent.clientX,
×
281
          clientY: event.originalEvent.clientY
×
282
        };
×
283
        break;
×
284

×
285
      case 'dragstart':
×
286
        mockEvent.type = 'panstart';
×
287
        deck._onEvent(mockEvent as unknown as MjolnirGestureEvent);
×
288
        break;
×
289

×
290
      case 'drag':
×
291
        mockEvent.type = 'panmove';
×
292
        deck._onEvent(mockEvent as unknown as MjolnirGestureEvent);
×
293
        break;
×
294

×
295
      case 'dragend':
×
296
        mockEvent.type = 'panend';
×
297
        deck._onEvent(mockEvent as unknown as MjolnirGestureEvent);
×
298
        break;
×
299

×
300
      case 'click':
×
301
        mockEvent.tapCount = 1;
×
302
        deck._onEvent(mockEvent as unknown as MjolnirGestureEvent);
×
303
        break;
×
304

×
305
      case 'dblclick':
×
306
        mockEvent.type = 'click';
×
307
        mockEvent.tapCount = 2;
×
308
        deck._onEvent(mockEvent as unknown as MjolnirGestureEvent);
×
309
        break;
×
310

×
311
      case 'mousemove':
×
312
        mockEvent.type = 'pointermove';
×
313
        deck._onPointerMove(mockEvent as unknown as MjolnirPointerEvent);
×
314
        break;
×
315

×
316
      case 'mouseout':
×
317
        mockEvent.type = 'pointerleave';
×
318
        deck._onPointerMove(mockEvent as unknown as MjolnirPointerEvent);
×
319
        break;
×
320

×
321
      default:
×
322
        return;
×
323
    }
×
324
  };
×
325
}
1✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc