maplibre-gl 3.3.1 → 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 (36) hide show
  1. package/README.md +1 -1
  2. package/dist/maplibre-gl-csp-worker.js +1 -1
  3. package/dist/maplibre-gl-csp-worker.js.map +1 -1
  4. package/dist/maplibre-gl-csp.js +1 -1
  5. package/dist/maplibre-gl-csp.js.map +1 -1
  6. package/dist/maplibre-gl-dev.js +202 -80
  7. package/dist/maplibre-gl-dev.js.map +1 -1
  8. package/dist/maplibre-gl.d.ts +14 -6
  9. package/dist/maplibre-gl.js +4 -4
  10. package/dist/maplibre-gl.js.map +1 -1
  11. package/package.json +41 -41
  12. package/src/data/dem_data.test.ts +120 -165
  13. package/src/data/dem_data.ts +38 -18
  14. package/src/source/image_source.test.ts +17 -24
  15. package/src/source/raster_dem_tile_source.ts +15 -2
  16. package/src/source/raster_dem_tile_worker_source.ts +2 -2
  17. package/src/source/raster_tile_source.test.ts +1 -1
  18. package/src/source/raster_tile_source.ts +1 -1
  19. package/src/source/terrain_source_cache.test.ts +1 -1
  20. package/src/source/vector_tile_source.test.ts +1 -1
  21. package/src/source/vector_tile_worker_source.test.ts +45 -1
  22. package/src/source/vector_tile_worker_source.ts +19 -6
  23. package/src/source/worker_source.ts +6 -2
  24. package/src/style/load_glyph_range.test.ts +6 -8
  25. package/src/style/load_sprite.test.ts +48 -71
  26. package/src/style/style.test.ts +19 -49
  27. package/src/style/style_layer/line_style_layer.test.ts +50 -0
  28. package/src/style/style_layer/line_style_layer.ts +8 -4
  29. package/src/ui/handler/scroll_zoom.ts +6 -0
  30. package/src/ui/handler_manager.ts +2 -1
  31. package/src/ui/map.ts +1 -0
  32. package/src/ui/marker.test.ts +25 -0
  33. package/src/ui/marker.ts +8 -1
  34. package/src/util/ajax.test.ts +1 -1
  35. package/src/util/image_request.test.ts +1 -1
  36. package/src/util/test/util.ts +12 -0
@@ -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 => {
@@ -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
  }
@@ -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
  }
package/src/ui/map.ts CHANGED
@@ -2562,6 +2562,7 @@ export class Map extends Camera {
2562
2562
  * @param name - The name of the paint property to set.
2563
2563
  * @param value - The value of the paint property to set.
2564
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.
2565
2566
  * @param options - Options object.
2566
2567
  * @returns `this`
2567
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
 
@@ -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
  }
@@ -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
+ }