@mapwhit/tilerenderer 0.52.1 → 1.0.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 (142) hide show
  1. package/README.md +7 -0
  2. package/build/min/package.json +1 -1
  3. package/build/min/src/shaders/_prelude.fragment.glsl.js +2 -2
  4. package/build/min/src/shaders/_prelude.vertex.glsl.js +2 -2
  5. package/build/min/src/shaders/background.fragment.glsl.js +2 -2
  6. package/build/min/src/shaders/background.vertex.glsl.js +1 -1
  7. package/build/min/src/shaders/background_pattern.fragment.glsl.js +2 -2
  8. package/build/min/src/shaders/background_pattern.vertex.glsl.js +1 -1
  9. package/build/min/src/shaders/circle.fragment.glsl.js +2 -2
  10. package/build/min/src/shaders/circle.vertex.glsl.js +2 -2
  11. package/build/min/src/shaders/clipping_mask.fragment.glsl.js +1 -1
  12. package/build/min/src/shaders/clipping_mask.vertex.glsl.js +1 -1
  13. package/build/min/src/shaders/collision_box.fragment.glsl.js +1 -1
  14. package/build/min/src/shaders/collision_box.vertex.glsl.js +1 -1
  15. package/build/min/src/shaders/collision_circle.fragment.glsl.js +1 -1
  16. package/build/min/src/shaders/collision_circle.vertex.glsl.js +1 -1
  17. package/build/min/src/shaders/debug.fragment.glsl.js +1 -1
  18. package/build/min/src/shaders/debug.vertex.glsl.js +1 -1
  19. package/build/min/src/shaders/fill.fragment.glsl.js +2 -2
  20. package/build/min/src/shaders/fill.vertex.glsl.js +2 -2
  21. package/build/min/src/shaders/fill_extrusion.fragment.glsl.js +2 -2
  22. package/build/min/src/shaders/fill_extrusion.vertex.glsl.js +2 -2
  23. package/build/min/src/shaders/fill_extrusion_pattern.fragment.glsl.js +2 -2
  24. package/build/min/src/shaders/fill_extrusion_pattern.vertex.glsl.js +2 -2
  25. package/build/min/src/shaders/fill_outline.fragment.glsl.js +2 -2
  26. package/build/min/src/shaders/fill_outline.vertex.glsl.js +2 -2
  27. package/build/min/src/shaders/fill_outline_pattern.fragment.glsl.js +2 -2
  28. package/build/min/src/shaders/fill_outline_pattern.vertex.glsl.js +2 -2
  29. package/build/min/src/shaders/fill_pattern.fragment.glsl.js +2 -2
  30. package/build/min/src/shaders/fill_pattern.vertex.glsl.js +2 -2
  31. package/build/min/src/shaders/heatmap.fragment.glsl.js +2 -2
  32. package/build/min/src/shaders/heatmap.vertex.glsl.js +2 -2
  33. package/build/min/src/shaders/heatmap_texture.fragment.glsl.js +2 -2
  34. package/build/min/src/shaders/heatmap_texture.vertex.glsl.js +1 -1
  35. package/build/min/src/shaders/hillshade.fragment.glsl.js +2 -2
  36. package/build/min/src/shaders/hillshade.vertex.glsl.js +1 -1
  37. package/build/min/src/shaders/hillshade_prepare.fragment.glsl.js +2 -2
  38. package/build/min/src/shaders/hillshade_prepare.vertex.glsl.js +1 -1
  39. package/build/min/src/shaders/line.fragment.glsl.js +2 -2
  40. package/build/min/src/shaders/line.vertex.glsl.js +2 -2
  41. package/build/min/src/shaders/line_gradient.fragment.glsl.js +2 -2
  42. package/build/min/src/shaders/line_gradient.vertex.glsl.js +2 -2
  43. package/build/min/src/shaders/line_pattern.fragment.glsl.js +2 -2
  44. package/build/min/src/shaders/line_pattern.vertex.glsl.js +2 -2
  45. package/build/min/src/shaders/line_sdf.fragment.glsl.js +2 -2
  46. package/build/min/src/shaders/line_sdf.vertex.glsl.js +2 -2
  47. package/build/min/src/shaders/raster.fragment.glsl.js +2 -2
  48. package/build/min/src/shaders/raster.vertex.glsl.js +1 -1
  49. package/build/min/src/shaders/symbol_icon.fragment.glsl.js +2 -2
  50. package/build/min/src/shaders/symbol_icon.vertex.glsl.js +2 -2
  51. package/build/min/src/shaders/symbol_sdf.fragment.glsl.js +2 -2
  52. package/build/min/src/shaders/symbol_sdf.vertex.glsl.js +2 -2
  53. package/package.json +3 -3
  54. package/src/data/array_types.js +1 -36
  55. package/src/data/bucket/circle_bucket.js +0 -3
  56. package/src/data/bucket/fill_bucket.js +0 -3
  57. package/src/data/bucket/fill_extrusion_bucket.js +0 -3
  58. package/src/data/bucket/heatmap_bucket.js +0 -4
  59. package/src/data/bucket/line_bucket.js +1 -4
  60. package/src/data/bucket/pattern_bucket_features.js +2 -2
  61. package/src/data/bucket/symbol_bucket.js +87 -126
  62. package/src/data/bucket.js +26 -21
  63. package/src/data/dem_data.js +0 -3
  64. package/src/data/feature_index.js +3 -8
  65. package/src/data/program_configuration.js +3 -12
  66. package/src/data/segment.js +0 -4
  67. package/src/render/draw_background.js +3 -3
  68. package/src/render/draw_circle.js +4 -4
  69. package/src/render/draw_fill.js +8 -8
  70. package/src/render/draw_fill_extrusion.js +8 -8
  71. package/src/render/draw_heatmap.js +4 -4
  72. package/src/render/draw_line.js +6 -6
  73. package/src/render/draw_raster.js +6 -6
  74. package/src/render/draw_symbol.js +16 -16
  75. package/src/render/glyph_atlas.js +0 -3
  76. package/src/render/glyph_manager.js +1 -2
  77. package/src/render/image_atlas.js +0 -4
  78. package/src/render/image_manager.js +33 -19
  79. package/src/render/painter.js +13 -14
  80. package/src/render/program/circle_program.js +4 -4
  81. package/src/render/program/fill_extrusion_program.js +1 -1
  82. package/src/render/program/heatmap_program.js +1 -1
  83. package/src/render/program/hillshade_program.js +6 -6
  84. package/src/render/program/line_program.js +3 -3
  85. package/src/render/program/raster_program.js +6 -6
  86. package/src/source/geojson_source.js +15 -24
  87. package/src/source/geojson_worker_source.js +40 -68
  88. package/src/source/geojson_wrapper.js +9 -1
  89. package/src/source/image_source.js +6 -16
  90. package/src/source/query_features.js +4 -5
  91. package/src/source/raster_dem_tile_source.js +45 -64
  92. package/src/source/raster_tile_source.js +1 -6
  93. package/src/source/resources/glyphs.js +2 -2
  94. package/src/source/resources/index.js +3 -9
  95. package/src/source/rtl_text_plugin.js +58 -31
  96. package/src/source/source.js +11 -13
  97. package/src/source/source_cache.js +135 -151
  98. package/src/source/source_state.js +101 -12
  99. package/src/source/tile.js +32 -46
  100. package/src/source/tile_bounds.js +26 -26
  101. package/src/source/tile_id.js +2 -5
  102. package/src/source/vector_tile_source.js +14 -14
  103. package/src/source/vector_tile_worker_source.js +19 -23
  104. package/src/source/worker_tile.js +120 -117
  105. package/src/style/create_style_layer.js +1 -1
  106. package/src/style/pauseable_placement.js +4 -5
  107. package/src/style/properties.js +1 -8
  108. package/src/style/query_utils.js +3 -3
  109. package/src/style/style.js +263 -195
  110. package/src/style/style_layer/circle_style_layer.js +13 -11
  111. package/src/style/style_layer/fill_extrusion_style_layer.js +42 -27
  112. package/src/style/style_layer/fill_style_layer.js +5 -5
  113. package/src/style/style_layer/heatmap_style_layer.js +1 -1
  114. package/src/style/style_layer/hillshade_style_layer.js +1 -1
  115. package/src/style/style_layer/line_style_layer.js +23 -19
  116. package/src/style/style_layer/symbol_style_layer.js +13 -13
  117. package/src/style/style_layer.js +48 -30
  118. package/src/style/style_layer_index.js +16 -41
  119. package/src/symbol/anchor.js +0 -4
  120. package/src/symbol/cross_tile_symbol_index.js +2 -5
  121. package/src/symbol/opacity_state.js +0 -4
  122. package/src/symbol/placement.js +3 -3
  123. package/src/symbol/quads.js +4 -4
  124. package/src/symbol/symbol_layout.js +7 -7
  125. package/src/symbol/transform_text.js +1 -1
  126. package/src/ui/map.js +49 -11
  127. package/src/util/group_layers.js +41 -0
  128. package/src/util/image.js +0 -5
  129. package/src/util/key.js +21 -0
  130. package/src/util/object.js +8 -53
  131. package/src/worker.js +1 -4
  132. package/src/source/resources/images.js +0 -68
  133. package/src/source/worker.js +0 -110
  134. package/src/source/worker_source.js +0 -14
  135. package/src/style-spec/deref.js +0 -51
  136. package/src/style-spec/group_by_layout.js +0 -46
  137. package/src/util/actor.js +0 -108
  138. package/src/util/dispatcher.js +0 -65
  139. package/src/util/global_worker_pool.js +0 -15
  140. package/src/util/transfer_registry.js +0 -168
  141. package/src/util/web_worker_transfer.js +0 -43
  142. package/src/util/worker_pool.js +0 -41
