maplibre-gl 3.3.0 → 3.4.0

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.
Files changed (50) hide show
  1. package/LICENSE.txt +1 -1
  2. package/README.md +3 -2
  3. package/build/generate-dist-package.js +7 -2
  4. package/build/generate-typings.ts +1 -1
  5. package/dist/LICENSE.txt +116 -0
  6. package/dist/maplibre-gl-csp-worker.js +1 -1
  7. package/dist/maplibre-gl-csp-worker.js.map +1 -1
  8. package/dist/maplibre-gl-csp.js +1 -1
  9. package/dist/maplibre-gl-csp.js.map +1 -1
  10. package/dist/maplibre-gl-dev.js +243 -100
  11. package/dist/maplibre-gl-dev.js.map +1 -1
  12. package/dist/maplibre-gl.d.ts +28 -15
  13. package/dist/maplibre-gl.js +4 -4
  14. package/dist/maplibre-gl.js.map +1 -1
  15. package/dist/package.json +1 -1
  16. package/package.json +48 -47
  17. package/src/data/dem_data.test.ts +120 -165
  18. package/src/data/dem_data.ts +38 -18
  19. package/src/render/program.ts +15 -0
  20. package/src/source/image_source.test.ts +17 -24
  21. package/src/source/raster_dem_tile_source.ts +15 -2
  22. package/src/source/raster_dem_tile_worker_source.ts +2 -2
  23. package/src/source/raster_tile_source.test.ts +1 -1
  24. package/src/source/raster_tile_source.ts +1 -1
  25. package/src/source/terrain_source_cache.test.ts +1 -1
  26. package/src/source/vector_tile_source.test.ts +1 -1
  27. package/src/source/vector_tile_source.ts +0 -1
  28. package/src/source/vector_tile_worker_source.test.ts +45 -1
  29. package/src/source/vector_tile_worker_source.ts +19 -6
  30. package/src/source/video_source.ts +4 -0
  31. package/src/source/worker_source.ts +6 -2
  32. package/src/style/load_glyph_range.test.ts +6 -8
  33. package/src/style/load_sprite.test.ts +48 -71
  34. package/src/style/style.test.ts +19 -49
  35. package/src/style/style.ts +14 -8
  36. package/src/style/style_layer/line_style_layer.test.ts +50 -0
  37. package/src/style/style_layer/line_style_layer.ts +8 -4
  38. package/src/style/style_layer/variable_text_anchor.ts +1 -1
  39. package/src/ui/control/navigation_control.ts +0 -1
  40. package/src/ui/handler/scroll_zoom.ts +6 -0
  41. package/src/ui/handler_manager.ts +2 -1
  42. package/src/ui/map.test.ts +37 -8
  43. package/src/ui/map.ts +15 -13
  44. package/src/ui/marker.test.ts +25 -0
  45. package/src/ui/marker.ts +9 -2
  46. package/src/ui/popup.ts +1 -1
  47. package/src/util/ajax.test.ts +1 -1
  48. package/src/util/image_request.test.ts +1 -1
  49. package/src/util/test/util.ts +12 -0
  50. package/src/util/throttle.ts +7 -3
@@ -13,7 +13,7 @@ import {
13
13
  } from '../source/rtl_text_plugin';
14
14
  import {browser} from '../util/browser';
15
15
  import {OverscaledTileID} from '../source/tile_id';
16
- import {fakeXhr, fakeServer} from 'nise';
16
+ import {fakeServer, type FakeServer} from 'nise';
17
17
 
18
18
  import {EvaluationParameters} from './evaluation_parameters';
19
19
  import {LayerSpecification, GeoJSONSourceSpecification, FilterSpecification, SourceSpecification} from '@maplibre/maplibre-gl-style-spec';
@@ -78,21 +78,17 @@ function createStyle(map = getStubMap()) {
78
78
  return style;
79
79
  }
80
80
 
81
- let sinonFakeXMLServer;
82
- let sinonFakeServer;
81
+ let server: FakeServer;
83
82
  let mockConsoleError;
84
83
 
85
84
  beforeEach(() => {
86
85
  global.fetch = null;
87
- sinonFakeServer = fakeServer.create();
88
- sinonFakeXMLServer = fakeXhr.useFakeXMLHttpRequest();
89
-
86
+ server = fakeServer.create();
90
87
  mockConsoleError = jest.spyOn(console, 'error').mockImplementation(() => { });
91
88
  });
92
89
 
