maplibre-gl 3.3.1 → 3.4.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.
Files changed (46) 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 +430 -128
  7. package/dist/maplibre-gl-dev.js.map +1 -1
  8. package/dist/maplibre-gl.d.ts +18 -9
  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/render/glyph_manager.test.ts +10 -9
  15. package/src/render/glyph_manager.ts +17 -10
  16. package/src/source/image_source.test.ts +17 -24
  17. package/src/source/raster_dem_tile_source.ts +36 -11
  18. package/src/source/raster_dem_tile_worker_source.ts +9 -26
  19. package/src/source/raster_tile_source.test.ts +1 -1
  20. package/src/source/raster_tile_source.ts +1 -1
  21. package/src/source/terrain_source_cache.test.ts +1 -1
  22. package/src/source/vector_tile_source.test.ts +1 -1
  23. package/src/source/vector_tile_worker_source.test.ts +45 -1
  24. package/src/source/vector_tile_worker_source.ts +19 -6
  25. package/src/source/worker_source.ts +6 -2
  26. package/src/style/load_glyph_range.test.ts +6 -8
  27. package/src/style/load_sprite.test.ts +48 -71
  28. package/src/style/style.test.ts +19 -49
  29. package/src/style/style.ts +3 -0
  30. package/src/style/style_glyph.ts +4 -3
  31. package/src/style/style_layer/line_style_layer.test.ts +50 -0
  32. package/src/style/style_layer/line_style_layer.ts +8 -4
  33. package/src/symbol/quads.ts +4 -2
  34. package/src/ui/handler/scroll_zoom.ts +6 -0
  35. package/src/ui/handler_manager.ts +2 -1
  36. package/src/ui/map.test.ts +17 -0
  37. package/src/ui/map.ts +1 -0
  38. package/src/ui/marker.test.ts +25 -0
  39. package/src/ui/marker.ts +8 -1
  40. package/src/util/ajax.test.ts +1 -1
  41. package/src/util/image_request.test.ts +1 -1
  42. package/src/util/offscreen_canvas_distorted.test.ts +13 -0
  43. package/src/util/offscreen_canvas_distorted.ts +39 -0
  44. package/src/util/test/util.ts +12 -0
  45. package/src/util/util.test.ts +171 -1
  46. package/src/util/util.ts +150 -0
@@ -180,6 +180,10 @@ export class GlyphManager {
180
180
  return;
181
181
  }
182
182
 
183
+ // Client-generated glyphs are rendered at 2x texture scale,
184
+ // because CJK glyphs are more detailed than others.
185
+ const textureScale = 2;
186
+
183
187
  let tinySDF = entry.tinySDF;