@@ -3,12 +3,10 @@ const Tile = require('./tile');
3
3
  const { Event, ErrorEvent, Evented } = require('@mapwhit/events');
4
4
  const TileCache = require('./tile_cache');
5
5
  const Coordinate = require('../geo/coordinate');
6
- const { keysDifference } = require('../util/object');
7
6
  const EXTENT = require('../data/extent');
8
7
  const { default: Point } = require('@mapbox/point-geometry');
9
8
  const browser = require('../util/browser');
10
9
  const { OverscaledTileID } = require('./tile_id');
11
- const assert = require('assert');
12
10
  const SourceFeatureState = require('./source_state');
13
11
 
14
12
  /**
@@ -23,47 +21,42 @@ const SourceFeatureState = require('./source_state');
23
21
  * @private
24
22
  */
25
23
  class SourceCache extends Evented {
26
- constructor(id, options, dispatcher) {
24
+ // this.#sourceLoaded signifies that the TileJSON is loaded if applicable.
25
+ // if the source type does not come with a TileJSON, the flag signifies the
26
+ // source data has loaded (i.e geojson has been tiled on the worker and is ready)
27
+ #sourceLoaded = false;
28
+ #sourceErrored = false;
29
+ #paused = false;
30
+ #shouldReloadOnResume = false;
31
+
32
+ constructor(id, options, workerOpts) {
27
33
  super();
28
34
  this.id = id;
29
- this.dispatcher = dispatcher;
30
35
 
31
36
  this.on('data', e => {
32
- // this._sourceLoaded signifies that the TileJSON is loaded if applicable.
33
- // if the source type does not come with a TileJSON, the flag signifies the
34
- // source data has loaded (i.e geojson has been tiled on the worker and is ready)
35
- if (e.dataType === 'source' && e.sourceDataType === 'metadata') this._sourceLoaded = true;
37
+ if (e.dataType !== 'source') return;
38
+ if (e.sourceDataType === 'metadata') this.#sourceLoaded = true;
36
39
 
37
40
  // for sources with mutable data, this event fires when the underlying data
38
41
  // to a source is changed. (i.e. GeoJSONSource#setData and ImageSource#serCoordinates)
39
- if (this._sourceLoaded && !this._paused && e.dataType === 'source' && e.sourceDataType === 'content') {
42
+ if (this.#sourceLoaded && !this.#paused && e.sourceDataType === 'content') {
40
43
  this.reload();
41
- if (this.transform) {
42
- this.update(this.transform);
43
- }
44
+ if (this.transform) this.update(this.transform);
44
45
  }
45
46
  });
46
47
 
47
- this.on('error', () => {
48
- this._sourceErrored = true;
49
- });
48
+ this.on('error', () => (this.#sourceErrored = true));
50
49
 
51
- this._source = createSource(id, options, dispatcher, this);
50
+ this._source = createSource(id, options, this, workerOpts);
52
51
 
53
- this._tiles = {};
52
+ this._tiles = new Map();
54
53
  this._cache = new TileCache(0, this._unloadTile.bind(this));
55
- this._maxTileCacheSize = null;
56
-
57
- this._isIdRenderable = this._isIdRenderable.bind(this);
58
- this._isIdRenderableForSymbols = this._isIdRenderableForSymbols.bind(this);
59
-
60
- this._coveredTiles = {};
54
+ this._coveredTiles = new Set();
61
55
  this._state = new SourceFeatureState();
62
56
  }
63
57
 
64
58
  onAdd(map) {
65
59
  this.map = map;
66
- this._maxTileCacheSize = map ? map._maxTileCacheSize : null;
67
60
  if (this._source?.onAdd) {
68
61
  this._source.onAdd(map);
69
62
  }
@@ -80,14 +73,11 @@ class SourceCache extends Evented {
80
73
  * an additional API call is received.
81
74
  */
82
75
  loaded() {
83
- if (this._sourceErrored) {
84
- return true;
85
- }
86
- if (!this._sourceLoaded) {
87
- return false;
88
- }
89
- for (const t in this._tiles) {
90
- const tile = this._tiles[t];
76
+ if (this.#sourceErrored) return true;
77
+
78
+ if (!this.#sourceLoaded) return false;
79
+
80
+ for (const tile of this._tiles.values()) {
91
81
  if (tile.state !== 'loaded' && tile.state !== 'errored') return false;
92
82
  }
93
83
  return true;
@@ -98,14 +88,14 @@ class SourceCache extends Evented {
98
88
  }
99
89
 
100
90
  pause() {
101
- this._paused = true;
91
+ this.#paused = true;
102
92
  }
103
93
 
104
94
  resume() {
105
- if (!this._paused) return;
106
- const shouldReload = this._shouldReloadOnResume;
107
- this._paused = false;
108
- this._shouldReloadOnResume = false;
95
+ if (!this.#paused) return;
96
+ const shouldReload = this.#shouldReloadOnResume;
97
+ this.#paused = false;
98
+ this.#shouldReloadOnResume = false;
109
99
  if (shouldReload) this.reload();
110
100
  if (this.transform) this.update(this.transform);
111
101
  }
@@ -122,77 +112,69 @@ class SourceCache extends Evented {
122
112
  return this._source.abortTile?.(tile);
123
113
  }
124
114
 
125
- serialize() {
126
- return this._source.serialize();
127
- }
128
-
129
115
  prepare(context) {
130
116
  this._source.prepare?.();
131
117
 
132
- this._state.coalesceChanges(this._tiles, this.map ? this.map.painter : null);
133
- for (const i in this._tiles) {
134
- this._tiles[i].upload(context);
135
- }
118
+ this._state.coalesceChanges(this._tiles.values(), this.map?.painter);
119
+ this._tiles.forEach(tile => tile.upload(context));
136
120
  }
137
121
 
138
122
  /**
139
- * Return all tile ids ordered with z-order, and cast to numbers
123
+ * Return all tile ids ordered with z-order
140
124
  */
141
- getIds() {
142
- const compareKeyZoom = (a_, b_) => {
143
- const a = this._tiles[a_].tileID;
144
- const b = this._tiles[b_].tileID;
145
- const rotatedA = new Point(a.canonical.x, a.canonical.y).rotate(this.transform.angle);
146
- const rotatedB = new Point(b.canonical.x, b.canonical.y).rotate(this.transform.angle);
147
- return a.overscaledZ - b.overscaledZ || rotatedB.y - rotatedA.y || rotatedB.x - rotatedA.x;
148
- };
149
-
150
- return Object.keys(this._tiles).map(Number).sort(compareKeyZoom);
125
+ getIds(filter = () => true) {
126
+ const angle = this.transform?.angle ?? 0;
127
+
128
+ return Array.from(this._tiles)
129
+ .filter(it => filter.call(this, it[1]))
130
+ .sort(compareKeyZoom)
131
+ .map(it => it[0]);
132
+
133
+ function compareKeyZoom([, a_], [, b_]) {
134
+ const a = a_.tileID;
135
+ const b = b_.tileID;
136
+ const zDiff = a.overscaledZ - b.overscaledZ;
137
+ if (zDiff !== 0) {
138
+ return zDiff;
139
+ }
140
+ const rotatedA = new Point(a.canonical.x, a.canonical.y).rotate(angle);
141
+ const rotatedB = new Point(b.canonical.x, b.canonical.y).rotate(angle);
142
+ return rotatedB.y - rotatedA.y || rotatedB.x - rotatedA.x;
143
+ }
151
144
  }
152
145
 
153
146
  getRenderableIds(symbolLayer) {
154
- return symbolLayer
155
- ? this.getIds().filter(this._isIdRenderableForSymbols)
156
- : this.getIds().filter(this._isIdRenderable);
147
+ const filter = symbolLayer ? this.#isTileRenderableForSymbols : this.#isTileRenderable;
148
+ return this.getIds(filter);
157
149
  }
158
150
 
159
151
  hasRenderableParent(tileID) {
160
152
  const parentTile = this.findLoadedParent(tileID, 0);
161
- if (parentTile) {
162
- return this._isIdRenderable(parentTile.tileID.key);
163
- }
164
- return false;
153
+ return this.#isTileRenderable(parentTile);
165
154
  }
166
155
 
167
- _isIdRenderable(id) {
168
- return this._tiles[id]?.hasData() && !this._coveredTiles[id] && !this._tiles[id].holdingForFade();
156
+ #isTileRenderable(tile) {
157
+ return tile?.hasData() && !this._coveredTiles.has(tile.tileID.key) && !tile.holdingForFade();
169
158
  }
170
159
 
171
- _isIdRenderableForSymbols(id) {
172
- return this._tiles[id]?.hasData() && !this._coveredTiles[id];
160
+ #isTileRenderableForSymbols(tile) {
161
+ return tile?.hasData() && !this._coveredTiles.has(tile.tileID.key);
173
162
  }
174
163
 
175
164
  reload() {
176
- if (this._paused) {
177
- this._shouldReloadOnResume = true;
165
+ if (this.#paused) {
166
+ this.#shouldReloadOnResume = true;
178
167
  return;
179
168
  }
180
169
 
181
170
  this._cache.reset();
182
171
 
183
- for (const i in this._tiles) {
184
- if (this._tiles[i].state !== 'errored') this._reloadTile(i, 'reloading');
185
- }
172
+ this._tiles.forEach(tile => {
173
+ if (tile.state !== 'errored') this._reloadTile(tile, 'reloading');
174
+ });
186
175
  }
187
176
 
188
- _reloadTile(id, state) {
189
- const tile = this._tiles[id];
190
-
191
- // this potentially does not address all underlying
192
- // issues https://github.com/mapbox/mapbox-gl-js/issues/4252
193
- // - hard to tell without repro steps
194
- if (!tile) return;
195
-
177
+ _reloadTile(tile, state) {
196
178
  // The difference between "loading" tiles and "reloading" or "expired"
197
179
  // tiles is that "reloading"/"expired" tiles are "renderable".
198
180
  // Therefore, a "loading" tile cannot become a "reloading" tile without
@@ -273,7 +255,7 @@ class SourceCache extends Evented {
273
255
  * Get a specific tile by id
274
256
  */
275
257
  getTileByID(id) {
276
- return this._tiles[id];
258
+ return this._tiles.get(+id);
277
259
  }
278
260
 
279
261
  /**
@@ -288,11 +270,14 @@ class SourceCache extends Evented {
288
270
  * between `zoom` (exclusive) and `maxCoveringZoom` (inclusive)
289
271
  */
290
272
  _retainLoadedChildren(idealTiles, zoom, maxCoveringZoom, retain) {
291
- for (const id in this._tiles) {
292
- let tile = this._tiles[id];
293
-
273
+ for (let [id, tile] of this._tiles) {
294
274
  // only consider renderable tiles up to maxCoveringZoom
295
- if (retain[id] || !tile.hasData() || tile.tileID.overscaledZ <= zoom || tile.tileID.overscaledZ > maxCoveringZoom)
275
+ if (
276
+ retain.has(id) ||
277
+ !tile.hasData() ||
278
+ tile.tileID.overscaledZ <= zoom ||
279
+ tile.tileID.overscaledZ > maxCoveringZoom
280
+ )
296
281
  continue;
297
282
 
298
283
  // loop through parents and retain the topmost loaded one if found
@@ -300,7 +285,7 @@ class SourceCache extends Evented {
300
285
  while (tile && tile.tileID.overscaledZ > zoom + 1) {
301
286
  const parentID = tile.tileID.scaledTo(tile.tileID.overscaledZ - 1);
302
287
 
303
- tile = this._tiles[parentID.key];
288
+ tile = this._tiles.get(parentID.key);
304
289
 
305
290
  if (tile?.hasData()) {
306
291
  topmostLoadedID = parentID;
@@ -311,10 +296,9 @@ class SourceCache extends Evented {
311
296
  let tileID = topmostLoadedID;
312
297
  while (tileID.overscaledZ > zoom) {
313
298
  tileID = tileID.scaledTo(tileID.overscaledZ - 1);
314
-
315
- if (idealTiles[tileID.key]) {
299
+ if (idealTiles.has(tileID.key)) {
316
300
  // found a parent that needed a loaded child; retain that child
317
- retain[topmostLoadedID.key] = topmostLoadedID;
301
+ retain.set(topmostLoadedID.key, topmostLoadedID);
318
302
  break;
319
303
  }
320
304
  }
@@ -328,8 +312,7 @@ class SourceCache extends Evented {
328
312
  for (let z = tileID.overscaledZ - 1; z >= minCoveringZoom; z--) {
329
313
  const parent = tileID.scaledTo(z);
330
314
  if (!parent) return;
331
- const id = String(parent.key);
332
- const tile = this._tiles[id];
315
+ const tile = this._tiles.get(parent.key);
333
316
  if (tile?.hasData()) {
334
317
  return tile;
335
318
  }
@@ -341,8 +324,7 @@ class SourceCache extends Evented {
341
324
  }
342
325
 
343
326
  /**
344
- * Resizes the tile cache based on the current viewport's size
345
- * or the maxTileCacheSize option passed during map creation
327
+ * Resizes the tile cache based on the current viewport's size.
346
328
  *
347
329
  * Larger viewports use more tiles and need larger caches. Larger viewports
348
330
  * are more likely to be found on devices with more memory and on pages where
@@ -355,12 +337,7 @@ class SourceCache extends Evented {
355
337
  const commonZoomRange = 5;
356
338
 
357
339
  const viewDependentMaxSize = Math.floor(approxTilesInView * commonZoomRange);
358
- const maxSize =
359
- typeof this._maxTileCacheSize === 'number'
360
- ? Math.min(this._maxTileCacheSize, viewDependentMaxSize)
361
- : viewDependentMaxSize;
362
-
363
- this._cache.setMaxSize(maxSize);
340
+ this._cache.setMaxSize(viewDependentMaxSize);
364
341
  }
365
342
 
366
343
  handleWrapJump(lng) {
@@ -386,11 +363,10 @@ class SourceCache extends Evented {
386
363
  this._prevLng = lng;
387
364
 
388
365
  if (wrapDelta) {
389
- const tiles = {};
390
- for (const key in this._tiles) {
391
- const tile = this._tiles[key];
366
+ const tiles = new Map();
367
+ for (const tile of this._tiles.values()) {
392
368
  tile.tileID = tile.tileID.unwrapTo(tile.tileID.wrap + wrapDelta);
393
- tiles[tile.tileID.key] = tile;
369
+ tiles.set(tile.tileID.key, tile);
394
370
  }
395
371
  this._tiles = tiles;
396
372
  }
@@ -402,7 +378,7 @@ class SourceCache extends Evented {
402
378
  */
403
379
  update(transform) {
404
380
  this.transform = transform;
405
- if (!this._sourceLoaded || this._paused) {
381
+ if (!this.#sourceLoaded || this.#paused) {
406
382
  return;
407
383
  }
408
384
 
@@ -411,7 +387,7 @@ class SourceCache extends Evented {
411
387
 
412
388
  // Covered is a list of retained tiles who's areas are fully covered by other,
413
389
  // better, retained tiles. They are not drawn separately.
414
- this._coveredTiles = {};
390
+ this._coveredTiles.clear();
415
391
 
416
392
  let idealTileIDs;
417
393
  if (!this.used) {
@@ -454,82 +430,77 @@ class SourceCache extends Evented {
454
430
  const retain = this._updateRetainedTiles(idealTileIDs, zoom);
455
431
 
456
432
  if (isRasterType(this._source.type)) {
457
- const parentsForFading = {};
458
- const fadingTiles = {};
459
- const ids = Object.keys(retain);
460
- for (const id of ids) {
461
- const tileID = retain[id];
462
- assert(tileID.key === +id);
463
-
464
- const tile = this._tiles[id];
433
+ const parentsForFading = new Map();
434
+ const fadingTiles = new Set();
435
+ for (const [id, tileID] of retain) {
436
+ const tile = this._tiles.get(id);
465
437
  if (!tile || (tile.fadeEndTime && tile.fadeEndTime <= browser.now())) continue;
466
438
 
467
439
  // if the tile is loaded but still fading in, find parents to cross-fade with it
468
440
  const parentTile = this.findLoadedParent(tileID, minCoveringZoom);
469
441
  if (parentTile) {
470
442
  this._addTile(parentTile.tileID);
471
- parentsForFading[parentTile.tileID.key] = parentTile.tileID;
443
+ parentsForFading.set(parentTile.tileID.key, parentTile.tileID);
472
444
  }
473
445
 
474
- fadingTiles[id] = tileID;
446
+ fadingTiles.add(id);
475
447
  }
476
448
 
477
449
  // for tiles that are still fading in, also find children to cross-fade with
478
450
  this._retainLoadedChildren(fadingTiles, zoom, maxCoveringZoom, retain);
479
451
 
480
- for (const id in parentsForFading) {
481
- if (!retain[id]) {
452
+ for (const [id, tileID] of parentsForFading) {
453
+ if (!retain.has(id)) {
482
454
  // If a tile is only needed for fading, mark it as covered so that it isn't rendered on it's own.
483
- this._coveredTiles[id] = true;
484
- retain[id] = parentsForFading[id];
455
+ this._coveredTiles.add(id);
456
+ retain.set(id, tileID);
485
457
  }
486
458
  }
487
459
  }
488
460
 
489
- for (const retainedId in retain) {
461
+ for (const [retainedId] of retain) {
490
462
  // Make sure retained tiles always clear any existing fade holds
491
463
  // so that if they're removed again their fade timer starts fresh.
492
- this._tiles[retainedId].clearFadeHold();
464
+ this._tiles.get(retainedId).clearFadeHold();
493
465
  }
494
466
 
495
467
  // Remove the tiles we don't need anymore.
496
- const remove = keysDifference(this._tiles, retain);
497
- for (const tileID of remove) {
498
- const tile = this._tiles[tileID];
468
+ for (const [id, tile] of this._tiles) {
469
+ if (retain.has(id)) continue;
499
470
  if (tile.hasSymbolBuckets && !tile.holdingForFade()) {
500
471
  tile.setHoldDuration(this.map._fadeDuration);
501
472
  } else if (!tile.hasSymbolBuckets || tile.symbolFadeFinished()) {
502
- this._removeTile(tileID);
473
+ this._removeTile(id);
503
474
  }
504
475
  }
505
476
  }
506
477
 
507
478
  releaseSymbolFadeTiles() {
508
- for (const id in this._tiles) {
509
- if (this._tiles[id].holdingForFade()) {
479
+ for (const [id, tile] of this._tiles) {
480
+ if (tile.holdingForFade()) {
510
481
  this._removeTile(id);
511
482
  }
512
483
  }
513
484
  }
514
485
 
515
486
  _updateRetainedTiles(idealTileIDs, zoom) {
516
- const retain = {};
517
- const checked = {};
487
+ const retain = new Map();
488
+ const checked = new Set();
518
489
  const minCoveringZoom = Math.max(zoom - SourceCache.maxOverzooming, this._source.minzoom);
519
490
  const maxCoveringZoom = Math.max(zoom + SourceCache.maxUnderzooming, this._source.minzoom);
520
491
 
521
- const missingTiles = {};
492
+ const missingTiles = new Set();
522
493
  for (const tileID of idealTileIDs) {
523
494
  const tile = this._addTile(tileID);
524
495
 
525
496
  // retain the tile even if it's not loaded because it's an ideal tile.
526
- retain[tileID.key] = tileID;
497
+ retain.set(tileID.key, tileID);
527
498
 
528
499
  if (tile.hasData()) continue;
529
500
 
530
501
  if (zoom < this._source.maxzoom) {
531
502
  // save missing tiles that potentially have loaded children
532
- missingTiles[tileID.key] = tileID;
503
+ missingTiles.add(tileID.key);
533
504
  }
534
505
  }
535
506
 
@@ -537,7 +508,7 @@ class SourceCache extends Evented {
537
508
  this._retainLoadedChildren(missingTiles, zoom, maxCoveringZoom, retain);
538
509
 
539
510
  for (const tileID of idealTileIDs) {
540
- let tile = this._tiles[tileID.key];
511
+ let tile = this._tiles.get(tileID.key);
541
512
 
542
513
  if (tile.hasData()) continue;
543
514
 
@@ -549,14 +520,19 @@ class SourceCache extends Evented {
549
520
  const childCoord = tileID.children(this._source.maxzoom)[0];
550
521
  const childTile = this.getTile(childCoord);
551
522
  if (!!childTile && childTile.hasData()) {
552
- retain[childCoord.key] = childCoord;
523
+ retain.set(childCoord.key, childCoord);
553
524
  continue; // tile is covered by overzoomed child
554
525
  }
555
526
  } else {
556
527
  // check if all 4 immediate children are loaded (i.e. the missing ideal tile is covered)
557
528
  const children = tileID.children(this._source.maxzoom);
558
529
 
559
- if (retain[children[0].key] && retain[children[1].key] && retain[children[2].key] && retain[children[3].key])
530
+ if (
531
+ retain.has(children[0].key) &&
532
+ retain.has(children[1].key) &&
533
+ retain.has(children[2].key) &&
534
+ retain.has(children[3].key)
535
+ )
560
536
  continue; // tile is covered by children
561
537
  }
562
538
 
@@ -571,15 +547,15 @@ class SourceCache extends Evented {
571
547
  const parentId = tileID.scaledTo(overscaledZ);
572
548
 
573
549
  // Break parent tile ascent if this route has been previously checked by another child.
574
- if (checked[parentId.key]) break;
575
- checked[parentId.key] = true;
550
+ if (checked.has(parentId.key)) break;
551
+ checked.add(parentId.key);
576
552
 
577
553
  tile = this.getTile(parentId);
578
554
  if (!tile && parentWasRequested) {
579
555
  tile = this._addTile(parentId);
580
556
  }
581
557
  if (tile) {
582
- retain[parentId.key] = parentId;
558
+ retain.set(parentId.key, parentId);
583
559
  // Save the current values, since they're the parent of the next iteration
584
560
  // of the parent tile ascent loop.
585
561
  parentWasRequested = tile.wasRequested();
@@ -596,14 +572,14 @@ class SourceCache extends Evented {
596
572
  * @private
597
573
  */
598
574
  _addTile(tileID) {
599
- let tile = this._tiles[tileID.key];
575
+ let tile = this._tiles.get(tileID.key);
600
576
  if (tile) return tile;
601
577
 
602
578
  tile = this._cache.getAndRemove(tileID);
603
579
  if (tile) {
604
580
  // set the tileID because the cached tile could have had a different wrap value
605
581
  tile.tileID = tileID;
606
- this._state.initializeTileState(tile, this.map ? this.map.painter : null);
582
+ this._state.initializeTileState(tile, this.map?.painter);
607
583
  }
608
584
 
609
585
  const cached = Boolean(tile);
@@ -619,7 +595,7 @@ class SourceCache extends Evented {
619
595
  if (!tile) return null;
620
596
 
621
597
  tile.uses++;
622
- this._tiles[tileID.key] = tile;
598
+ this._tiles.set(tileID.key, tile);
623
599
  if (!cached) this._source.fire(new Event('dataloading', { tile: tile, coord: tile.tileID, dataType: 'source' }));
624
600
 
625
601
  return tile;
@@ -630,11 +606,11 @@ class SourceCache extends Evented {
630
606
  * @private
631
607
  */
632
608
  _removeTile(id) {
633
- const tile = this._tiles[id];
609
+ const tile = this._tiles.get(id);
634
610
  if (!tile) return;
635
611
 
636
612
  tile.uses--;
637
- delete this._tiles[id];
613
+ this._tiles.delete(id);
638
614
 
639
615
  if (tile.uses > 0) return;
640
616
 
@@ -651,10 +627,10 @@ class SourceCache extends Evented {
651
627
  * Remove all tiles from this pyramid
652
628
  */
653
629
  clearTiles() {
654
- this._shouldReloadOnResume = false;
655
- this._paused = false;
630
+ this.#shouldReloadOnResume = false;
631
+ this.#paused = false;
656
632
 
657
- for (const id in this._tiles) this._removeTile(id);
633
+ for (const id of this._tiles.keys()) this._removeTile(id);
658
634
 
659
635
  this._cache.reset();
660
636
  }
@@ -693,8 +669,8 @@ class SourceCache extends Evented {
693
669
  maxY = Math.max(maxY, p.row);
694
670
  }
695
671
 
696
- for (let i = 0; i < ids.length; i++) {
697
- const tile = this._tiles[ids[i]];
672
+ for (const id of ids) {
673
+ const tile = this._tiles.get(id);
698
674
  if (tile.holdingForFade()) {
699
675
  // Tiles held for fading are covered by tiles that are closer to ideal
700
676
  continue;
@@ -731,7 +707,7 @@ class SourceCache extends Evented {
731
707
  }
732
708
 
733
709
  getVisibleCoordinates(symbolLayer) {
734
- const coords = this.getRenderableIds(symbolLayer).map(id => this._tiles[id].tileID);
710
+ const coords = this.getRenderableIds(symbolLayer).map(id => this._tiles.get(id).tileID);
735
711
  for (const coord of coords) {
736
712
  coord.posMatrix = this.transform.calculatePosMatrix(coord.toUnwrapped());
737
713
  }
@@ -744,8 +720,7 @@ class SourceCache extends Evented {
744
720
  }
745
721
 
746
722
  if (isRasterType(this._source.type)) {
747
- for (const id in this._tiles) {
748
- const tile = this._tiles[id];
723
+ for (const tile of this._tiles.values()) {
749
724
  if (tile.fadeEndTime !== undefined && tile.fadeEndTime >= browser.now()) {
750
725
  return true;
751
726
  }
@@ -764,6 +739,15 @@ class SourceCache extends Evented {
764
739
  this._state.updateState(sourceLayer, feature, state);
765
740
  }
766
741
 
742
+ /**
743
+ * Resets the value of a particular state key for a feature
744
+ * @private
745
+ */
746
+ removeFeatureState(sourceLayer, feature, key) {
747
+ sourceLayer = sourceLayer || '_geojsonTileLayer';
748
+ this._state.removeFeatureState(sourceLayer, feature, key);
749
+ }
750
+
767
751
  /**
768
752
  * Get the entire state object for a feature
769
753
  * @private