93
90
  afterEach(() => {
94
- sinonFakeXMLServer.restore();
95
- sinonFakeServer.restore();
91
+ server.restore();
96
92
  mockConsoleError.mockRestore();
97
93
  });
98
94
 
@@ -115,12 +111,12 @@ describe('Style', () => {
115
111
 
116
112
  test('loads plugin immediately if already registered', done => {
117
113
  clearRTLTextPlugin();
118
- sinonFakeServer.respondWith('/plugin.js', 'doesn\'t matter');
114
+ server.respondWith('/plugin.js', 'doesn\'t matter');
119
115
  setRTLTextPlugin('/plugin.js', (error) => {
120
116
  expect(error).toMatch(/Cannot set the state of the rtl-text-plugin when not in the web-worker context/);
121
117
  done();
122
118
  });
123
- sinonFakeServer.respond();
119
+ server.respond();
124
120
  new Style(getStubMap());
125
121
  });
126
122
 
@@ -152,7 +148,7 @@ describe('Style', () => {
152
148
  jest.spyOn(style.sourceCaches['vector'], 'reload');
153
149
 
154
150
  clearRTLTextPlugin();
155
- sinonFakeServer.respondWith('/plugin.js', 'doesn\'t matter');
151
+ server.respondWith('/plugin.js', 'doesn\'t matter');
156
152
  const _broadcast = style.dispatcher.broadcast;
157
153
  style.dispatcher.broadcast = function (type, state, callback) {
158
154
  if (type === 'syncRTLPluginState') {
@@ -171,7 +167,7 @@ describe('Style', () => {
171
167
  done();
172
168
  }, 0);
173
169
  });
174
- sinonFakeServer.respond();
170
+ server.respond();
175
171
  });
176
172
  });
177
173
  });
@@ -211,15 +207,15 @@ describe('Style#loadURL', () => {
211
207
  });
212
208
 
213
209
  style.loadURL('style.json');
214
- sinonFakeServer.respondWith(JSON.stringify(createStyleJSON({version: 'invalid'})));
215
- sinonFakeServer.respond();
210
+ server.respondWith(JSON.stringify(createStyleJSON({version: 'invalid'})));
211
+ server.respond();
216
212
  });
217
213
 
218
214
  test('cancels pending requests if removed', () => {
219
215
  const style = new Style(getStubMap());
220
216
  style.loadURL('style.json');
221
217
  style._remove();
222
- expect(sinonFakeServer.lastRequest.aborted).toBe(true);
218
+ expect((server.lastRequest as any).aborted).toBe(true);
223
219
  });
224
220
  });
225
221
 
@@ -266,21 +262,8 @@ describe('Style#loadJSON', () => {
266
262
  // stub Image so we can invoke 'onload'
267
263
  // https://github.com/jsdom/jsdom/commit/58a7028d0d5b6aacc5b435daee9fd8f9eacbb14c
268
264
 
269
- // fake the image request (sinon doesn't allow non-string data for
270
- // server.respondWith, so we do so manually)
271
- const requests = [];
272
- sinonFakeXMLServer.onCreate = req => { requests.push(req); };
273
- const respond = () => {
274
- let req = requests.find(req => req.url === 'http://example.com/sprite.png');
275
- req.setStatus(200);
276
- req.response = new ArrayBuffer(8);
277
- req.onload();
278
-
279
- req = requests.find(req => req.url === 'http://example.com/sprite.json');
280
- req.setStatus(200);
281
- req.response = '{}';
282
- req.onload();
283
- };
265
+ server.respondWith('GET', 'http://example.com/sprite.png', new ArrayBuffer(8));
266
+ server.respondWith('GET', 'http://example.com/sprite.json', '{}');
284
267
 
285
268
  const style = new Style(getStubMap());
286
269
 
@@ -303,7 +286,7 @@ describe('Style#loadJSON', () => {
303
286
  done();
304
287
  });
305
288
 
306
- respond();
289
+ server.respond();
307
290
  });
308
291
  });
309
292
 
