maplibre-gl 3.2.0 → 3.2.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.
@@ -139,30 +139,19 @@ export class VectorTileWorkerSource implements WorkerSource {
139
139
  * Implements {@link WorkerSource#reloadTile}.
140
140
  */
141
141
  reloadTile(params: WorkerTileParameters, callback: WorkerTileCallback) {
142
- const loaded = this.loaded,
143
- uid = params.uid,
144
- vtSource = this;
142
+ const loaded = this.loaded;
143
+ const uid = params.uid;
145
144
  if (loaded && loaded[uid]) {
146
145
  const workerTile = loaded[uid];
147
146
  workerTile.showCollisionBoxes = params.showCollisionBoxes;
148
-
149
- const done = (err?: Error, data?: any) => {
150
- const reloadCallback = workerTile.reloadCallback;
151
- if (reloadCallback) {
152
- delete workerTile.reloadCallback;
153
- workerTile.parse(workerTile.vectorTile, vtSource.layerIndex, this.availableImages, vtSource.actor, reloadCallback);
154
- }
155
- callback(err, data);
156
- };
157
-
158
147
  if (workerTile.status === 'parsing') {
159
- workerTile.reloadCallback = done;
148
+ workerTile.parse(workerTile.vectorTile, this.layerIndex, this.availableImages, this.actor, callback);
160
149
  } else if (workerTile.status === 'done') {
161
150
  // if there was no vector tile data on the initial load, don't try and re-parse tile
162
151
  if (workerTile.vectorTile) {
163
- workerTile.parse(workerTile.vectorTile, this.layerIndex, this.availableImages, this.actor, done);
152
+ workerTile.parse(workerTile.vectorTile, this.layerIndex, this.availableImages, this.actor, callback);
164
153
  } else {
165
- done();
154
+ callback();
166
155
  }
167
156
  }
168
157
  }
@@ -99,4 +99,147 @@ describe('worker tile', () => {
99
99
  done();
100
100
  });
101
101
  });
102
+
103
+ test('WorkerTile#parse would request all types of dependencies', done => {
104
+ const tile = createWorkerTile();
105
+ const layerIndex = new StyleLayerIndex([{
106
+ id: '1',
107
+ type: 'fill',
108
+ source: 'source',
109
+ 'source-layer': 'test',
110
+ paint: {
111
+ 'fill-pattern': 'hello'
112
+ }
113
+ }, {
114
+ id: 'test',
115
+ source: 'source',
116
+ 'source-layer': 'test',
117
+ type: 'symbol',
118
+ layout: {
119
+ 'icon-image': 'hello',
120
+ 'text-font': ['StandardFont-Bold'],
121
+ 'text-field': '{name}'
122
+ }
123
+ }]);
124
+
125
+ const data = {
126
+ layers: {
127
+ test: {
128
+ version: 2,
129
+ name: 'test',
130
+ extent: 8192,
131
+ length: 1,
132
+ feature: (featureIndex: number) => ({
133
+ extent: 8192,
134
+ type: 1,
135
+ id: featureIndex,
136
+ properties: {
137
+ name: 'test'
138
+ },
139
+ loadGeometry () {
140
+ return [[{x: 0, y: 0}]];
141
+ }
142
+ })
143
+ }
144
+ }
145
+ } as any as VectorTile;
146
+
147
+ const send = jest.fn().mockImplementation((type: string, data: unknown, callback: Function) => {
148
+ setTimeout(() => callback(null,
149
+ type === 'getImages' ?
150
+ {'hello': {width: 1, height: 1, data: new Uint8Array([0])}} :
151
+ {'StandardFont-Bold': {width: 1, height: 1, data: new Uint8Array([0])}}
152
+ ));
153
+ });
154
+
155
+ const actorMock = {
156
+ send
157
+ } as unknown as Actor;
158
+ tile.parse(data, layerIndex, ['hello'], actorMock, (err, result) => {
159
+ expect(err).toBeFalsy();
160
+ expect(result).toBeDefined();
161
+ expect(send).toHaveBeenCalledTimes(3);
162
+ expect(send).toHaveBeenCalledWith('getImages', expect.objectContaining({'icons': ['hello'], 'type': 'icons'}), expect.any(Function));
163
+ expect(send).toHaveBeenCalledWith('getImages', expect.objectContaining({'icons': ['hello'], 'type': 'patterns'}), expect.any(Function));
164
+ expect(send).toHaveBeenCalledWith('getGlyphs', expect.objectContaining({'source': 'source', 'type': 'glyphs', 'stacks': {'StandardFont-Bold': [101, 115, 116]}}), expect.any(Function));
165
+ done();
166
+ });
167
+ });
168
+
169
+ test('WorkerTile#parse would cancel and only event once on repeated reparsing', done => {
170
+ const tile = createWorkerTile();
171
+ const layerIndex = new StyleLayerIndex([{
172
+ id: '1',
173
+ type: 'fill',
174
+ source: 'source',
175
+ 'source-layer': 'test',
176
+ paint: {
177
+ 'fill-pattern': 'hello'
178
+ }
179
+ }, {
180
+ id: 'test',
181
+ source: 'source',
182
+ 'source-layer': 'test',
183
+ type: 'symbol',
184
+ layout: {
185
+ 'icon-image': 'hello',
186
+ 'text-font': ['StandardFont-Bold'],
187
+ 'text-field': '{name}'
188
+ }
189
+ }]);
190
+
191
+ const data = {
192
+ layers: {
193
+ test: {
194
+ version: 2,
195
+ name: 'test',
196
+ extent: 8192,
197
+ length: 1,
198
+ feature: (featureIndex: number) => ({
199
+ extent: 8192,
200
+ type: 1,
201
+ id: featureIndex,
202
+ properties: {
203
+ name: 'test'
204
+ },
205
+ loadGeometry () {
206
+ return [[{x: 0, y: 0}]];
207
+ }
208
+ })
209
+ }
210
+ }
211
+ } as any as VectorTile;
212
+
213
+ let cancelCount = 0;
214
+ const send = jest.fn().mockImplementation((type: string, data: unknown, callback: Function) => {
215
+ const res = setTimeout(() => callback(null,
216
+ type === 'getImages' ?
217
+ {'hello': {width: 1, height: 1, data: new Uint8Array([0])}} :
218
+ {'StandardFont-Bold': {width: 1, height: 1, data: new Uint8Array([0])}}
219
+ ));
220
+
221
+ return {
222
+ cancel: () => {
223
+ cancelCount += 1;
224
+ clearTimeout(res);
225
+ }
226
+ };
227
+ });
228
+
229
+ const actorMock = {
230
+ send
231
+ } as unknown as Actor;
232
+ tile.parse(data, layerIndex, ['hello'], actorMock, () => done.fail('should not be called'));
233
+ tile.parse(data, layerIndex, ['hello'], actorMock, () => done.fail('should not be called'));
234
+ tile.parse(data, layerIndex, ['hello'], actorMock, (err, result) => {
235
+ expect(err).toBeFalsy();
236
+ expect(result).toBeDefined();
237
+ expect(cancelCount).toBe(6);
238
+ expect(send).toHaveBeenCalledTimes(9);
239
+ expect(send).toHaveBeenCalledWith('getImages', expect.objectContaining({'icons': ['hello'], 'type': 'icons'}), expect.any(Function));
240
+ expect(send).toHaveBeenCalledWith('getImages', expect.objectContaining({'icons': ['hello'], 'type': 'patterns'}), expect.any(Function));
241
+ expect(send).toHaveBeenCalledWith('getGlyphs', expect.objectContaining({'source': 'source', 'type': 'glyphs', 'stacks': {'StandardFont-Bold': [101, 115, 116]}}), expect.any(Function));
242
+ done();
243
+ });
244
+ });
102
245
  });
