@mapwhit/tilerenderer 0.47.1 → 0.48.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 (76) hide show
  1. package/build/min/package.json +1 -1
  2. package/package.json +1 -2
  3. package/src/data/array_types.js +1 -1
  4. package/src/data/bucket/circle_bucket.js +1 -1
  5. package/src/data/bucket/fill_bucket.js +1 -1
  6. package/src/data/bucket/fill_extrusion_bucket.js +1 -1
  7. package/src/data/bucket/heatmap_bucket.js +1 -1
  8. package/src/data/bucket/line_bucket.js +1 -1
  9. package/src/data/bucket/symbol_bucket.js +26 -12
  10. package/src/data/dem_data.js +1 -1
  11. package/src/data/feature_index.js +43 -82
  12. package/src/data/program_configuration.js +19 -11
  13. package/src/data/segment.js +2 -2
  14. package/src/geo/transform.js +4 -2
  15. package/src/gl/color_mode.js +6 -6
  16. package/src/index.js +3 -1
  17. package/src/render/glyph_atlas.js +1 -1
  18. package/src/render/glyph_manager.js +43 -48
  19. package/src/render/image_atlas.js +1 -1
  20. package/src/render/image_manager.js +9 -37
  21. package/src/source/geojson_source.js +49 -93
  22. package/src/source/geojson_worker_source.js +33 -134
  23. package/src/source/image_source.js +9 -14
  24. package/src/source/load_tilejson.js +27 -34
  25. package/src/source/raster_dem_tile_source.js +27 -40
  26. package/src/source/raster_tile_source.js +53 -62
  27. package/src/source/rtl_text_plugin.js +3 -1
  28. package/src/source/source_cache.js +23 -21
  29. package/src/source/source_state.js +17 -26
  30. package/src/source/tile.js +6 -5
  31. package/src/source/tile_id.js +1 -1
  32. package/src/source/vector_tile_source.js +56 -73
  33. package/src/source/vector_tile_worker_source.js +20 -85
  34. package/src/source/worker.js +37 -103
  35. package/src/source/worker_tile.js +39 -84
  36. package/src/style/load_sprite.js +14 -17
  37. package/src/style/properties.js +1 -1
  38. package/src/style/style.js +22 -37
  39. package/src/style/style_layer/symbol_style_layer_properties.js +1 -1
  40. package/src/style/style_layer_index.js +17 -23
  41. package/src/style-spec/expression/compound_expression.js +30 -16
  42. package/src/style-spec/expression/definitions/coercion.js +13 -0
  43. package/src/style-spec/expression/definitions/comparison.js +193 -0
  44. package/src/style-spec/expression/definitions/formatted.js +123 -0
  45. package/src/style-spec/expression/definitions/index.js +10 -60
  46. package/src/style-spec/expression/definitions/interpolate.js +17 -7
  47. package/src/style-spec/expression/definitions/literal.js +5 -0
  48. package/src/style-spec/expression/parsing_context.js +4 -0
  49. package/src/style-spec/expression/types.js +12 -1
  50. package/src/style-spec/feature_filter/index.js +1 -1
  51. package/src/style-spec/reference/v8.json +120 -49
  52. package/src/symbol/anchor.js +1 -1
  53. package/src/symbol/collision_index.js +23 -16
  54. package/src/symbol/get_anchors.js +11 -22
  55. package/src/symbol/grid_index.js +176 -182
  56. package/src/symbol/mergelines.js +51 -48
  57. package/src/symbol/opacity_state.js +1 -1
  58. package/src/symbol/placement.js +8 -2
  59. package/src/symbol/quads.js +7 -6
  60. package/src/symbol/shaping.js +185 -40
  61. package/src/symbol/symbol_layout.js +9 -6
  62. package/src/symbol/transform_text.js +12 -1
  63. package/src/ui/camera.js +82 -85
  64. package/src/ui/map.js +13 -57
  65. package/src/util/actor.js +46 -42
  66. package/src/util/browser.js +6 -0
  67. package/src/util/dictionary_coder.js +13 -21
  68. package/src/util/dispatcher.js +14 -17
  69. package/src/util/image.js +1 -1
  70. package/src/util/loader/image.js +11 -11
  71. package/src/util/polyfill.js +16 -0
  72. package/src/util/task_queue.js +39 -43
  73. package/src/util/transfer_registry.js +167 -0
  74. package/src/util/web_worker_transfer.js +5 -190
  75. package/src/source/raster_dem_tile_worker_source.js +0 -26
  76. package/src/style-spec/expression/definitions/equals.js +0 -93