@@ -315,21 +298,8 @@ describe('Style#loadJSON', () => {
315
298
  // stub Image so we can invoke 'onload'
316
299
  // https://github.com/jsdom/jsdom/commit/58a7028d0d5b6aacc5b435daee9fd8f9eacbb14c
317
300
 
318
- // fake the image request (sinon doesn't allow non-string data for
319
- // server.respondWith, so we do so manually)
320
- const requests = [];
321
- sinonFakeXMLServer.onCreate = req => { requests.push(req); };
322
- const respond = () => {
323
- let req = requests.find(req => req.url === 'http://example.com/sprite.png');
324
- req.setStatus(200);
325
- req.response = new ArrayBuffer(8);
326
- req.onload();
327
-
328
- req = requests.find(req => req.url === 'http://example.com/sprite.json');
329
- req.setStatus(200);
330
- req.response = '{"image1": {"width": 1, "height": 1, "x": 0, "y": 0, "pixelRatio": 1.0}}';
331
- req.onload();
332
- };
301
+ server.respondWith('GET', 'http://example.com/sprite.png', new ArrayBuffer(8));
302
+ server.respondWith('GET', 'http://example.com/sprite.json', '{"image1": {"width": 1, "height": 1, "x": 0, "y": 0, "pixelRatio": 1.0}}');
333
303
 
334
304
  const style = new Style(getStubMap());
335
305
 
@@ -357,7 +327,7 @@ describe('Style#loadJSON', () => {
357
327
  });
358
328
  });
359
329
 
360
- respond();
330
+ server.respond();
361
331
  });
362
332
  });
363
333
 
@@ -751,7 +721,7 @@ describe('Style#setState', () => {
751
721
  });
752
722
 