@@ -24,6 +24,7 @@ import type {
24
24
  } from '../source/worker_source';
25
25
  import type {PromoteIdSpecification} from '@maplibre/maplibre-gl-style-spec';
26
26
  import type {VectorTile} from '@mapbox/vector-tile';
27
+ import {Cancelable} from '../types/cancelable';
27
28
 
28
29
  export class WorkerTile {
29
30
  tileID: OverscaledTileID;
@@ -43,8 +44,9 @@ export class WorkerTile {
43
44
  collisionBoxArray: CollisionBoxArray;
44
45
 
45
46
  abort: (() => void);
46
- reloadCallback: WorkerTileCallback;
47
47
  vectorTile: VectorTile;
48
+ inFlightDependencies: Cancelable[];
49
+ dependencySentinel: number;
48
50
 
49
51
  constructor(params: WorkerTileParameters) {
50
52
  this.tileID = new OverscaledTileID(params.tileID.overscaledZ, params.tileID.wrap, params.tileID.canonical.z, params.tileID.canonical.x, params.tileID.canonical.y);
@@ -58,6 +60,8 @@ export class WorkerTile {
58
60
  this.collectResourceTiming = !!params.collectResourceTiming;
59
61
  this.returnDependencies = !!params.returnDependencies;
60
62
  this.promoteId = params.promoteId;
63
+ this.inFlightDependencies = [];
64
+ this.dependencySentinel = -1;
61
65
  }
62
66
 
63
67
  parse(data: VectorTile, layerIndex: StyleLayerIndex, availableImages: Array<string>, actor: Actor, callback: WorkerTileCallback) {
@@ -138,40 +142,55 @@ export class WorkerTile {
138
142
  let patternMap: {[_: string]: StyleImage};
139
143
 
140
144
  const stacks = mapObject(options.glyphDependencies, (glyphs) => Object.keys(glyphs).map(Number));
145
+
146
+ this.inFlightDependencies.forEach((request) => request?.cancel());
147
+ this.inFlightDependencies = [];
148
+
149
+ // cancelling seems to be not sufficient, we seems to still manage to get a callback hit, so use a sentinel to drop stale results
150
+ const dependencySentinel = ++this.dependencySentinel;
141
151
  if (Object.keys(stacks).length) {
142
- actor.send('getGlyphs', {uid: this.uid, stacks, source: this.source, tileID: this.tileID, type: 'glyphs'}, (err, result) => {
152
+ this.inFlightDependencies.push(actor.send('getGlyphs', {uid: this.uid, stacks, source: this.source, tileID: this.tileID, type: 'glyphs'}, (err, result) => {
153
+ if (dependencySentinel !== this.dependencySentinel) {
154
+ return;
155
+ }
143
156
  if (!error) {
144
157
  error = err;
145
158
  glyphMap = result;
146
159
  maybePrepare.call(this);
147
160
  }
148
- });
161
+ }));
149
162
  } else {
150
163
  glyphMap = {};
151
164
  }
152
165
 
153
166
  const icons = Object.keys(options.iconDependencies);
154
167
  if (icons.length) {
155
- actor.send('getImages', {icons, source: this.source, tileID: this.tileID, type: 'icons'}, (err, result) => {
168
+ this.inFlightDependencies.push(actor.send('getImages', {icons, source: this.source, tileID: this.tileID, type: 'icons'}, (err, result) => {
169
+ if (dependencySentinel !== this.dependencySentinel) {
170
+ return;
171
+ }
156
172
  if (!error) {
157
173
  error = err;
158
174
  iconMap = result;
159
175
  maybePrepare.call(this);
160
176
  }
161
- });
177
+ }));
162
178
  } else {
163
179
  iconMap = {};
164
180
  }
165
181
 
166
182
  const patterns = Object.keys(options.patternDependencies);
167
183
  if (patterns.length) {
168
- actor.send('getImages', {icons: patterns, source: this.source, tileID: this.tileID, type: 'patterns'}, (err, result) => {
184
+ this.inFlightDependencies.push(actor.send('getImages', {icons: patterns, source: this.source, tileID: this.tileID, type: 'patterns'}, (err, result) => {
185
+ if (dependencySentinel !== this.dependencySentinel) {
186
+ return;
187
+ }
169
188
  if (!error) {
170
189
  error = err;
171
190
  patternMap = result;
172
191
  maybePrepare.call(this);
173
192
  }
174
- });
193
+ }));
175
194
  } else {
176
195
  patternMap = {};
177
196
  }
@@ -1659,10 +1659,12 @@ describe('#flyTo', () => {
1659
1659
 
1660
1660
  test('check elevation callbacks', done => {
1661
1661
  const camera = createCamera();
1662
+ camera.terrain = {
1663
+ getElevationForLngLatZoom: () => 100,
1664
+ getMinTileElevationForLngLatZoom: () => 200
1665
+ };
1662
1666
  camera.transform = {
1663
1667
  elevation: 0,
1664
- freezeElevation: false,
1665
- getElevation: () => 100,
1666
1668
  recalculateZoom: () => true
1667
1669
  };
1668
1670
 
@@ -1670,15 +1672,15 @@ describe('#flyTo', () => {
1670
1672
  // expect(camera._elevationCenter).toBe([10, 0]);
1671
1673
  expect(camera._elevationStart).toBe(0);
1672
1674
  expect(camera._elevationTarget).toBe(100);
1673
- expect(camera.transform.freezeElevation).toBeTruthy();
1675
+ expect(camera._elevationFreeze).toBeTruthy();
1674
1676
 
1675
- camera.transform.getElevation = () => 200;
1677
+ camera.terrain.getElevationForLngLatZoom = () => 200;
1676
1678
  camera._updateElevation(0.5);
1677
1679
  expect(camera._elevationStart).toBe(-100);
1678
1680
  expect(camera._elevationTarget).toBe(200);
1679
1681
 
1680
1682
  camera._finalizeElevation();
1681
- expect(camera.transform.freezeElevation).toBeFalsy();
1683
+ expect(camera._elevationFreeze).toBeFalsy();
1682
1684
 
1683
1685
  done();
1684
1686
  });
@@ -2015,9 +2017,10 @@ describe('queryTerrainElevation', () => {
2015
2017
  test('should return the correct elevation', () => {
2016
2018
  // Set up mock transform and terrain objects
2017
2019
  const transform = new Transform(0, 22, 0, 60, true);
2018
- transform.getElevation = jest.fn().mockReturnValue(200);
2019
2020
  transform.elevation = 50;
2020
- const terrain = {} as Terrain;
2021
+ const terrain = {
2022
+ getElevationForLngLatZoom: jest.fn().mockReturnValue(200)
2023
+ } as any as Terrain;
2021
2024
 
2022
2025
  // Set up camera with mock transform and terrain
2023
2026
  camera.transform = transform;
@@ -2029,12 +2032,12 @@ describe('queryTerrainElevation', () => {
2029
2032
  const result = camera.queryTerrainElevation(lngLatLike);
2030
2033
 
2031
2034
  // Check that transform.getElevation was called with the correct arguments
2032
- expect(transform.getElevation).toHaveBeenCalledWith(
2035
+ expect(terrain.getElevationForLngLatZoom).toHaveBeenCalledWith(
2033
2036
  expect.objectContaining({
2034
2037
  lng: lngLatLike[0],
2035
2038
  lat: lngLatLike[1],
2036
2039
  }),
2037
- terrain
2040
+ transform.tileZoom
2038
2041
  );
2039
2042
 
2040
2043
  // Check that the correct elevation value was returned