184
188
  if (!tinySDF) {
185
189
  let fontWeight = '400';
@@ -191,9 +195,9 @@ export class GlyphManager {
191
195
  fontWeight = '200';
192
196
  }
193
197
  tinySDF = entry.tinySDF = new GlyphManager.TinySDF({
194
- fontSize: 24,
195
- buffer: 3,
196
- radius: 8,
198
+ fontSize: 24 * textureScale,
199
+ buffer: 3 * textureScale,
200
+ radius: 8 * textureScale,
197
201
  cutoff: 0.25,
198
202
  fontFamily,
199
203
  fontWeight
@@ -215,17 +219,20 @@ export class GlyphManager {
215
219
  * To approximately align TinySDF glyphs with server-provided glyphs, we use this baseline adjustment
216
220
  * factor calibrated to be in between DIN Pro and Arial Unicode (but closer to Arial Unicode)
217
221
  */
218
- const topAdjustment = 27;
222
+ const topAdjustment = 27.5;
223
+
224
+ const leftAdjustment = 0.5;
219
225
 
220
226
  return {
221
227
  id,
222
- bitmap: new AlphaImage({width: char.width || 30, height: char.height || 30}, char.data),
228
+ bitmap: new AlphaImage({width: char.width || 30 * textureScale, height: char.height || 30 * textureScale}, char.data),
223
229
  metrics: {
224
- width: char.glyphWidth || 24,
225
- height: char.glyphHeight || 24,
226
- left: char.glyphLeft || 0,
227
- top: char.glyphTop - topAdjustment || -8,
228
- advance: char.glyphAdvance || 24
230
+ width: char.glyphWidth / textureScale || 24,
231
+ height: char.glyphHeight / textureScale || 24,
232
+ left: (char.glyphLeft / textureScale + leftAdjustment) || 0,
233
+ top: char.glyphTop / textureScale - topAdjustment || -8,
234
+ advance: char.glyphAdvance / textureScale || 24,
235
+ isDoubleResolution: true
229
236
  }
230
237
  };
231
238
  }
@@ -2,7 +2,7 @@ import {ImageSource} from './image_source';
2
2
  import {Evented} from '../util/evented';
3
3
  import {Transform} from '../geo/transform';
4
4
  import {extend} from '../util/util';
5
- import {fakeXhr} from 'nise';
5
+ import {type FakeServer, fakeServer} from 'nise';
6
6
  import {RequestManager} from '../util/request_manager';
7
7
  import {Dispatcher} from '../util/dispatcher';
8
8
  import {stubAjaxGetImage} from '../util/test/util';
@@ -44,20 +44,15 @@ class StubMap extends Evented {
44
44
  }
45
45
 
46
46
  describe('ImageSource', () => {
47
- const requests = [];
48
- fakeXhr.useFakeXMLHttpRequest().onCreate = (req) => { requests.push(req); };
49
47
  stubAjaxGetImage(undefined);
48
+ let server: FakeServer;
49
+
50
50
  beforeEach(() => {
51
51
  global.fetch = null;
52
+ server = fakeServer.create();
53
+ server.respondWith(new ArrayBuffer(1));
52
54
  });
53
55
 
54
- const respond = () => {
55
- const req = requests.shift();
56
- req.setStatus(200);
57
- req.response = new ArrayBuffer(1);
58
- req.onload();
59
- };
60
-
61
56
  test('constructor', () => {
62
57
  const source = createSource({url: '/image.png'});
63
58
 
@@ -72,7 +67,7 @@ describe('ImageSource', () => {
72
67
  expect(e.dataType).toBe('source');
73
68
  });
74
69
  source.onAdd(new StubMap() as any);
75
- respond();
70
+ server.respond();
76
71
  expect(source.image).toBeTruthy();
77
72
  });
78
73
 
@@ -81,7 +76,7 @@ describe('ImageSource', () => {
81
76
  const map = new StubMap() as any;
82
77
  const spy = jest.spyOn(map._requestManager, 'transformRequest');
83
78
  source.onAdd(map);
84
- respond();
79
+ server.respond();
85
80
  expect(spy).toHaveBeenCalledTimes(1);
86
81
  expect(spy.mock.calls[0][0]).toBe('/image.png');
87
82
  expect(spy.mock.calls[0][1]).toBe('Image');
@@ -92,12 +87,12 @@ describe('ImageSource', () => {
92
87
  const map = new StubMap() as any;
93
88
  const spy = jest.spyOn(map._requestManager, 'transformRequest');
94
89
  source.onAdd(map);
95
- respond();
90
+ server.respond();
96
91
  expect(spy).toHaveBeenCalledTimes(1);
97
92
  expect(spy.mock.calls[0][0]).toBe('/image.png');
98
93
  expect(spy.mock.calls[0][1]).toBe('Image');
99
94
  source.updateImage({url: '/image2.png'});
100
- respond();
95
+ server.respond();
101
96
  expect(spy).toHaveBeenCalledTimes(2);
102
97
  expect(spy.mock.calls[1][0]).toBe('/image2.png');
103
98
  expect(spy.mock.calls[1][1]).toBe('Image');
@@ -107,7 +102,7 @@ describe('ImageSource', () => {
107
102
  const source = createSource({url: '/image.png'});
108
103
  const map = new StubMap() as any;
109
104
  source.onAdd(map);
110
- respond();
105
+ server.respond();
111
106
  const beforeSerialized = source.serialize();
112
107
  expect(beforeSerialized.coordinates).toEqual([[0, 0], [1, 0], [1, 1], [0, 1]]);
113
108
  source.setCoordinates([[0, 0], [-1, 0], [-1, -1], [0, -1]]);
@@ -119,14 +114,14 @@ describe('ImageSource', () => {
119
114
  const source = createSource({url: '/image.png'});
120
115
  const map = new StubMap() as any;
121
116
  source.onAdd(map);
122
- respond();
117
+ server.respond();
123
118
  const beforeSerialized = source.serialize();
124
119
  expect(beforeSerialized.coordinates).toEqual([[0, 0], [1, 0], [1, 1], [0, 1]]);
125
120
  source.updateImage({
126
121
  url: '/image2.png',
127
122
  coordinates: [[0, 0], [-1, 0], [-1, -1], [0, -1]]
128
123
  });
129
- respond();
124
+ server.respond();
130
125
  const afterSerialized = source.serialize();
131
126
  expect(afterSerialized.coordinates).toEqual([[0, 0], [-1, 0], [-1, -1], [0, -1]]);
132
127
  });
@@ -140,7 +135,7 @@ describe('ImageSource', () => {
140
135
  }
141
136
  });
142
137
  source.onAdd(new StubMap() as any);
143
- respond();
138
+ server.respond();
144
139
  });
145
140
 
146
141
  test('fires data event when metadata is loaded', done => {
@@ -151,7 +146,7 @@ describe('ImageSource', () => {
151
146
  }
152
147
  });
153
148
  source.onAdd(new StubMap() as any);
154
- respond();
149
+ server.respond();
155
150
  });
156
151
 
157
152
  test('fires idle event on prepare call when there is at least one not loaded tile', done => {
@@ -164,7 +159,7 @@ describe('ImageSource', () => {
164
159
  }
165
160
  });
166
161
  source.onAdd(new StubMap() as any);
167
- respond();
162
+ server.respond();
168
163
 
169
164
  source.tiles[String(tile.tileID.wrap)] = tile;
170
165
  source.image = new ImageBitmap();
@@ -190,10 +185,9 @@ describe('ImageSource', () => {
190
185
 
191
186
  source.onAdd(map);
192
187
 
193
- requests.shift();
194
188
  expect(source.image).toBeUndefined();
195
189
  source.updateImage({url: '/image2.png'});
196
- respond();
190
+ server.respond();
197
191
  expect(source.image).toBeTruthy();
198
192
  });
199
193
 
@@ -203,8 +197,7 @@ describe('ImageSource', () => {
203
197
 
204
198
  source.onAdd(map);
205
199
 
206
- const request = requests.shift() as any;
207
- const spy = jest.spyOn(request, 'abort');
200
+ const spy = jest.spyOn(server.requests[0] as any, 'abort');
208
201
 
209
202
  source.updateImage({url: '/image2.png'});
210
203
  expect(spy).toHaveBeenCalled();
@@ -1,6 +1,6 @@
1
1
  import {ImageRequest} from '../util/image_request';
2
2
  import {ResourceType} from '../util/request_manager';
3
- import {extend, isImageBitmap} from '../util/util';
3
+ import {extend, isImageBitmap, readImageUsingVideoFrame} from '../util/util';
4
4
  import {Evented} from '../util/evented';
5
5
  import {browser} from '../util/browser';
6
6
  import {offscreenCanvasSupported} from '../util/offscreen_canvas_supported';
@@ -8,6 +8,7 @@ import {OverscaledTileID} from './tile_id';
8
8
  import {RasterTileSource} from './raster_tile_source';
9
9
  // ensure DEMData is registered for worker transfer on main thread:
10
10
  import '../data/dem_data';
11
+ import type {DEMEncoding} from '../data/dem_data';
11
12
 
12
13
  import type {Source} from './source';
13
14
  import type {Dispatcher} from '../util/dispatcher';
@@ -15,6 +16,8 @@ import type {Tile} from './tile';
15
16
  import type {Callback} from '../types/callback';
16
17
  import type {RasterDEMSourceSpecification} from '@maplibre/maplibre-gl-style-spec';
17
18
  import type {ExpiryData} from '../util/ajax';
19
+ import {isOffscreenCanvasDistorted} from '../util/offscreen_canvas_distorted';
20
+ import {RGBAImage} from '../util/image';
18
21
 
19
22
  /**
20
23
  * A source containing raster DEM tiles (See the [Style Specification](https://maplibre.org/maplibre-style-spec/) for detailed documentation of options.)
@@ -33,7 +36,11 @@ import type {ExpiryData} from '../util/ajax';
33
36
  * @see [3D Terrain](https://maplibre.org/maplibre-gl-js/docs/examples/3d-terrain/)
34
37
  */
35
38
  export class RasterDEMTileSource extends RasterTileSource implements Source {
36
- encoding: 'mapbox' | 'terrarium';
39
+ encoding: DEMEncoding;
40
+ redFactor?: number;
41
+ greenFactor?: number;
42
+ blueFactor?: number;
43
+ baseShift?: number;
37
44
 
38
45
  constructor(id: string, options: RasterDEMSourceSpecification, dispatcher: Dispatcher, eventedParent: Evented) {
39
46
  super(id, options, dispatcher, eventedParent);
@@ -41,14 +48,17 @@ export class RasterDEMTileSource extends RasterTileSource implements Source {
41
48
  this.maxzoom = 22;
42
49
  this._options = extend({type: 'raster-dem'}, options);
43
50
  this.encoding = options.encoding || 'mapbox';
51
+ this.redFactor = options.redFactor;
52
+ this.greenFactor = options.greenFactor;
53
+ this.blueFactor = options.blueFactor;
54
+ this.baseShift = options.baseShift;
44
55
  }
45
56
 
46
57
  loadTile(tile: Tile, callback: Callback<void>) {
47
58
  const url = tile.tileID.canonical.url(this.tiles, this.map.getPixelRatio(), this.scheme);
48
- tile.request = ImageRequest.getImage(this.map._requestManager.transformRequest(url, ResourceType.Tile), imageLoaded.bind(this), this.map._refreshExpiredTiles);
49
-
59
+ const request = this.map._requestManager.transformRequest(url, ResourceType.Tile);
50
60
  tile.neighboringTiles = this._getNeighboringTiles(tile.tileID);
51
- function imageLoaded(err: Error, img: (HTMLImageElement | ImageBitmap) & ExpiryData) {
61
+ tile.request = ImageRequest.getImage(request, async (err: Error, img: (HTMLImageElement | ImageBitmap), expiry: ExpiryData) => {
52
62
  delete tile.request;
53
63
  if (tile.aborted) {
54
64
  tile.state = 'unloaded';
@@ -57,24 +67,39 @@ export class RasterDEMTileSource extends RasterTileSource implements Source {
57
67
  tile.state = 'errored';
58
68
  callback(err);
59
69
  } else if (img) {
60
- if (this.map._refreshExpiredTiles) tile.setExpiryData(img);
61
- delete img.cacheControl;
62
- delete img.expires;
70
+ if (this.map._refreshExpiredTiles) tile.setExpiryData(expiry);
63
71
  const transfer = isImageBitmap(img) && offscreenCanvasSupported();
64
- const rawImageData = transfer ? img : browser.getImageData(img, 1);
72
+ const rawImageData = transfer ? img : await readImageNow(img);
65
73
  const params = {
66
74
  uid: tile.uid,
67
75
  coord: tile.tileID,
68
76
  source: this.id,
69
77
  rawImageData,
70
- encoding: this.encoding
78
+ encoding: this.encoding,
79
+ redFactor: this.redFactor,
80
+ greenFactor: this.greenFactor,
81
+ blueFactor: this.blueFactor,
82
+ baseShift: this.baseShift
71
83
  };
72
84
 
73
85
  if (!tile.actor || tile.state === 'expired') {
74
86
  tile.actor = this.dispatcher.getActor();
75
- tile.actor.send('loadDEMTile', params, done.bind(this));
87
+ tile.actor.send('loadDEMTile', params, done);
88
+ }
89
+ }
90
+ }, this.map._refreshExpiredTiles);
91
+
92
+ async function readImageNow(img: ImageBitmap | HTMLImageElement): Promise<RGBAImage | ImageData> {
93
+ if (typeof VideoFrame !== 'undefined' && isOffscreenCanvasDistorted()) {
94
+ const width = img.width + 2;
95
+ const height = img.height + 2;
96
+ try {
97
+ return new RGBAImage({width, height}, await readImageUsingVideoFrame(img, -1, -1, width, height));
98
+ } catch (e) {
99
+ // fall-back to browser canvas decoding
76
100
  }
77
101
  }
102
+ return browser.getImageData(img, 1);
78
103
  }
79
104
 
80
105
  function done(err, data) {
@@ -6,46 +6,29 @@ import type {
6
6
  WorkerDEMTileCallback,
7
7
  TileParameters
8
8
  } from './worker_source';
9
- import {isImageBitmap} from '../util/util';
9
+ import {getImageData, isImageBitmap} from '../util/util';
10
10
 
11
11
  export class RasterDEMTileWorkerSource {
12
12
  actor: Actor;
13
13
  loaded: {[_: string]: DEMData};
14
- offscreenCanvas: OffscreenCanvas;
15
- offscreenCanvasContext: OffscreenCanvasRenderingContext2D;
16
14
 
17
15
  constructor() {
18
16
  this.loaded = {};
19
17
  }
20
18
 
21
- loadTile(params: WorkerDEMTileParameters, callback: WorkerDEMTileCallback) {
22
- const {uid, encoding, rawImageData} = params;
23
- // Main thread will transfer ImageBitmap if offscreen decode with OffscreenCanvas is supported, else it will transfer an already decoded image.
24
- const imagePixels = isImageBitmap(rawImageData) ? this.getImageData(rawImageData) : rawImageData as RGBAImage;
25
- const dem = new DEMData(uid, imagePixels, encoding);
19
+ async loadTile(params: WorkerDEMTileParameters, callback: WorkerDEMTileCallback) {
20
+ const {uid, encoding, rawImageData, redFactor, greenFactor, blueFactor, baseShift} = params;
21
+ const width = rawImageData.width + 2;
22
+ const height = rawImageData.height + 2;
23
+ const imagePixels: RGBAImage = isImageBitmap(rawImageData) ?
24
+ new RGBAImage({width, height}, await getImageData(rawImageData, -1, -1, width, height)) :
25
+ rawImageData;
26
+ const dem = new DEMData(uid, imagePixels, encoding, redFactor, greenFactor, blueFactor, baseShift);
26
27
  this.loaded = this.loaded || {};
27
28
  this.loaded[uid] = dem;
28
29
  callback(null, dem);
29
30
  }
30
31
 
31
- getImageData(imgBitmap: ImageBitmap): RGBAImage {
32
- // Lazily initialize OffscreenCanvas
33
- if (!this.offscreenCanvas || !this.offscreenCanvasContext) {
34
- // Dem tiles are typically 256x256
35
- this.offscreenCanvas = new OffscreenCanvas(imgBitmap.width, imgBitmap.height);
36
- this.offscreenCanvasContext = this.offscreenCanvas.getContext('2d', {willReadFrequently: true});
37
- }
38
-
39
- this.offscreenCanvas.width = imgBitmap.width;
40
- this.offscreenCanvas.height = imgBitmap.height;
41
-
42
- this.offscreenCanvasContext.drawImage(imgBitmap, 0, 0, imgBitmap.width, imgBitmap.height);
43
- // Insert an additional 1px padding around the image to allow backfilling for neighboring data.
44
- const imgData = this.offscreenCanvasContext.getImageData(-1, -1, imgBitmap.width + 2, imgBitmap.height + 2);
45
- this.offscreenCanvasContext.clearRect(0, 0, this.offscreenCanvas.width, this.offscreenCanvas.height);
46
- return new RGBAImage({width: imgData.width, height: imgData.height}, imgData.data);
47
- }
48
-
49
32
  removeTile(params: TileParameters) {
50
33
  const loaded = this.loaded,
51
34
  uid = params.uid;
@@ -2,7 +2,7 @@ import {RasterTileSource} from './raster_tile_source';
2
2
  import {OverscaledTileID} from './tile_id';
3
3
  import {RequestManager} from '../util/request_manager';
4
4
  import {Dispatcher} from '../util/dispatcher';
5
- import {fakeServer, FakeServer} from 'nise';
5
+ import {fakeServer, type FakeServer} from 'nise';
6
6
  import {Tile} from './tile';
7
7
  import {stubAjaxGetImage} from '../util/test/util';
8
8
 
@@ -29,7 +29,7 @@ import type {
29
29
  * ```ts
30
30
  * map.addSource('raster-source', {
31
31
  * 'type': 'raster',
32
- * 'tiles': ['https://stamen-tiles.a.ssl.fastly.net/watercolor/{z}/{x}/{y}.jpg'],
32
+ * 'tiles': ['https://tiles.stadiamaps.com/tiles/stamen_watercolor/{z}/{x}/{y}.jpg'],
33
33
  * 'tileSize': 256,
34
34
  * });
35
35
  * ```
@@ -2,7 +2,7 @@ import {TerrainSourceCache} from './terrain_source_cache';
2
2
  import {Style} from '../style/style';
3
3
  import {RequestManager} from '../util/request_manager';
4
4
  import {Dispatcher} from '../util/dispatcher';
5
- import {fakeServer, FakeServer} from 'nise';
5
+ import {fakeServer, type FakeServer} from 'nise';
6
6
  import {Transform} from '../geo/transform';
7
7
  import {Evented} from '../util/evented';
8
8
  import {Painter} from '../render/painter';
@@ -1,4 +1,4 @@
1
- import {fakeServer, FakeServer} from 'nise';
1
+ import {fakeServer, type FakeServer} from 'nise';
2
2
  import {Source} from './source';
3
3
  import {VectorTileSource} from './vector_tile_source';
4
4
  import {Tile} from './tile';
@@ -4,7 +4,7 @@ import vt from '@mapbox/vector-tile';
4
4
  import Protobuf from 'pbf';
5
5
  import {VectorTileWorkerSource} from '../source/vector_tile_worker_source';
6
6
  import {StyleLayerIndex} from '../style/style_layer_index';
7
- import {fakeServer, FakeServer} from 'nise';
7
+ import {fakeServer, type FakeServer} from 'nise';
8
8
  import {Actor} from '../util/actor';
9
9
  import {TileParameters, WorkerTileParameters, WorkerTileResult} from './worker_source';
10
10
  import {WorkerTile} from './worker_tile';
@@ -234,6 +234,50 @@ describe('vector tile worker source', () => {
234
234
 
235
235
  });
236
236
 
237
+ test('VectorTileWorkerSource#returns a good error message when failing to parse a tile', () => {
238
+ const source = new VectorTileWorkerSource(actor, new StyleLayerIndex(), []);
239
+ const parse = jest.fn();
240
+ const callback = jest.fn();
241
+
242
+ server.respondWith(request => {
243
+ request.respond(200, {'Content-Type': 'application/pbf'}, 'something...');
244
+ });
245
+
246
+ source.loadTile({
247
+ source: 'source',
248
+ uid: 0,
249
+ tileID: {overscaledZ: 0, wrap: 0, canonical: {x: 0, y: 0, z: 0, w: 0}},
250
+ request: {url: 'http://localhost:2900/faketile.pbf'}
251
+ } as any as WorkerTileParameters, callback);
252
+
253
+ server.respond();
254
+
255
+ expect(parse).not.toHaveBeenCalled();
256
+ expect(callback).toHaveBeenCalledTimes(1);
257
+ expect(callback.mock.calls[0][0].message).toContain('Unable to parse the tile at');
258
+ });
259
+
260
+ test('VectorTileWorkerSource#returns a good error message when failing to parse a gzipped tile', () => {
261
+ const source = new VectorTileWorkerSource(actor, new StyleLayerIndex(), []);
262
+ const parse = jest.fn();
263
+ const callback = jest.fn();
264
+
265
+ server.respondWith(new Uint8Array([0x1f, 0x8b]).buffer);
266
+
267
+ source.loadTile({
268
+ source: 'source',
269
+ uid: 0,
270
+ tileID: {overscaledZ: 0, wrap: 0, canonical: {x: 0, y: 0, z: 0, w: 0}},
271
+ request: {url: 'http://localhost:2900/faketile.pbf'}
272
+ } as any as WorkerTileParameters, callback);
273
+
274
+ server.respond();
275
+
276
+ expect(parse).not.toHaveBeenCalled();
277
+ expect(callback).toHaveBeenCalledTimes(1);
278
+ expect(callback.mock.calls[0][0].message).toContain('gzipped');
279
+ });
280
+
237
281
  test('VectorTileWorkerSource provides resource timing information', done => {
238
282
  const rawTileData = fs.readFileSync(path.join(__dirname, '/../../test/unit/assets/mbsv5-6-18-23.vector.pbf'));
239
283
 
@@ -46,12 +46,25 @@ function loadVectorTile(params: WorkerTileParameters, callback: LoadVectorDataCa
46
46
  if (err) {
47
47
  callback(err);
48
48
  } else if (data) {
49
- callback(null, {
50
- vectorTile: new vt.VectorTile(new Protobuf(data)),
51
- rawData: data,
52
- cacheControl,
53
- expires
54
- });
49
+ try {
50
+ const vectorTile = new vt.VectorTile(new Protobuf(data));
51
+ callback(null, {
52
+ vectorTile,
53
+ rawData: data,
54
+ cacheControl,
55
+ expires
56
+ });
57
+ } catch (ex) {
58
+ const bytes = new Uint8Array(data);
59
+ const isGzipped = bytes[0] === 0x1f && bytes[1] === 0x8b;
60
+ let errorMessage = `Unable to parse the tile at ${params.request.url}, `;
61
+ if (isGzipped) {
62
+ errorMessage += 'please make sure the data is not gzipped and that you have configured the relevant header in the server';
63
+ } else {
64
+ errorMessage += `got error: ${ex.messge}`;
65
+ }
66
+ callback(new Error(errorMessage));
67
+ }
55
68
  }
56
69
  });
57
70
  return () => {
@@ -6,7 +6,7 @@ import type {OverscaledTileID} from './tile_id';
6
6
  import type {Bucket} from '../data/bucket';
7
7
  import type {FeatureIndex} from '../data/feature_index';
8
8
  import type {CollisionBoxArray} from '../data/array_types.g';
9
- import type {DEMData} from '../data/dem_data';
9
+ import type {DEMData, DEMEncoding} from '../data/dem_data';
10
10
  import type {StyleGlyph} from '../style/style_glyph';
11
11
  import type {StyleImage} from '../style/style_image';
12
12
  import type {PromoteIdSpecification} from '@maplibre/maplibre-gl-style-spec';
@@ -37,7 +37,11 @@ export type WorkerDEMTileParameters = TileParameters & {
37
37
  w: number;
38
38
  };
39
39
  rawImageData: RGBAImage | ImageBitmap;
40
- encoding: 'mapbox' | 'terrarium';
40
+ encoding: DEMEncoding;
41
+ redFactor: number;
42
+ greenFactor: number;
43
+ blueFactor: number;
44
+ baseShift: number;
41
45
  };
42
46
 
43
47
  /**
@@ -2,7 +2,8 @@ import fs from 'fs';
2
2
  import path from 'path';
3
3
  import {RequestManager} from '../util/request_manager';
4
4
  import {loadGlyphRange} from './load_glyph_range';
5
- import {fakeXhr} from 'nise';
5
+ import {fakeServer} from 'nise';
6
+ import {bufferToArrayBuffer} from '../util/test/util';
6
7
 
7
8
  test('loadGlyphRange', done => {
8
9
  global.fetch = null;
@@ -13,8 +14,8 @@ test('loadGlyphRange', done => {
13
14
 
14
15
  const manager = new RequestManager(transform);
15
16
 
16
- let request;
17
- fakeXhr.useFakeXMLHttpRequest().onCreate = (req) => { request = req; };
17
+ const server = fakeServer.create();
18
+ server.respondWith(bufferToArrayBuffer(fs.readFileSync(path.join(__dirname, '../../test/unit/assets/0-255.pbf'))));
18
19
 
19
20
  loadGlyphRange('Arial Unicode MS', 0, 'https://localhost/fonts/v1/{fontstack}/{range}.pbf', manager, (err, result) => {
20
21
  expect(err).toBeFalsy();
@@ -35,9 +36,6 @@ test('loadGlyphRange', done => {
35
36
  }
36
37
  done();
37
38
  });
38
-
39
- expect(request.url).toBe('https://localhost/fonts/v1/Arial Unicode MS/0-255.pbf');
40
- request.setStatus(200);
41
- request.response = fs.readFileSync(path.join(__dirname, '../../test/unit/assets/0-255.pbf'));
42
- request.onload();
39
+ server.respond();
40
+ expect(server.requests[0].url).toBe('https://localhost/fonts/v1/Arial Unicode MS/0-255.pbf');
43
41
  });