753
723
  test('Issue #3893: compare new source options against originally provided options rather than normalized properties', done => {
754
- sinonFakeServer.respondWith('/tilejson.json', JSON.stringify({
724
+ server.respondWith('/tilejson.json', JSON.stringify({
755
725
  tiles: ['http://tiles.server']
756
726
  }));
757
727
  const initial = createStyleJSON();
@@ -767,7 +737,7 @@ describe('Style#setState', () => {
767
737
  style.setState(initial);
768
738
  done();
769
739
  });
770
- sinonFakeServer.respond();
740
+ server.respond();
771
741
  });
772
742
 
773
743
  test('return true if there is a change', done => {
@@ -191,6 +191,12 @@ export type StyleSwapOptions = {
191
191
  transformStyle?: TransformStyleFunction;
192
192
  }
193
193
 
194
+ /**
195
+ * Specifies a layer to be added to a {@link Style}. In addition to a standard {@link LayerSpecification}
196
+ * or a {@link CustomLayerInterface}, a {@link LayerSpecification} with an embedded {@link SourceSpecification} can also be provided.
197
+ */
198
+ export type AddLayerObject = LayerSpecification | (Omit<LayerSpecification, 'source'> & {source: SourceSpecification}) | CustomLayerInterface;
199
+
194
200
  /**
195
201
  * The Style base class
196
202
  */
@@ -362,7 +368,7 @@ export class Style extends Evented {
362
368
 
363
369
  this.light = new Light(this.stylesheet.light);
364
370
 
365
- this.map.setTerrain(this.stylesheet.terrain);
371
+ this.map.setTerrain(this.stylesheet.terrain ?? null);
366
372
 
367
373
  this.fire(new Event('data', {dataType: 'style'}));
368
374
  this.fire(new Event('style.load'));
@@ -848,7 +854,7 @@ export class Style extends Evented {
848
854
  * @param options - Style setter options.
849
855
  * @returns `this`.
850
856
  */
851
- addLayer(layerObject: LayerSpecification | CustomLayerInterface, before?: string, options: StyleSetterOptions = {}): this {
857
+ addLayer(layerObject: AddLayerObject, before?: string, options: StyleSetterOptions = {}): this {
852
858
  this._checkLoaded();
853
859
 
854
860
  const id = layerObject.id;
@@ -858,7 +864,7 @@ export class Style extends Evented {
858
864
  return;
859
865
  }
860
866
 
861
- let layer;
867
+ let layer: ReturnType<typeof createStyleLayer>;
862
868
  if (layerObject.type === 'custom') {
863
869
 
864
870
  if (emitValidationErrors(this, validateCustomStyleLayer(layerObject))) return;
@@ -866,17 +872,17 @@ export class Style extends Evented {
866
872
  layer = createStyleLayer(layerObject);
867
873
 
868
874
  } else {
869
- if (typeof (layerObject as any).source === 'object') {
870
- this.addSource(id, (layerObject as any).source);
875
+ if ('source' in layerObject && typeof layerObject.source === 'object') {
876
+ this.addSource(id, layerObject.source);
871
877
  layerObject = clone(layerObject);
872
- layerObject = (extend(layerObject, {source: id}) as any);
878
+ layerObject = extend(layerObject, {source: id});
873
879
  }
874
880
 
875
881
  // this layer is not in the style.layers array, so we pass an impossible array index
876
882
  if (this._validate(validateStyle.layer,
877
883
  `layers.${id}`, layerObject, {arrayIndex: -1}, options)) return;
878
884
 
879
- layer = createStyleLayer(layerObject);
885
+ layer = createStyleLayer(layerObject as LayerSpecification | CustomLayerInterface);
880
886
  this._validateLayer(layer);
881
887
 
882
888
  layer.setEventedParent(this, {layer: {id}});
@@ -994,7 +1000,7 @@ export class Style extends Evented {
994
1000
  * @param id - id of the desired layer
995
1001
  * @returns a layer, if one with the given `id` exists
996
1002
  */
997
- getLayer(id: string): StyleLayer {
1003
+ getLayer(id: string): StyleLayer | undefined {
998
1004
  return this._layers[id];
999
1005
  }
1000
1006
 
@@ -0,0 +1,50 @@
1
+ import {createStyleLayer} from '../create_style_layer';
2
+ import {extend} from '../../util/util';
3
+ import {LineStyleLayer} from './line_style_layer';
4
+
5
+ describe('LineStyleLayer', () => {
6
+ function createLineLayer(layer?) {
7
+ return extend({
8
+ type: 'line',
9
+ source: 'line',
10
+ id: 'line',
11
+ paint: {
12
+ 'line-color': 'red',
13
+ 'line-width': 14,
14
+ 'line-gradient': [
15
+ 'interpolate',
16
+ ['linear'],
17
+ ['line-progress'],
18
+ 0,
19
+ 'blue',
20
+ 1,
21
+ 'red'
22
+ ]
23
+ }
24
+ }, layer);
25
+ }
26
+
27
+ test('updating with valid line-gradient updates this.gradientVersion', () => {
28
+ const lineLayer = createStyleLayer(createLineLayer()) as LineStyleLayer;
29
+ const gradientVersion = lineLayer.gradientVersion;
30
+
31
+ lineLayer.setPaintProperty('line-gradient', [
32
+ 'interpolate',
33
+ ['linear'],
34
+ ['line-progress'],
35
+ 0,
36
+ 'red',
37
+ 1,
38
+ 'blue'
39
+ ]);
40
+ expect(lineLayer.gradientVersion).toBeGreaterThan(gradientVersion);
41
+ });
42
+
43
+ test('updating with invalid line-gradient updates this.gradientVersion', () => {
44
+ const lineLayer = createStyleLayer(createLineLayer()) as LineStyleLayer;
45
+ const gradientVersion = lineLayer.gradientVersion;
46
+
47
+ lineLayer.setPaintProperty('line-gradient', null);
48
+ expect(lineLayer.gradientVersion).toBeGreaterThan(gradientVersion);
49
+ });
50
+ });
@@ -9,8 +9,8 @@ import {extend} from '../../util/util';
9
9
  import {EvaluationParameters} from '../evaluation_parameters';
10
10
  import {Transitionable, Transitioning, Layout, PossiblyEvaluated, DataDrivenProperty} from '../properties';
11
11
 
12
- import {Step} from '@maplibre/maplibre-gl-style-spec';
13
- import type {FeatureState, ZoomConstantExpression, LayerSpecification} from '@maplibre/maplibre-gl-style-spec';
12
+ import {isZoomExpression, Step} from '@maplibre/maplibre-gl-style-spec';
13
+ import type {FeatureState, LayerSpecification} from '@maplibre/maplibre-gl-style-spec';
14
14
  import type {Bucket, BucketParameters} from '../../data/bucket';
15
15
  import type {LineLayoutProps, LinePaintProps} from './line_style_layer_properties.g';
16
16
  import type {Transform} from '../../geo/transform';
@@ -60,8 +60,12 @@ export class LineStyleLayer extends StyleLayer {
60
60
 
61
61
  _handleSpecialPaintPropertyUpdate(name: string) {
62
62
  if (name === 'line-gradient') {
63
- const expression: ZoomConstantExpression<'source'> = (this._transitionablePaint._values['line-gradient'].value.expression as any);
64
- this.stepInterpolant = expression._styleExpression.expression instanceof Step;
63
+ const expression = this.gradientExpression();
64
+ if (isZoomExpression(expression)) {
65
+ this.stepInterpolant = expression._styleExpression.expression instanceof Step;
66
+ } else {
67
+ this.stepInterpolant = false;
68
+ }
65
69
  this.gradientVersion = (this.gradientVersion + 1) % Number.MAX_SAFE_INTEGER;
66
70
  }
67
71
  }
@@ -32,7 +32,7 @@ export function evaluateVariableOffset(anchor: TextAnchor, offset: [number, numb
32
32
  let x = 0, y = 0;
33
33
  if (radialOffset < 0) radialOffset = 0; // Ignore negative offset.
34
34
  // solve for r where r^2 + r^2 = radialOffset^2
35
- const hypotenuse = radialOffset / Math.sqrt(2);
35
+ const hypotenuse = radialOffset / Math.SQRT2;
36
36
  switch (anchor) {
37
37
  case 'top-right':
38
38
  case 'top-left':
@@ -43,7 +43,6 @@ const defaultOptions: NavigationOptions = {
43
43
  * map.addControl(nav, 'top-left');
44
44
  * ```
45
45
  * @see [Display map navigation controls](https://maplibre.org/maplibre-gl-js/docs/examples/navigation/)
46
- * @see [Add a third party vector tile source](https://maplibre.org/maplibre-gl-js/docs/examples/third-party/)
47
46
  */
48
47
  export class NavigationControl implements IControl {
49
48
  _map: Map;
@@ -351,5 +351,11 @@ export class ScrollZoomHandler implements Handler {
351
351
 
352
352
  reset() {
353
353
  this._active = false;
354
+ this._zooming = false;
355
+ delete this._targetZoom;
356
+ if (this._finishTimeout) {
357
+ clearTimeout(this._finishTimeout);
358
+ delete this._finishTimeout;
359
+ }
354
360
  }
355
361
  }
@@ -17,6 +17,7 @@ import {DragPanHandler} from './handler/shim/drag_pan';
17
17
  import {DragRotateHandler} from './handler/shim/drag_rotate';
18
18
  import {TwoFingersTouchZoomRotateHandler} from './handler/shim/two_fingers_touch';
19
19
  import {extend} from '../util/util';
20
+ import {browser} from '../util/browser';
20
21
  import Point from '@mapbox/point-geometry';
21
22
 
22
23
  export type InputEvent = MouseEvent | TouchEvent | KeyboardEvent | WheelEvent;
@@ -583,7 +584,7 @@ export class HandlerManager {
583
584
 
584
585
  const shouldSnapToNorth = bearing => bearing !== 0 && -this._bearingSnap < bearing && bearing < this._bearingSnap;
585
586
 
586
- if (inertialEase) {
587
+ if (inertialEase && (inertialEase.essential || !browser.prefersReducedMotion)) {
587
588
  if (shouldSnapToNorth(inertialEase.bearing || this._map.getBearing())) {
588
589
  inertialEase.bearing = 0;
589
590
  }
@@ -6,7 +6,7 @@ import {OverscaledTileID} from '../source/tile_id';
6
6
  import {Event, ErrorEvent} from '../util/evented';
7
7
  import simulate from '../../test/unit/lib/simulate_interaction';
8
8
  import {fixedLngLat, fixedNum} from '../../test/unit/lib/fixed';
9
- import {LayerSpecification, SourceSpecification, StyleSpecification} from '@maplibre/maplibre-gl-style-spec';
9
+ import {GeoJSONSourceSpecification, LayerSpecification, SourceSpecification, StyleSpecification} from '@maplibre/maplibre-gl-style-spec';
10
10
  import {RequestTransformFunction} from '../util/request_manager';
11
11
  import {extend} from '../util/util';
12
12
  import {LngLatBoundsLike} from '../geo/lng_lat_bounds';
@@ -675,6 +675,19 @@ describe('Map', () => {
675
675
  map.addSource('fill', source);
676
676
  });
677
677
 
678
+ test('a layer can be added with an embedded source specification', () => {
679
+ const map = createMap({deleteStyle: true});
680
+ const source: GeoJSONSourceSpecification = {
681
+ type: 'geojson',
682
+ data: {type: 'Point', coordinates: [0, 0]}
683
+ };
684
+ map.addLayer({
685
+ id: 'foo',
686
+ type: 'symbol',
687
+ source
688
+ });
689
+ });
690
+
678
691
  test('returns the style with added source and layer', done => {
679
692
  const style = createStyle();
680
693
  const map = createMap({style});
@@ -848,21 +861,30 @@ describe('Map', () => {
848
861
 
849
862
  const map = createMap();
850
863
 
851
- const spyA = jest.spyOn(map, '_update');
852
- const spyB = jest.spyOn(map, 'resize');
864
+ const updateSpy = jest.spyOn(map, '_update');
865
+ const resizeSpy = jest.spyOn(map, 'resize');
853
866
 
854
867
  // The initial "observe" event fired by ResizeObserver should be captured/muted
855
868
  // in the map constructor
856
869
 
857
870
  observerCallback();
858
- expect(spyA).not.toHaveBeenCalled();
859
- expect(spyB).not.toHaveBeenCalled();
871
+ expect(updateSpy).not.toHaveBeenCalled();
872
+ expect(resizeSpy).not.toHaveBeenCalled();
873
+
874
+ // The next "observe" event should fire a resize / _update
860
875
 
861
- // Following "observe" events should fire a resize / _update
876
+ observerCallback();
877
+ expect(updateSpy).toHaveBeenCalled();
878
+ expect(resizeSpy).toHaveBeenCalledTimes(1);
862
879
 
880
+ // Additional "observe" events should be throttled
881
+ observerCallback();
882
+ observerCallback();
863
883
  observerCallback();
864
- expect(spyA).toHaveBeenCalled();
865
- expect(spyB).toHaveBeenCalled();
884
+ observerCallback();
885
+ expect(resizeSpy).toHaveBeenCalledTimes(1);
886
+ await new Promise((resolve) => { setTimeout(resolve, 100); });
887
+ expect(resizeSpy).toHaveBeenCalledTimes(2);
866
888
  });
867
889
 
868
890
  test('width and height correctly rounded', () => {
@@ -2569,6 +2591,13 @@ describe('Map', () => {
2569
2591
  });
2570
2592
  });
2571
2593
 
2594
+ describe('#getTerrain', () => {
2595
+ test('returns null when not set', () => {
2596
+ const map = createMap();
2597
+ expect(map.getTerrain()).toBeNull();
2598
+ });
2599
+ });
2600
+
2572
2601
  describe('#setCooperativeGestures', () => {
2573
2602
  test('returns self', () => {
2574
2603
  const map = createMap();
package/src/ui/map.ts CHANGED
@@ -25,6 +25,7 @@ import {RGBAImage} from '../util/image';
25
25
  import {Event, ErrorEvent, Listener} from '../util/evented';
26
26
  import {MapEventType, MapLayerEventType, MapMouseEvent, MapSourceDataEvent, MapStyleDataEvent} from './events';
27
27
  import {TaskQueue} from '../util/task_queue';
28
+ import {throttle} from '../util/throttle';
28
29
  import {webpSupported} from '../util/webp_supported';
29
30
  import {PerformanceMarkers, PerformanceUtils} from '../util/performance';
30
31
  import {Source, SourceClass} from '../source/source';
@@ -33,9 +34,8 @@ import {StyleLayer} from '../style/style_layer';
33
34
  import type {RequestTransformFunction} from '../util/request_manager';
34
35
  import type {LngLatLike} from '../geo/lng_lat';
35
36
  import type {LngLatBoundsLike} from '../geo/lng_lat_bounds';
36
- import type {FeatureIdentifier, StyleOptions, StyleSetterOptions} from '../style/style';
37
+ import type {AddLayerObject, FeatureIdentifier, StyleOptions, StyleSetterOptions} from '../style/style';
37
38
  import type {MapDataEvent} from './events';
38
- import type {CustomLayerInterface} from '../style/style_layer/custom_style_layer';
39
39
  import type {StyleImage, StyleImageInterface, StyleImageMetadata} from '../style/style_image';
40
40
  import type {PointLike} from './camera';
41
41
  import type {ScrollZoomHandler} from './handler/scroll_zoom';
@@ -51,7 +51,6 @@ import {defaultLocale} from './default_locale';
51
51
  import type {TaskID} from '../util/task_queue';
52
52
  import type {Cancelable} from '../types/cancelable';
53
53
  import type {
54
- LayerSpecification,
55
54
  FilterSpecification,
56
55
  StyleSpecification,
57
56
  LightSpecification,
@@ -640,15 +639,17 @@ export class Map extends Camera {
640
639
  if (typeof window !== 'undefined') {
641
640
  addEventListener('online', this._onWindowOnline, false);
642
641
  let initialResizeEventCaptured = false;
642
+ const throttledResizeCallback = throttle((entries: ResizeObserverEntry[]) => {
643
+ if (this._trackResize && !this._removed) {
644
+ this.resize(entries)._update();
645
+ }
646
+ }, 50);
643
647
  this._resizeObserver = new ResizeObserver((entries) => {
644
648
  if (!initialResizeEventCaptured) {
645
649
  initialResizeEventCaptured = true;
646
650
  return;
647
651
  }
648
-
649
- if (this._trackResize) {
650
- this.resize(entries)._update();
651
- }
652
+ throttledResizeCallback(entries);
652
653
  });
653
654
  this._resizeObserver.observe(this._container);
654
655
  }
@@ -1962,7 +1963,7 @@ export class Map extends Camera {
1962
1963
  * map.setTerrain({ source: 'terrain' });
1963
1964
  * ```
1964
1965
  */
1965
- setTerrain(options: TerrainSpecification): this {
1966
+ setTerrain(options: TerrainSpecification | null): this {
1966
1967
  this.style._checkLoaded();
1967
1968
 
1968
1969
  // clear event handlers
@@ -2017,8 +2018,8 @@ export class Map extends Camera {
2017
2018
  * map.getTerrain(); // { source: 'terrain' };
2018
2019
  * ```
2019
2020
  */
2020
- getTerrain(): TerrainSpecification {
2021
- return this.terrain && this.terrain.options;
2021
+ getTerrain(): TerrainSpecification | null {
2022
+ return this.terrain?.options ?? null;
2022
2023
  }
2023
2024
 
2024
2025
  /**
@@ -2346,7 +2347,7 @@ export class Map extends Camera {
2346
2347
  *
2347
2348
  * @param layer - The layer to add,
2348
2349
  * conforming to either the MapLibre Style Specification's [layer definition](https://maplibre.org/maplibre-style-spec/layers) or,
2349
- * less commonly, the {@link CustomLayerInterface} specification.
2350
+ * less commonly, the {@link CustomLayerInterface} specification. Can also be a layer definition with an embedded source definition.
2350
2351
  * The MapLibre Style Specification's layer definition is appropriate for most layers.
2351
2352
  *
2352
2353
  * @param beforeId - The ID of an existing layer to insert the new layer before,
@@ -2418,7 +2419,7 @@ export class Map extends Camera {
2418
2419
  * @see [Add a vector tile source](https://maplibre.org/maplibre-gl-js/docs/examples/vector-source/)
2419
2420
  * @see [Add a WMS source](https://maplibre.org/maplibre-gl-js/docs/examples/wms/)
2420
2421
  */
2421
- addLayer(layer: (LayerSpecification & {source?: string | SourceSpecification}) | CustomLayerInterface, beforeId?: string) {
2422
+ addLayer(layer: AddLayerObject, beforeId?: string) {
2422
2423
  this._lazyInitEmptyStyle();
2423
2424
  this.style.addLayer(layer, beforeId);
2424
2425
  return this._update(true);
@@ -2475,7 +2476,7 @@ export class Map extends Camera {
2475
2476
  * @see [Filter symbols by toggling a list](https://maplibre.org/maplibre-gl-js/docs/examples/filter-markers/)
2476
2477
  * @see [Filter symbols by text input](https://maplibre.org/maplibre-gl-js/docs/examples/filter-markers-by-input/)
2477
2478
  */
2478
- getLayer(id: string): StyleLayer {
2479
+ getLayer(id: string): StyleLayer | undefined {
2479
2480
  return this.style.getLayer(id);
2480
2481
  }
2481
2482
 
@@ -2561,6 +2562,7 @@ export class Map extends Camera {
2561
2562
  * @param name - The name of the paint property to set.
2562
2563
  * @param value - The value of the paint property to set.
2563
2564
  * Must be of a type appropriate for the property, as defined in the [MapLibre Style Specification](https://maplibre.org/maplibre-style-spec/).
2565
+ * Pass `null` to unset the existing value.
2564
2566
  * @param options - Options object.
2565
2567
  * @returns `this`
2566
2568
  * @example
@@ -811,4 +811,29 @@ describe('marker', () => {
811
811
 
812
812
  map.remove();
813
813
  });
814
+
815
+ test('Marker after the terrain event must listen to the render event till is fully loaded', async () => {
816
+ const map = createMap();
817
+
818
+ new Marker()
819
+ .setLngLat([1, 1])
820
+ .addTo(map);
821
+
822
+ expect(map._oneTimeListeners.render).toBeUndefined();
823
+
824
+ map.fire('terrain');
825
+ expect(map._oneTimeListeners.render).toHaveLength(1);
826
+
827
+ map.fire('render');
828
+ expect(map._oneTimeListeners.render).toHaveLength(1);
829
+
830
+ map.fire('render');
831
+ expect(map._oneTimeListeners.render).toHaveLength(1);
832
+
833
+ // await idle to be fully loaded
834
+ await map.once('idle');
835
+ map.fire('render');
836
+ expect(map._oneTimeListeners.render).toHaveLength(0);
837
+ map.remove();
838
+ });
814
839
  });
package/src/ui/marker.ts CHANGED
@@ -297,6 +297,8 @@ export class Marker extends Evented {
297
297
  map.getCanvasContainer().appendChild(this._element);
298
298
  map.on('move', this._update);
299
299
  map.on('moveend', this._update);
300
+ map.on('terrain', this._update);
301
+
300
302
  this.setDraggable(this._draggable);
301
303
  this._update();
302
304
 
@@ -419,7 +421,7 @@ export class Marker extends Evented {
419
421
  if (!('offset' in popup.options)) {
420
422
  const markerHeight = 41 - (5.8 / 2);
421
423
  const markerRadius = 13.5;
422
- const linearOffset = Math.sqrt(Math.pow(markerRadius, 2) / 2);
424
+ const linearOffset = Math.abs(markerRadius) / Math.SQRT2;
423
425
  popup.options.offset = this._defaultMarker ? {
424
426
  'top': [0, 0],
425
427
  'top-left': [0, 0],
@@ -504,9 +506,14 @@ export class Marker extends Evented {
504
506
  return this;
505
507
  }
506
508
 
507
- _update = (e?: { type: 'move' | 'moveend' }) => {
509
+ _update = (e?: { type: 'move' | 'moveend' | 'terrain' | 'render' }) => {
508
510
  if (!this._map) return;
509
511
 
512
+ const isFullyLoaded = this._map.loaded() && !this._map.isMoving();
513
+ if (e?.type === 'terrain' || (e?.type === 'render' && !isFullyLoaded)) {
514
+ this._map.once('render', this._update);
515
+ }
516
+
510
517
  if (this._map.transform.renderWorldCopies) {
511
518
  this._lngLat = smartWrap(this._lngLat, this._pos, this._map.transform);
512
519
  }
package/src/ui/popup.ts CHANGED
@@ -627,7 +627,7 @@ function normalizeOffset(offset?: Offset | null) {
627
627
 
628
628
  } else if (typeof offset === 'number') {
629
629
  // input specifies a radius from which to calculate offsets at all positions
630
- const cornerOffset = Math.round(Math.sqrt(0.5 * Math.pow(offset, 2)));
630
+ const cornerOffset = Math.round(Math.abs(offset) / Math.SQRT2);
631
631
  return {
632
632
  'center': new Point(0, 0),
633
633
  'top': new Point(0, offset),
@@ -6,7 +6,7 @@ import {
6
6
  sameOrigin
7
7
  } from './ajax';
8
8
 
9
- import {fakeServer, FakeServer} from 'nise';
9
+ import {fakeServer, type FakeServer} from 'nise';
10
10
  import {destroyFetchMock, FetchMock, RequestMock, setupFetchMock} from './test/mock_fetch';
11
11
 
12
12
  function readAsText(blob) {
@@ -1,7 +1,7 @@
1
1
  import {config} from './config';
2
2
  import {webpSupported} from './webp_supported';
3
3
  import {stubAjaxGetImage} from './test/util';
4
- import {fakeServer, FakeServer} from 'nise';
4
+ import {fakeServer, type FakeServer} from 'nise';
5
5
  import {ImageRequest, ImageRequestQueueItem} from './image_request';
6
6
  import * as ajax from './ajax';
7
7
 
@@ -132,3 +132,15 @@ export function stubAjaxGetImage(createImageBitmap) {
132
132
  }
133
133
  });
134
134
  }
135
+
136
+ /**
137
+ * This should be used in test that use nise since the internal buffer returned from a file is not an instance of ArrayBuffer for some reason.
138
+ * @param data - the data read from a file, for example by `fs.readFileSync(...)`
139
+ * @returns a copy of the data in the file in `ArrayBuffer` format
140
+ */
141
+ export function bufferToArrayBuffer(data: Buffer): ArrayBuffer {
142
+ const newBuffer = new ArrayBuffer(data.buffer.byteLength);
143
+ const view = new Uint8Array(newBuffer);
144
+ data.copy(view);
145
+ return view.buffer;
146
+ }