@@ -22,10 +22,9 @@ const padding = 1;
22
22
  to refactor this.
23
23
  */
24
24
  class ImageManager {
25
+ #loadedState = Promise.withResolvers();
25
26
  constructor() {
26
27
  this.images = {};
27
- this.loaded = false;
28
- this.requestors = [];
29
28
 
30
29
  this.shelfPack = new ShelfPack(64, 64, { autoResize: true });
31
30
  this.patterns = {};
@@ -34,22 +33,15 @@ class ImageManager {
34
33
  }
35
34
 
36
35
  isLoaded() {
37
- return this.loaded;
36
+ return !!this.#loadedState.loaded;
38
37
  }
39
38
 
40
- setLoaded(loaded) {
41
- if (this.loaded === loaded) {
39
+ setLoaded() {
40
+ if (this.#loadedState.loaded) {
42
41
  return;
43
42
  }
44
-
45
- this.loaded = loaded;
46
-
47
- if (loaded) {
48
- for (const { ids, callback } of this.requestors) {
49
- this._notify(ids, callback);
50
- }
51
- this.requestors = [];
52
- }
43
+ this.#loadedState.loaded = true;
44
+ this.#loadedState.resolve();
53
45
  }
54
46
 
55
47
  getImage(id) {
@@ -76,29 +68,10 @@ class ImageManager {
76
68
  return Object.keys(this.images);
77
69
  }
78
70
 
79
- getImages(ids, callback) {
80
- // If the sprite has been loaded, or if all the icon dependencies are already present
81
- // (i.e. if they've been addeded via runtime styling), then notify the requestor immediately.
82
- // Otherwise, delay notification until the sprite is loaded. At that point, if any of the
83
- // dependencies are still unavailable, we'll just assume they are permanently missing.
84
- let hasAllDependencies = true;
85
- if (!this.isLoaded()) {
86
- for (const id of ids) {
87
- if (!this.images[id]) {
88
- hasAllDependencies = false;
89
- }
90
- }
91
- }
92
- if (this.isLoaded() || hasAllDependencies) {
93
- this._notify(ids, callback);
94
- } else {
95
- this.requestors.push({ ids, callback });
96
- }
97
- }
71
+ async getImages(ids) {
72
+ await this.#loadedState.promise;
98
73
 
99
- _notify(ids, callback) {
100
74
  const response = {};
101
-
102
75
  for (const id of ids) {
103
76
  const image = this.images[id];
104
77
  if (image) {
@@ -110,8 +83,7 @@ class ImageManager {
110
83
  };
111
84
  }
112
85
  }
113
-
114
- callback(null, response);
86
+ return response;
115
87
  }
116
88
 
117
89
  // Pattern stuff
@@ -49,9 +49,10 @@ const browser = require('../util/browser');
49
49
  * @see [Create a heatmap from points](https://www.mapbox.com/mapbox-gl-js/example/heatmap/)
50
50
  */
51
51
  class GeoJSONSource extends Evented {
52
- /**
53
- * @private
54
- */
52
+ #pendingDataEvents = new Set();
53
+ #newData = false;
54
+ #updateInProgress = false;
55
+
55
56
  constructor(id, options, dispatcher, eventedParent) {
56
57
  super();
57
58
 
@@ -109,19 +110,7 @@ class GeoJSONSource extends Evented {
109
110
  }
110
111
 
111
112
  load() {
112
- this.fire(new Event('dataloading', { dataType: 'source' }));
113
- this._updateWorkerData(err => {
114
- if (err) {
115
- this.fire(new ErrorEvent(err));
116
- return;
117
- }
118
-
119
- const data = { dataType: 'source', sourceDataType: 'metadata' };
120
-
121
- // although GeoJSON sources contain no metadata, we fire this event to let the SourceCache
122
- // know its ok to start requesting tiles.
123
- this.fire(new Event('data', data));
124
- });
113
+ this.#updateData('metadata');
125
114
  }
126
115
 
127
116
  onAdd(map) {
@@ -137,73 +126,55 @@ class GeoJSONSource extends Evented {
137
126
  */
138
127
  setData(data) {
139
128
  this._data = data;
140
- this.fire(new Event('dataloading', { dataType: 'source' }));
141
- this._updateWorkerData(err => {
142
- if (err) {
143
- return this.fire(new ErrorEvent(err));
144
- }
145
-
146
- const data = { dataType: 'source', sourceDataType: 'content' };
147
- this.fire(new Event('data', data));
148
- });
149
-
129
+ this.#updateData();
150
130
  return this;
151
131
  }
152
132
 
133
+ async #updateData(sourceDataType = 'content') {
134
+ this.#newData = true;
135
+ this.#pendingDataEvents.add(sourceDataType);
136
+ if (this.#updateInProgress) {
137
+ // will handle this update when the current one is done
138
+ return;
139
+ }
140
+ try {
141
+ this.#updateInProgress = true;
142
+ this.fire(new Event('dataloading', { dataType: 'source' }));
143
+ while (this.#newData) {
144
+ this.#newData = false;
145
+ await this._updateWorkerData(this._data);
146
+ }
147
+ this.#pendingDataEvents.forEach(sourceDataType =>
148
+ this.fire(new Event('data', { dataType: 'source', sourceDataType }))
149
+ );
150
+ this.#pendingDataEvents.clear();
151
+ } catch (err) {
152
+ this.fire(new ErrorEvent(err));
153
+ } finally {
154
+ this.#updateInProgress = false;
155
+ }
156
+ }
157
+
153
158
  /*
154
159
  * Responsible for invoking WorkerSource's geojson.loadData target, which
155
160
  * handles loading the geojson data and preparing to serve it up as tiles,
156
161
  * using geojson-vt or supercluster as appropriate.
157
162
  */
158
- _updateWorkerData(callback) {
159
- // biome-ignore lint/suspicious/useAwait: normalize return values as promises
160
- async function loadGeoJSON(data) {
161
- if (typeof data === 'function') {
162
- return data();
163
- }
164
- return data;
163
+ async _updateWorkerData(data) {
164
+ const json = typeof data === 'function' ? await data().catch(() => {}) : data;
165
+ if (!json) {
166
+ throw new Error('no GeoJSON data');
165
167
  }
168
+ const options = { ...this.workerOptions, data: JSON.stringify(json) };
169
+ this.workerID ??= this.dispatcher.nextWorkerId();
166
170
 
167
- const data = this._data;
168
- loadGeoJSON(data)
169
- .catch(() => {})
170
- .then(json => {
171
- if (!json) {
172
- return callback(new Error('no GeoJSON data'));
173
- }
174
- const options = Object.assign({}, this.workerOptions);
175
- options.data = JSON.stringify(json);
176
- // target {this.type}.loadData rather than literally geojson.loadData,
177
- // so that other geojson-like source types can easily reuse this
178
- // implementation
179
- this.workerID = this.dispatcher.send(
180
- `${this.type}.loadData`,
181
- options,
182
- (err, result) => {
183
- if (this._removed || result?.abandoned) {
184
- return;
185
- }
186
-
187
- this._loaded = true;
188
-
189
- if (result?.resourceTiming?.[this.id]) this._resourceTiming = result.resourceTiming[this.id].slice(0);
190
- // Any `loadData` calls that piled up while we were processing
191
- // this one will get coalesced into a single call when this
192
- // 'coalesce' message is processed.
193
- // We would self-send from the worker if we had access to its
194
- // message queue. Waiting instead for the 'coalesce' to round-trip
195
- // through the foreground just means we're throttling the worker
196
- // to run at a little less than full-throttle.
197
- this.dispatcher.send(`${this.type}.coalesce`, { source: options.source }, null, this.workerID);
198
- callback(err);
199
- },
200
- this.workerID
201
- );
202
- });
171
+ // target {this.type}.loadData rather than literally geojson.loadData,
172
+ // so that other geojson-like source types can easily reuse this
173
+ // implementation
174
+ await this.dispatcher.send(`${this.type}.loadData`, options, this.workerID);
203
175
  }
204
176
 
205
- loadTile(tile, callback) {
206
- const message = tile.workerID === undefined ? 'loadTile' : 'reloadTile';
177
+ async loadTile(tile) {
207
178
  const params = {
208
179
  type: this.type,
209
180
  uid: tile.uid,
@@ -216,26 +187,12 @@ class GeoJSONSource extends Evented {
216
187
  showCollisionBoxes: this.map.showCollisionBoxes
217
188
  };
218
189
 
219
- tile.workerID = this.dispatcher.send(
220
- message,
221
- params,
222
- (err, data) => {
223
- tile.unloadVectorData();
224
-
225
- if (tile.aborted) {
226
- return callback(null);
227
- }
228
-
229
- if (err) {
230
- return callback(err);
231
- }
232
-
233
- tile.loadVectorData(data, this.map.painter, message === 'reloadTile');
234
-
235
- return callback(null);
236
- },
237
- this.workerID
238
- );
190
+ const justReloaded = tile.workerID != null;
191
+ tile.workerID ??= this.dispatcher.nextWorkerId(this.workerID);
192
+ const data = await this.dispatcher.send('loadTile', params, tile.workerID).finally(() => tile.unloadVectorData());
193
+ if (!tile.aborted) {
194
+ tile.loadVectorData(data, this.map.painter, justReloaded);
195
+ }
239
196
  }
240
197
 
241
198
  abortTile(tile) {
@@ -244,12 +201,11 @@ class GeoJSONSource extends Evented {
244
201
 
245
202
  unloadTile(tile) {
246
203
  tile.unloadVectorData();
247
- this.dispatcher.send('removeTile', { uid: tile.uid, type: this.type, source: this.id }, null, tile.workerID);
248
204
  }
249
205
 
250
206
  onRemove() {
251
207
  this._removed = true;
252
- this.dispatcher.send('removeSource', { type: this.type, source: this.id }, null, this.workerID);
208
+ return this.dispatcher.send('removeSource', { type: this.type, source: this.id }, this.workerID);
253
209
  }
254
210
 
255
211
  serialize() {
@@ -3,19 +3,25 @@ const GeoJSONWrapper = require('./geojson_wrapper');
3
3
  const vtpbf = require('@mapwhit/vt-pbf');
4
4
  const supercluster = require('supercluster');
5
5
  const geojsonvt = require('geojson-vt');
6
- const assert = require('assert');
7
6
  const VectorTileWorkerSource = require('./vector_tile_worker_source');
8
7
 
9
- function loadGeoJSONTile(params, callback) {
10
- const canonical = params.tileID.canonical;
11
-
8
+ function loadGeoJSONTile(params) {
12
9
  if (!this._geoJSONIndex) {
13
- return callback(null, null); // we couldn't load the file
10
+ if (!this._createGeoJSONIndex) {
11
+ return; // we couldn't load the file
12
+ }
13
+
14
+ try {
15
+ this._geoJSONIndex = this._createGeoJSONIndex();
16
+ } finally {
17
+ this._createGeoJSONIndex = null;
18
+ }
14
19
  }
15
20
 
16
- const geoJSONTile = this._geoJSONIndex.getTile(canonical.z, canonical.x, canonical.y);
21
+ const { z, x, y } = params.tileID.canonical;
22
+ const geoJSONTile = this._geoJSONIndex.getTile(z, x, y);
17
23
  if (!geoJSONTile) {
18
- return callback(null, null); // nothing in the given tile
24
+ return; // nothing in the given tile
19
25
  }
20
26
 
21
27
  const geojsonWrapper = new GeoJSONWrapper(geoJSONTile.features);
@@ -29,14 +35,12 @@ function loadGeoJSONTile(params, callback) {
29
35
  pbf = new Uint8Array(pbf);
30
36
  }
31
37
 
32
- callback(null, {
38
+ return {
33
39
  vectorTile: geojsonWrapper,
34
40
  rawData: pbf.buffer
35
- });
41
+ };
36
42
  }
37
43
 
38
- // 'loadData' received while coalescing, trigger one more 'loadData' on receiving 'coalesced'
39
-
40
44
  /**
41
45
  * The {@link WorkerSource} implementation that supports {@link GeoJSONSource}.
42
46
  * This class is designed to be easily reused to support custom source types
@@ -45,7 +49,6 @@ function loadGeoJSONTile(params, callback) {
45
49
  * `new GeoJSONWorkerSource(actor, layerIndex, customLoadGeoJSONFunction)`.
46
50
  * For a full example, see [mapbox-gl-topojson](https://github.com/developmentseed/mapbox-gl-topojson).
47
51
  *
48
- * @private
49
52
  */
50
53
  class GeoJSONWorkerSource extends VectorTileWorkerSource {
51
54
  /**
@@ -69,141 +72,37 @@ class GeoJSONWorkerSource extends VectorTileWorkerSource {
69
72
  * expecting `callback(error, data)` to be called with either an error or a
70
73
  * parsed GeoJSON object.
71
74
  *
72
- * When `loadData` requests come in faster than they can be processed,
73
- * they are coalesced into a single request using the latest data.
74
- * See {@link GeoJSONWorkerSource#coalesce}
75
- *
76
75
  * @param params
77
76
  * @param callback
78
77
  */
79
- loadData(params, callback) {
80
- if (this._pendingCallback) {
81
- // Tell the foreground the previous call has been abandoned
82
- this._pendingCallback(null, { abandoned: true });
83
- }
84
- this._pendingCallback = callback;
85
- this._pendingLoadDataParams = params;
86
-
87
- if (this._state && this._state !== 'Idle') {
88
- this._state = 'NeedsLoadData';
89
- } else {
90
- this._state = 'Coalescing';
91
- this._loadData();
92
- }
93
- }
94
-
95
- /**
96
- * Internal implementation: called directly by `loadData`
97
- * or by `coalesce` using stored parameters.
98
- */
99
- _loadData() {
100
- if (!this._pendingCallback || !this._pendingLoadDataParams) {
101
- assert(false);
102
- return;
103
- }
104
- const callback = this._pendingCallback;
105
- const params = this._pendingLoadDataParams;
106
- delete this._pendingCallback;
107
- delete this._pendingLoadDataParams;
108
- this.loadGeoJSON(params, (err, data) => {
109
- if (err || !data) {
110
- return callback(err);
111
- }
112
- if (typeof data !== 'object') {
113
- return callback(new Error('Input data is not a valid GeoJSON object.'));
114
- }
115
- rewind(data, true);
116
-
117
- try {
118
- this._geoJSONIndex = params.cluster
119
- ? supercluster(params.superclusterOptions).load(data.features)
120
- : geojsonvt(data, params.geojsonVtOptions);
121
- } catch (err) {
122
- return callback(err);
123
- }
124
-
125
- this.loaded = {};
126
-
127
- const result = {};
128
- callback(null, result);
129
- });
78
+ loadData(params) {
79
+ const data = this.loadGeoJSON(params);
80
+ this._geoJSONIndex = null;
81
+ this._createGeoJSONIndex = params.cluster
82
+ ? () => {
83
+ rewind(data, true);
84
+ return supercluster(params.superclusterOptions).load(data.features);
85
+ }
86
+ : () => {
87
+ rewind(data, true);
88
+ return geojsonvt(data, params.geojsonVtOptions);
89
+ };
130
90
  }
131
91
 
132
92
  /**
133
- * While processing `loadData`, we coalesce all further
134
- * `loadData` messages into a single call to _loadData
135
- * that will happen once we've finished processing the
136
- * first message. {@link GeoJSONSource#_updateWorkerData}
137
- * is responsible for sending us the `coalesce` message
138
- * at the time it receives a response from `loadData`
139
- *
140
- * State: Idle
141
- * ↑ |
142
- * 'coalesce' 'loadData'
143
- * | (triggers load)
144
- * | ↓
145
- * State: Coalescing
146
- * ↑ |
147
- * (triggers load) |
148
- * 'coalesce' 'loadData'
149
- * | ↓
150
- * State: NeedsLoadData
151
- */
152
- coalesce() {
153
- if (this._state === 'Coalescing') {
154
- this._state = 'Idle';
155
- } else if (this._state === 'NeedsLoadData') {
156
- this._state = 'Coalescing';
157
- this._loadData();
158
- }
159
- }
160
-
161
- /**
162
- * Implements {@link WorkerSource#reloadTile}.
163
- *
164
- * If the tile is loaded, uses the implementation in VectorTileWorkerSource.
165
- * Otherwise, such as after a setData() call, we load the tile fresh.
166
- *
167
- * @param params
168
- * @param params.uid The UID for this tile.
169
- */
170
- reloadTile(params, callback) {
171
- const loaded = this.loaded;
172
- const uid = params.uid;
173
-
174
- if (loaded?.[uid]) {
175
- return super.reloadTile(params, callback);
176
- }
177
- return this.loadTile(params, callback);
178
- }
179
-
180
- /**
181
- * Fetch and parse GeoJSON according to the given params. Calls `callback`
182
- * with `(err, data)`, where `data` is a parsed GeoJSON object.
93
+ * Fetch and parse GeoJSON according to the given params.
183
94
  *
184
95
  * GeoJSON is expected as a literal (string or object) `params.data`.
185
96
  *
186
97
  * @param params
187
98
  * @param [params.data] Literal GeoJSON data. Must be provided.
188
99
  */
189
- loadGeoJSON(params, callback) {
190
- if (typeof params.data === 'string') {
191
- try {
192
- return callback(null, JSON.parse(params.data));
193
- } catch (e) {
194
- return callback(new Error('Input data is not a valid GeoJSON object.'));
195
- }
196
- } else {
197
- return callback(new Error('Input data is not a valid GeoJSON object.'));
198
- }
199
- }
200
-
201
- removeSource(params, callback) {
202
- if (this._pendingCallback) {
203
- // Don't leak callbacks
204
- this._pendingCallback(null, { abandoned: true });
100
+ loadGeoJSON(params) {
101
+ try {
102
+ return JSON.parse(params.data);
103
+ } catch (e) {
104
+ throw new Error('Input data is not a valid GeoJSON object.');
205
105
  }
206
- callback();
207
106
  }
208
107
  }
209
108
 
@@ -61,19 +61,15 @@ class ImageSource extends Evented {
61
61
  this.options = options;
62
62
  }
63
63
 
64
- load() {
64
+ async load() {
65
65
  this.fire(new Event('dataloading', { dataType: 'source' }));
66
-
67
66
  this.url = this.options.url;
68
-
69
- loadImage(this.url, (err, image) => {
70
- if (err) {
71
- this.fire(new ErrorEvent(err));
72
- } else if (image) {
73
- this.image = image;
74
- this._finishLoading();
75
- }
76
- });
67
+ try {
68
+ this.image = await loadImage(this.url);
69
+ this._finishLoading();
70
+ } catch (err) {
71
+ this.fire(new ErrorEvent(err));
72
+ }
77
73
  }
78
74
 
79
75
  _finishLoading() {
@@ -180,7 +176,7 @@ class ImageSource extends Evented {
180
176
  }
181
177
  }
182
178
 
183
- loadTile(tile, callback) {
179
+ loadTile(tile) {
184
180
  // We have a single tile -- whoose coordinates are this.tileID -- that
185
181
  // covers the image we want to render. If that's the one being
186
182
  // requested, set it up with the image; otherwise, mark the tile as
@@ -190,11 +186,10 @@ class ImageSource extends Evented {
190
186
  if (this.tileID?.equals(tile.tileID.canonical)) {
191
187
  this.tiles[String(tile.tileID.wrap)] = tile;
192
188
  tile.buckets = {};
193
- callback(null);
194
189
  } else {
195
190
  tile.state = 'errored';
196
- callback(null);
197
191
  }
192
+ return Promise.resolve();
198
193
  }
199
194
 
200
195
  serialize() {
@@ -1,40 +1,33 @@
1
1
  const { pick } = require('../util/object');
2
- const browser = require('../util/browser');
3
2
 
4
- module.exports = function (options, callback) {
5
- function loaded(err, tileJSON) {
6
- if (err) return callback(err);
7
- if (tileJSON) {
8
- const { resourceSets } = tileJSON;
9
- if (resourceSets) {
10
- if (!resourceSets.length) {
11
- return callback('expected resources');
12
- }
13
- const { resources } = resourceSets[0];
14
- if (!resources?.length) {
15
- return callback('expected resources');
16
- }
17
- const { imageUrl, imageUrlSubdomains } = resources[0];
18
- const result = Object.assign(
19
- {
20
- tiles: imageUrlSubdomains.map(sub => imageUrl.replace('{subdomain}', sub).replace('http:', 'https:'))
21
- },
22
- options
23
- );
24
- delete result.url;
25
- return callback(null, result);
26
- }
27
- const result = pick(tileJSON, ['tiles', 'minzoom', 'maxzoom', 'attribution', 'mapbox_logo', 'bounds']);
28
-
29
- if (tileJSON.vector_layers) {
30
- result.vectorLayers = tileJSON.vector_layers;
31
- result.vectorLayerIds = result.vectorLayers.map(layer => layer.id);
32
- }
3
+ module.exports = function (tileJSON) {
4
+ return tileJSON.resourceSets ? fromResourseSets(tileJSON) : fromTileJSON(tileJSON);
5
+ };
33
6
 
34
- callback(null, result);
35
- }
7
+ function fromTileJSON(tileJSON) {
8
+ const result = pick(tileJSON, ['tiles', 'minzoom', 'maxzoom', 'attribution', 'bounds']);
9
+ if (tileJSON.vector_layers) {
10
+ result.vectorLayers = tileJSON.vector_layers;
11
+ result.vectorLayerIds = result.vectorLayers.map(layer => layer.id);
36
12
  }
13
+ return result;
14
+ }
37
15
 
16
+ function fromResourseSets(tileJSON) {
17
+ const { resourceSets } = tileJSON;
18
+ if (!resourceSets.length) {
19
+ throw new Error('expected resources');
20
+ }
21
+ const { resources } = resourceSets[0];
22
+ if (!resources?.length) {
23
+ throw new Error('expected resources');
24
+ }
25
+ const { imageUrl, imageUrlSubdomains } = resources[0];
26
+ const result = {
27
+ tiles: imageUrlSubdomains.map(sub => imageUrl.replace('{subdomain}', sub).replace('http:', 'https:')),
28
+ ...tileJSON
29
+ };
38
30
  // expects already loaded object, `url` property is ignored
39
- browser.frame(() => loaded(null, options));
40
- };
31
+ delete result.url;
32
+ return result;
33
+ }