ol 9.2.5-dev.1718652616882 → 9.2.5-dev.1719254553242

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 (143) hide show
  1. package/DataTile.d.ts +14 -0
  2. package/DataTile.d.ts.map +1 -1
  3. package/DataTile.js +24 -1
  4. package/Map.d.ts +1 -1
  5. package/Map.d.ts.map +1 -1
  6. package/Map.js +9 -10
  7. package/Tile.d.ts +2 -22
  8. package/Tile.d.ts.map +1 -1
  9. package/Tile.js +2 -77
  10. package/TileQueue.d.ts.map +1 -1
  11. package/TileQueue.js +6 -4
  12. package/VectorRenderTile.d.ts +8 -13
  13. package/VectorRenderTile.d.ts.map +1 -1
  14. package/VectorRenderTile.js +17 -24
  15. package/VectorTile.d.ts +5 -5
  16. package/VectorTile.d.ts.map +1 -1
  17. package/VectorTile.js +3 -3
  18. package/dist/ol.d.ts +10 -2
  19. package/dist/ol.d.ts.map +1 -1
  20. package/dist/ol.js +2 -2
  21. package/dist/ol.js.map +1 -1
  22. package/featureloader.d.ts +4 -4
  23. package/featureloader.d.ts.map +1 -1
  24. package/featureloader.js +2 -2
  25. package/format/EsriJSON.d.ts +1 -1
  26. package/format/Feature.d.ts +8 -8
  27. package/format/Feature.d.ts.map +1 -1
  28. package/format/Feature.js +7 -5
  29. package/format/GeoJSON.d.ts +9 -9
  30. package/format/GeoJSON.d.ts.map +1 -1
  31. package/format/GeoJSON.js +10 -14
  32. package/format/JSONFeature.d.ts +7 -7
  33. package/format/JSONFeature.d.ts.map +1 -1
  34. package/format/JSONFeature.js +9 -11
  35. package/format/MVT.d.ts +12 -12
  36. package/format/MVT.d.ts.map +1 -1
  37. package/format/MVT.js +12 -14
  38. package/format/TextFeature.d.ts +1 -1
  39. package/format/TopoJSON.d.ts +1 -1
  40. package/format/WKB.d.ts +1 -1
  41. package/format/XMLFeature.d.ts +1 -1
  42. package/interaction/DragAndDrop.d.ts +1 -1
  43. package/layer/BaseTile.d.ts +23 -6
  44. package/layer/BaseTile.d.ts.map +1 -1
  45. package/layer/BaseTile.js +22 -3
  46. package/layer/Tile.d.ts +1 -1
  47. package/layer/Tile.d.ts.map +1 -1
  48. package/layer/Tile.js +3 -1
  49. package/layer/VectorTile.d.ts +19 -6
  50. package/layer/VectorTile.d.ts.map +1 -1
  51. package/layer/VectorTile.js +20 -8
  52. package/layer/WebGLTile.d.ts +5 -10
  53. package/layer/WebGLTile.d.ts.map +1 -1
  54. package/layer/WebGLTile.js +3 -12
  55. package/package.json +1 -1
  56. package/renderer/Layer.d.ts +22 -25
  57. package/renderer/Layer.d.ts.map +1 -1
  58. package/renderer/Layer.js +36 -36
  59. package/renderer/canvas/TileLayer.d.ts +100 -38
  60. package/renderer/canvas/TileLayer.d.ts.map +1 -1
  61. package/renderer/canvas/TileLayer.js +542 -329
  62. package/renderer/canvas/VectorTileLayer.d.ts +11 -11
  63. package/renderer/canvas/VectorTileLayer.d.ts.map +1 -1
  64. package/renderer/canvas/VectorTileLayer.js +33 -46
  65. package/renderer/webgl/TileLayerBase.d.ts +0 -6
  66. package/renderer/webgl/TileLayerBase.d.ts.map +1 -1
  67. package/renderer/webgl/TileLayerBase.js +61 -78
  68. package/reproj/DataTile.d.ts.map +1 -1
  69. package/reproj/DataTile.js +103 -100
  70. package/source/BingMaps.d.ts +2 -2
  71. package/source/BingMaps.d.ts.map +1 -1
  72. package/source/BingMaps.js +1 -2
  73. package/source/CartoDB.d.ts +2 -2
  74. package/source/CartoDB.js +1 -1
  75. package/source/DataTile.d.ts +49 -20
  76. package/source/DataTile.d.ts.map +1 -1
  77. package/source/DataTile.js +56 -9
  78. package/source/GeoTIFF.d.ts +2 -6
  79. package/source/GeoTIFF.d.ts.map +1 -1
  80. package/source/GeoTIFF.js +3 -3
  81. package/source/Google.d.ts.map +1 -1
  82. package/source/Google.js +0 -2
  83. package/source/IIIF.d.ts +1 -1
  84. package/source/IIIF.js +1 -1
  85. package/source/ImageTile.d.ts +102 -0
  86. package/source/ImageTile.d.ts.map +1 -0
  87. package/source/ImageTile.js +208 -0
  88. package/source/OGCMapTile.d.ts +2 -2
  89. package/source/OGCMapTile.js +1 -1
  90. package/source/OGCVectorTile.d.ts +4 -4
  91. package/source/OGCVectorTile.js +2 -2
  92. package/source/OSM.d.ts +2 -7
  93. package/source/OSM.d.ts.map +1 -1
  94. package/source/OSM.js +1 -3
  95. package/source/StadiaMaps.d.ts +2 -2
  96. package/source/StadiaMaps.d.ts.map +1 -1
  97. package/source/StadiaMaps.js +2 -18
  98. package/source/Tile.d.ts +7 -36
  99. package/source/Tile.d.ts.map +1 -1
  100. package/source/Tile.js +5 -69
  101. package/source/TileArcGISRest.d.ts +2 -2
  102. package/source/TileArcGISRest.js +1 -1
  103. package/source/TileDebug.d.ts.map +1 -1
  104. package/source/TileDebug.js +0 -1
  105. package/source/TileImage.d.ts +7 -11
  106. package/source/TileImage.d.ts.map +1 -1
  107. package/source/TileImage.js +11 -43
  108. package/source/TileJSON.d.ts +2 -2
  109. package/source/TileJSON.js +1 -1
  110. package/source/TileWMS.d.ts +2 -2
  111. package/source/TileWMS.d.ts.map +1 -1
  112. package/source/TileWMS.js +1 -4
  113. package/source/UTFGrid.d.ts +1 -1
  114. package/source/UrlTile.d.ts +14 -12
  115. package/source/UrlTile.d.ts.map +1 -1
  116. package/source/UrlTile.js +12 -7
  117. package/source/Vector.d.ts +5 -5
  118. package/source/Vector.d.ts.map +1 -1
  119. package/source/Vector.js +3 -3
  120. package/source/VectorTile.d.ts +4 -17
  121. package/source/VectorTile.d.ts.map +1 -1
  122. package/source/VectorTile.js +10 -85
  123. package/source/WMTS.d.ts +2 -2
  124. package/source/WMTS.js +3 -3
  125. package/source/XYZ.d.ts +14 -17
  126. package/source/XYZ.d.ts.map +1 -1
  127. package/source/XYZ.js +7 -8
  128. package/source/Zoomify.d.ts +2 -2
  129. package/source/Zoomify.d.ts.map +1 -1
  130. package/source/Zoomify.js +3 -2
  131. package/source.d.ts +1 -0
  132. package/source.d.ts.map +1 -1
  133. package/source.js +1 -0
  134. package/tilecoord.d.ts +7 -0
  135. package/tilecoord.d.ts.map +1 -1
  136. package/tilecoord.js +11 -1
  137. package/tileurlfunction.d.ts +3 -7
  138. package/tileurlfunction.d.ts.map +1 -1
  139. package/tileurlfunction.js +14 -47
  140. package/uri.d.ts +23 -3
  141. package/uri.d.ts.map +1 -1
  142. package/uri.js +75 -0
  143. package/util.js +1 -1
@@ -2,7 +2,10 @@
2
2
  * @module ol/renderer/canvas/TileLayer
3
3
  */
4
4
  import CanvasLayerRenderer from './Layer.js';
5
+ import DataTile, {asImageLike} from '../../DataTile.js';
5
6
  import ImageTile from '../../ImageTile.js';
7
+ import LRUCache from '../../structs/LRUCache.js';
8
+ import ReprojDataTile from '../../reproj/DataTile.js';
6
9
  import ReprojTile from '../../reproj/Tile.js';
7
10
  import TileRange from '../../TileRange.js';
8
11
  import TileState from '../../TileState.js';
@@ -16,14 +19,96 @@ import {
16
19
  createEmpty,
17
20
  equals,
18
21
  getIntersection,
19
- getRotatedViewport,
20
22
  getTopLeft,
21
23
  intersects,
22
24
  } from '../../extent.js';
25
+ import {createOrUpdate as createTileCoord, getKeyZXY} from '../../tilecoord.js';
23
26
  import {fromUserExtent} from '../../proj.js';
24
27
  import {getUid} from '../../util.js';
25
28
  import {toSize} from '../../size.js';
26
29
 
30
+ /**
31
+ * @param {string} sourceKey The source key.
32
+ * @param {number} z The tile z level.
33
+ * @param {number} x The tile x level.
34
+ * @param {number} y The tile y level.
35
+ * @return {string} The cache key.
36
+ */
37
+ function getCacheKey(sourceKey, z, x, y) {
38
+ return `${sourceKey},${getKeyZXY(z, x, y)}`;
39
+ }
40
+
41
+ /**
42
+ * @typedef {Object<number, Set<import("../../Tile.js").default>>} TileLookup
43
+ */
44
+
45
+ /**
46
+ * Add a tile to the lookup.
47
+ * @param {TileLookup} tilesByZ Lookup of tiles by zoom level.
48
+ * @param {import("../../Tile.js").default} tile A tile.
49
+ * @param {number} z The zoom level.
50
+ * @return {boolean} The tile was added to the lookup.
51
+ */
52
+ function addTileToLookup(tilesByZ, tile, z) {
53
+ if (!(z in tilesByZ)) {
54
+ tilesByZ[z] = new Set([tile]);
55
+ return true;
56
+ }
57
+ const set = tilesByZ[z];
58
+ const existing = set.has(tile);
59
+ if (!existing) {
60
+ set.add(tile);
61
+ }
62
+ return !existing;
63
+ }
64
+
65
+ /**
66
+ * Remove a tile from the lookup.
67
+ * @param {TileLookup} tilesByZ Lookup of tiles by zoom level.
68
+ * @param {import("../../Tile.js").default} tile A tile.
69
+ * @param {number} z The zoom level.
70
+ * @return {boolean} The tile was removed from the lookup.
71
+ */
72
+ function removeTileFromLookup(tilesByZ, tile, z) {
73
+ const set = tilesByZ[z];
74
+ if (set) {
75
+ return set.delete(tile);
76
+ }
77
+ return false;
78
+ }
79
+
80
+ /**
81
+ * @param {import("../../Map.js").FrameState} frameState Frame state.
82
+ * @param {import("../../extent.js").Extent} extent The frame extent.
83
+ * @return {import("../../extent.js").Extent} Frame extent intersected with layer extents.
84
+ */
85
+ function getRenderExtent(frameState, extent) {
86
+ const layerState = frameState.layerStatesArray[frameState.layerIndex];
87
+ if (layerState.extent) {
88
+ extent = getIntersection(
89
+ extent,
90
+ fromUserExtent(layerState.extent, frameState.viewState.projection),
91
+ );
92
+ }
93
+ const source = /** @type {import("../../source/Tile.js").default} */ (
94
+ layerState.layer.getRenderSource()
95
+ );
96
+ if (!source.getWrapX()) {
97
+ const gridExtent = source
98
+ .getTileGridForProjection(frameState.viewState.projection)
99
+ .getExtent();
100
+ if (gridExtent) {
101
+ extent = getIntersection(extent, gridExtent);
102
+ }
103
+ }
104
+ return extent;
105
+ }
106
+
107
+ /**
108
+ * @typedef {Object} Options
109
+ * @property {number} [cacheSize=512] The cache size.
110
+ */
111
+
27
112
  /**
28
113
  * @classdesc
29
114
  * Canvas renderer for tile layers.
@@ -34,16 +119,25 @@ import {toSize} from '../../size.js';
34
119
  class CanvasTileLayerRenderer extends CanvasLayerRenderer {
35
120
  /**
36
121
  * @param {LayerType} tileLayer Tile layer.
122
+ * @param {Options} [options] Options.
37
123
  */
38
- constructor(tileLayer) {
124
+ constructor(tileLayer, options) {
39
125
  super(tileLayer);
40
126
 
127
+ options = options || {};
128
+
41
129
  /**
42
130
  * Rendered extent has changed since the previous `renderFrame()` call
43
131
  * @type {boolean}
44
132
  */
45
133
  this.extentChanged = true;
46
134
 
135
+ /**
136
+ * The last call to `renderFrame` was completed with all tiles loaded
137
+ * @type {boolean}
138
+ */
139
+ this.renderComplete = false;
140
+
47
141
  /**
48
142
  * @private
49
143
  * @type {?import("../../extent.js").Extent}
@@ -76,60 +170,101 @@ class CanvasTileLayerRenderer extends CanvasLayerRenderer {
76
170
 
77
171
  /**
78
172
  * @private
79
- * @type {boolean}
173
+ * @type {string}
80
174
  */
81
- this.newTiles_ = false;
175
+ this.renderedSourceKey_;
176
+
177
+ /**
178
+ * @private
179
+ * @type {number}
180
+ */
181
+ this.renderedSourceRevision_;
82
182
 
83
183
  /**
84
184
  * @protected
85
185
  * @type {import("../../extent.js").Extent}
86
186
  */
87
- this.tmpExtent = createEmpty();
187
+ this.tempExtent = createEmpty();
88
188
 
89
189
  /**
90
190
  * @private
91
191
  * @type {import("../../TileRange.js").default}
92
192
  */
93
- this.tmpTileRange_ = new TileRange(0, 0, 0, 0);
193
+ this.tempTileRange_ = new TileRange(0, 0, 0, 0);
194
+
195
+ /**
196
+ * @type {import("../../tilecoord.js").TileCoord}
197
+ * @private
198
+ */
199
+ this.tempTileCoord_ = createTileCoord(0, 0, 0);
200
+
201
+ const cacheSize = options.cacheSize !== undefined ? options.cacheSize : 512;
202
+
203
+ /**
204
+ * @type {import("../../structs/LRUCache.js").default<import("../../Tile.js").default>}
205
+ * @private
206
+ */
207
+ this.tileCache_ = new LRUCache(cacheSize);
208
+
209
+ this.maxStaleKeys = cacheSize * 0.5;
94
210
  }
95
211
 
96
212
  /**
97
- * @protected
98
- * @param {import("../../Tile.js").default} tile Tile.
99
- * @return {boolean} Tile is drawable.
213
+ * @return {LRUCache} Tile cache.
100
214
  */
101
- isDrawableTile(tile) {
102
- const tileLayer = this.getLayer();
103
- const tileState = tile.getState();
104
- const useInterimTilesOnError = tileLayer.getUseInterimTilesOnError();
105
- return (
106
- tileState == TileState.LOADED ||
107
- tileState == TileState.EMPTY ||
108
- (tileState == TileState.ERROR && !useInterimTilesOnError)
109
- );
215
+ getTileCache() {
216
+ return this.tileCache_;
110
217
  }
111
218
 
112
219
  /**
220
+ * Get a tile from the cache or create one if needed.
221
+ *
113
222
  * @param {number} z Tile coordinate z.
114
223
  * @param {number} x Tile coordinate x.
115
224
  * @param {number} y Tile coordinate y.
116
225
  * @param {import("../../Map.js").FrameState} frameState Frame state.
117
- * @return {!import("../../Tile.js").default} Tile.
226
+ * @return {import("../../Tile.js").default|null} Tile (or null if outside source extent).
227
+ * @protected
118
228
  */
119
- getTile(z, x, y, frameState) {
120
- const pixelRatio = frameState.pixelRatio;
121
- const projection = frameState.viewState.projection;
229
+ getOrCreateTile(z, x, y, frameState) {
230
+ const tileCache = this.tileCache_;
122
231
  const tileLayer = this.getLayer();
123
232
  const tileSource = tileLayer.getSource();
124
- let tile = tileSource.getTile(z, x, y, pixelRatio, projection);
125
- if (tile.getState() == TileState.ERROR) {
126
- if (tileLayer.getUseInterimTilesOnError() && tileLayer.getPreload() > 0) {
127
- // Preloaded tiles for lower resolutions might have finished loading.
128
- this.newTiles_ = true;
233
+ const cacheKey = getCacheKey(tileSource.getKey(), z, x, y);
234
+
235
+ /** @type {import("../../Tile.js").default} */
236
+ let tile;
237
+
238
+ if (tileCache.containsKey(cacheKey)) {
239
+ tile = tileCache.get(cacheKey);
240
+ } else {
241
+ tile = tileSource.getTile(
242
+ z,
243
+ x,
244
+ y,
245
+ frameState.pixelRatio,
246
+ frameState.viewState.projection,
247
+ );
248
+ if (!tile) {
249
+ return null;
129
250
  }
251
+ tileCache.set(cacheKey, tile);
130
252
  }
131
- if (!this.isDrawableTile(tile)) {
132
- tile = tile.getInterimTile();
253
+ return tile;
254
+ }
255
+
256
+ /**
257
+ * @param {number} z Tile coordinate z.
258
+ * @param {number} x Tile coordinate x.
259
+ * @param {number} y Tile coordinate y.
260
+ * @param {import("../../Map.js").FrameState} frameState Frame state.
261
+ * @return {import("../../Tile.js").default|null} Tile (or null if outside source extent).
262
+ * @protected
263
+ */
264
+ getTile(z, x, y, frameState) {
265
+ const tile = this.getOrCreateTile(z, x, y, frameState);
266
+ if (!tile) {
267
+ return null;
133
268
  }
134
269
  return tile;
135
270
  }
@@ -157,8 +292,6 @@ class CanvasTileLayerRenderer extends CanvasLayerRenderer {
157
292
  }
158
293
  }
159
294
 
160
- const pixelRatio = frameState.pixelRatio;
161
- const projection = frameState.viewState.projection;
162
295
  const viewState = frameState.viewState;
163
296
  const source = layer.getRenderSource();
164
297
  const tileGrid = source.getTileGridForProjection(viewState.projection);
@@ -170,21 +303,8 @@ class CanvasTileLayerRenderer extends CanvasLayerRenderer {
170
303
  --z
171
304
  ) {
172
305
  const tileCoord = tileGrid.getTileCoordForCoordAndZ(coordinate, z);
173
- const tile = source.getTile(
174
- z,
175
- tileCoord[1],
176
- tileCoord[2],
177
- pixelRatio,
178
- projection,
179
- );
180
- if (
181
- !(tile instanceof ImageTile || tile instanceof ReprojTile) ||
182
- (tile instanceof ReprojTile && tile.getState() === TileState.EMPTY)
183
- ) {
184
- return null;
185
- }
186
-
187
- if (tile.getState() !== TileState.LOADED) {
306
+ const tile = this.getTile(z, tileCoord[1], tileCoord[2], frameState);
307
+ if (!tile || tile.getState() !== TileState.LOADED) {
188
308
  continue;
189
309
  }
190
310
 
@@ -192,6 +312,21 @@ class CanvasTileLayerRenderer extends CanvasLayerRenderer {
192
312
  const tileSize = toSize(tileGrid.getTileSize(z));
193
313
  const tileResolution = tileGrid.getResolution(z);
194
314
 
315
+ /**
316
+ * @type {import('../../DataTile.js').ImageLike}
317
+ */
318
+ let image;
319
+ if (tile instanceof ImageTile) {
320
+ image = tile.getImage();
321
+ } else if (tile instanceof DataTile) {
322
+ image = asImageLike(tile.getData());
323
+ if (!image) {
324
+ continue;
325
+ }
326
+ } else {
327
+ continue;
328
+ }
329
+
195
330
  const col = Math.floor(
196
331
  tilePixelRatio *
197
332
  ((coordinate[0] - tileOrigin[0]) / tileResolution -
@@ -208,47 +343,208 @@ class CanvasTileLayerRenderer extends CanvasLayerRenderer {
208
343
  tilePixelRatio * source.getGutterForProjection(viewState.projection),
209
344
  );
210
345
 
211
- return this.getImageData(tile.getImage(), col + gutter, row + gutter);
346
+ return this.getImageData(image, col + gutter, row + gutter);
212
347
  }
213
348
 
214
349
  return null;
215
350
  }
216
351
 
217
352
  /**
218
- * @param {Object<number, Object<string, import("../../Tile.js").default>>} tiles Lookup of loaded tiles by zoom level.
219
- * @param {number} zoom Zoom level.
220
- * @param {import("../../Tile.js").default} tile Tile.
221
- * @return {boolean|void} If `false`, the tile will not be considered loaded.
353
+ * Determine whether render should be called.
354
+ * @param {import("../../Map.js").FrameState} frameState Frame state.
355
+ * @return {boolean} Layer is ready to be rendered.
222
356
  */
223
- loadedTileCallback(tiles, zoom, tile) {
224
- if (this.isDrawableTile(tile)) {
225
- return super.loadedTileCallback(tiles, zoom, tile);
357
+ prepareFrame(frameState) {
358
+ const source = this.getLayer().getSource();
359
+ if (!source) {
360
+ return false;
226
361
  }
227
- return false;
362
+ const sourceRevision = this.getLayer().getSource().getRevision();
363
+ if (!this.renderedRevision_) {
364
+ this.renderedRevision_ = sourceRevision;
365
+ } else if (this.renderedRevision_ !== sourceRevision) {
366
+ this.renderedRevision_ = sourceRevision;
367
+ if (this.renderedSourceKey_ === source.getKey()) {
368
+ this.tileCache_.clear();
369
+ }
370
+ }
371
+ return true;
228
372
  }
229
373
 
230
374
  /**
231
- * Determine whether render should be called.
232
375
  * @param {import("../../Map.js").FrameState} frameState Frame state.
233
- * @return {boolean} Layer is ready to be rendered.
376
+ * @param {import("../../extent.js").Extent} extent The extent to be rendered.
377
+ * @param {number} initialZ The zoom level.
378
+ * @param {TileLookup} tilesByZ Lookup of tiles by zoom level.
379
+ * @param {number} preload Number of additional levels to load.
234
380
  */
235
- prepareFrame(frameState) {
236
- return !!this.getLayer().getSource();
381
+ enqueueTiles(frameState, extent, initialZ, tilesByZ, preload) {
382
+ const viewState = frameState.viewState;
383
+ const tileLayer = this.getLayer();
384
+ const tileSource = tileLayer.getRenderSource();
385
+ const tileGrid = tileSource.getTileGridForProjection(viewState.projection);
386
+
387
+ const tileSourceKey = getUid(tileSource);
388
+ if (!(tileSourceKey in frameState.wantedTiles)) {
389
+ frameState.wantedTiles[tileSourceKey] = {};
390
+ }
391
+
392
+ const wantedTiles = frameState.wantedTiles[tileSourceKey];
393
+
394
+ const map = tileLayer.getMapInternal();
395
+ const minZ = Math.max(
396
+ initialZ - preload,
397
+ tileGrid.getMinZoom(),
398
+ tileGrid.getZForResolution(
399
+ Math.min(
400
+ tileLayer.getMaxResolution(),
401
+ map
402
+ ? map
403
+ .getView()
404
+ .getResolutionForZoom(Math.max(tileLayer.getMinZoom(), 0))
405
+ : tileGrid.getResolution(0),
406
+ ),
407
+ tileSource.zDirection,
408
+ ),
409
+ );
410
+ for (let z = initialZ; z >= minZ; --z) {
411
+ const tileRange = tileGrid.getTileRangeForExtentAndZ(
412
+ extent,
413
+ z,
414
+ this.tempTileRange_,
415
+ );
416
+
417
+ const tileResolution = tileGrid.getResolution(z);
418
+
419
+ for (let x = tileRange.minX; x <= tileRange.maxX; ++x) {
420
+ for (let y = tileRange.minY; y <= tileRange.maxY; ++y) {
421
+ const tile = this.getTile(z, x, y, frameState);
422
+ if (!tile) {
423
+ continue;
424
+ }
425
+ const added = addTileToLookup(tilesByZ, tile, z);
426
+ if (!added) {
427
+ continue;
428
+ }
429
+
430
+ const tileQueueKey = tile.getKey();
431
+ wantedTiles[tileQueueKey] = true;
432
+
433
+ if (tile.getState() === TileState.IDLE) {
434
+ if (!frameState.tileQueue.isKeyQueued(tileQueueKey)) {
435
+ const tileCoord = createTileCoord(z, x, y, this.tempTileCoord_);
436
+ frameState.tileQueue.enqueue([
437
+ tile,
438
+ tileSourceKey,
439
+ tileGrid.getTileCoordCenter(tileCoord),
440
+ tileResolution,
441
+ ]);
442
+ }
443
+ }
444
+ }
445
+ }
446
+ }
447
+ }
448
+
449
+ /**
450
+ * Look for tiles covering the provided tile coordinate at an alternate
451
+ * zoom level. Loaded tiles will be added to the provided tile texture lookup.
452
+ * @param {import("../../tilecoord.js").TileCoord} tileCoord The target tile coordinate.
453
+ * @param {TileLookup} tilesByZ Lookup of tiles by zoom level.
454
+ * @return {boolean} The tile coordinate is covered by loaded tiles at the alternate zoom level.
455
+ * @private
456
+ */
457
+ findStaleTile_(tileCoord, tilesByZ) {
458
+ const tileCache = this.tileCache_;
459
+ const z = tileCoord[0];
460
+ const x = tileCoord[1];
461
+ const y = tileCoord[2];
462
+ const staleKeys = this.getStaleKeys();
463
+ for (let i = 0; i < staleKeys.length; ++i) {
464
+ const cacheKey = getCacheKey(staleKeys[i], z, x, y);
465
+ if (tileCache.containsKey(cacheKey)) {
466
+ const tile = tileCache.get(cacheKey);
467
+ if (tile.getState() === TileState.LOADED) {
468
+ tile.endTransition(getUid(this));
469
+ addTileToLookup(tilesByZ, tile, z);
470
+ return true;
471
+ }
472
+ }
473
+ }
474
+ return false;
475
+ }
476
+
477
+ /**
478
+ * Look for tiles covering the provided tile coordinate at an alternate
479
+ * zoom level. Loaded tiles will be added to the provided tile texture lookup.
480
+ * @param {import("../../tilegrid/TileGrid.js").default} tileGrid The tile grid.
481
+ * @param {import("../../tilecoord.js").TileCoord} tileCoord The target tile coordinate.
482
+ * @param {number} altZ The alternate zoom level.
483
+ * @param {TileLookup} tilesByZ Lookup of tiles by zoom level.
484
+ * @return {boolean} The tile coordinate is covered by loaded tiles at the alternate zoom level.
485
+ * @private
486
+ */
487
+ findAltTiles_(tileGrid, tileCoord, altZ, tilesByZ) {
488
+ const tileRange = tileGrid.getTileRangeForTileCoordAndZ(
489
+ tileCoord,
490
+ altZ,
491
+ this.tempTileRange_,
492
+ );
493
+
494
+ if (!tileRange) {
495
+ return false;
496
+ }
497
+
498
+ let covered = true;
499
+ const tileCache = this.tileCache_;
500
+ const source = this.getLayer().getRenderSource();
501
+ const sourceKey = source.getKey();
502
+ for (let x = tileRange.minX; x <= tileRange.maxX; ++x) {
503
+ for (let y = tileRange.minY; y <= tileRange.maxY; ++y) {
504
+ const cacheKey = getCacheKey(sourceKey, altZ, x, y);
505
+ let loaded = false;
506
+ if (tileCache.containsKey(cacheKey)) {
507
+ const tile = tileCache.get(cacheKey);
508
+ if (tile.getState() === TileState.LOADED) {
509
+ addTileToLookup(tilesByZ, tile, altZ);
510
+ loaded = true;
511
+ }
512
+ }
513
+ if (!loaded) {
514
+ covered = false;
515
+ }
516
+ }
517
+ }
518
+ return covered;
237
519
  }
238
520
 
239
521
  /**
240
522
  * Render the layer.
523
+ *
524
+ * The frame rendering logic has three parts:
525
+ *
526
+ * 1. Enqueue tiles
527
+ * 2. Find alt tiles for those that are not yet loaded
528
+ * 3. Render loaded tiles
529
+ *
241
530
  * @param {import("../../Map.js").FrameState} frameState Frame state.
242
531
  * @param {HTMLElement} target Target that may be used to render content to.
243
532
  * @return {HTMLElement} The rendered element.
244
533
  */
245
534
  renderFrame(frameState, target) {
535
+ this.renderComplete = true;
536
+
537
+ /**
538
+ * TODO:
539
+ * * maybe skip transition when not fully opaque
540
+ * * decide if this.renderComplete is useful
541
+ */
542
+
246
543
  const layerState = frameState.layerStatesArray[frameState.layerIndex];
247
544
  const viewState = frameState.viewState;
248
545
  const projection = viewState.projection;
249
546
  const viewResolution = viewState.resolution;
250
547
  const viewCenter = viewState.center;
251
- const rotation = viewState.rotation;
252
548
  const pixelRatio = frameState.pixelRatio;
253
549
 
254
550
  const tileLayer = this.getLayer();
@@ -258,8 +554,15 @@ class CanvasTileLayerRenderer extends CanvasLayerRenderer {
258
554
  const z = tileGrid.getZForResolution(viewResolution, tileSource.zDirection);
259
555
  const tileResolution = tileGrid.getResolution(z);
260
556
 
261
- let extent = frameState.extent;
262
- const resolution = frameState.viewState.resolution;
557
+ const sourceKey = tileSource.getKey();
558
+ if (!this.renderedSourceKey_) {
559
+ this.renderedSourceKey_ = sourceKey;
560
+ } else if (this.renderedSourceKey_ !== sourceKey) {
561
+ this.prependStaleKey(this.renderedSourceKey_);
562
+ this.renderedSourceKey_ = sourceKey;
563
+ }
564
+
565
+ let frameExtent = frameState.extent;
263
566
  const tilePixelRatio = tileSource.getTilePixelRatio(pixelRatio);
264
567
 
265
568
  this.prepareContainer(frameState, target);
@@ -271,8 +574,8 @@ class CanvasTileLayerRenderer extends CanvasLayerRenderer {
271
574
  const layerExtent =
272
575
  layerState.extent && fromUserExtent(layerState.extent, projection);
273
576
  if (layerExtent) {
274
- extent = getIntersection(
275
- extent,
577
+ frameExtent = getIntersection(
578
+ frameExtent,
276
579
  fromUserExtent(layerState.extent, projection),
277
580
  );
278
581
  }
@@ -286,84 +589,107 @@ class CanvasTileLayerRenderer extends CanvasLayerRenderer {
286
589
  viewCenter[1] + dy,
287
590
  ];
288
591
 
289
- const tileRange = tileGrid.getTileRangeForExtentAndZ(extent, z);
592
+ /**
593
+ * @type {TileLookup}
594
+ */
595
+ const tilesByZ = {};
290
596
 
291
597
  /**
292
- * @type {Object<number, Object<string, import("../../Tile.js").default>>}
598
+ * Part 1: Enqueue tiles
293
599
  */
294
- const tilesToDrawByZ = {};
295
- tilesToDrawByZ[z] = {};
296
600
 
297
- const findLoadedTiles = this.createLoadedTileFinder(
298
- tileSource,
299
- projection,
300
- tilesToDrawByZ,
301
- );
601
+ const preload = tileLayer.getPreload();
602
+ if (frameState.nextExtent) {
603
+ const targetZ = tileGrid.getZForResolution(
604
+ viewState.nextResolution,
605
+ tileSource.zDirection,
606
+ );
607
+ const nextExtent = getRenderExtent(frameState, frameState.nextExtent);
608
+ this.enqueueTiles(frameState, nextExtent, targetZ, tilesByZ, preload);
609
+ }
302
610
 
303
- const tmpExtent = this.tmpExtent;
304
- const tmpTileRange = this.tmpTileRange_;
305
- this.newTiles_ = false;
306
- const viewport = rotation
307
- ? getRotatedViewport(
308
- viewState.center,
309
- resolution,
310
- rotation,
311
- frameState.size,
312
- )
313
- : undefined;
314
- for (let x = tileRange.minX; x <= tileRange.maxX; ++x) {
315
- for (let y = tileRange.minY; y <= tileRange.maxY; ++y) {
316
- if (
317
- rotation &&
318
- !tileGrid.tileCoordIntersectsViewport([z, x, y], viewport)
319
- ) {
611
+ const renderExtent = getRenderExtent(frameState, frameExtent);
612
+ this.enqueueTiles(frameState, renderExtent, z, tilesByZ, 0);
613
+ if (preload > 0) {
614
+ setTimeout(() => {
615
+ this.enqueueTiles(
616
+ frameState,
617
+ renderExtent,
618
+ z - 1,
619
+ tilesByZ,
620
+ preload - 1,
621
+ );
622
+ }, 0);
623
+ }
624
+
625
+ /**
626
+ * Part 2: Find alt tiles for those that are not yet loaded
627
+ */
628
+
629
+ const uid = getUid(this);
630
+ const time = frameState.time;
631
+
632
+ // look for cached tiles to use if a target tile is not ready
633
+ for (const tile of tilesByZ[z]) {
634
+ const tileState = tile.getState();
635
+ if (
636
+ (tile instanceof ReprojTile || tile instanceof ReprojDataTile) &&
637
+ tileState === TileState.EMPTY
638
+ ) {
639
+ continue;
640
+ }
641
+ const tileCoord = tile.tileCoord;
642
+
643
+ if (tileState === TileState.LOADED) {
644
+ const alpha = tile.getAlpha(uid, time);
645
+ if (alpha === 1) {
646
+ // no need to look for alt tiles
647
+ tile.endTransition(uid);
320
648
  continue;
321
649
  }
322
- const tile = this.getTile(z, x, y, frameState);
323
- if (this.isDrawableTile(tile)) {
324
- const uid = getUid(this);
325
- if (tile.getState() == TileState.LOADED) {
326
- tilesToDrawByZ[z][tile.tileCoord.toString()] = tile;
327
- let inTransition = tile.inTransition(uid);
328
- if (inTransition && layerState.opacity !== 1) {
329
- // Skipping transition when layer is not fully opaque avoids visual artifacts.
330
- tile.endTransition(uid);
331
- inTransition = false;
332
- }
333
- if (
334
- !this.newTiles_ &&
335
- (inTransition || !this.renderedTiles.includes(tile))
336
- ) {
337
- this.newTiles_ = true;
338
- }
339
- }
340
- if (tile.getAlpha(uid, frameState.time) === 1) {
341
- // don't look for alt tiles if alpha is 1
342
- continue;
343
- }
344
- }
650
+ }
651
+ this.renderComplete = false;
345
652
 
346
- const childTileRange = tileGrid.getTileCoordChildTileRange(
347
- tile.tileCoord,
348
- tmpTileRange,
349
- tmpExtent,
653
+ const hasStaleTile = this.findStaleTile_(tileCoord, tilesByZ);
654
+ if (hasStaleTile) {
655
+ // use the stale tile before the new tile's transition has completed
656
+ removeTileFromLookup(tilesByZ, tile, z);
657
+ frameState.animate = true;
658
+ continue;
659
+ }
660
+
661
+ // first look for child tiles (at z + 1)
662
+ const coveredByChildren = this.findAltTiles_(
663
+ tileGrid,
664
+ tileCoord,
665
+ z + 1,
666
+ tilesByZ,
667
+ );
668
+
669
+ if (coveredByChildren) {
670
+ continue;
671
+ }
672
+
673
+ // next look for parent tiles
674
+ const minZoom = tileGrid.getMinZoom();
675
+ for (let parentZ = z - 1; parentZ >= minZoom; --parentZ) {
676
+ const coveredByParent = this.findAltTiles_(
677
+ tileGrid,
678
+ tileCoord,
679
+ parentZ,
680
+ tilesByZ,
350
681
  );
351
682
 
352
- let covered = false;
353
- if (childTileRange) {
354
- covered = findLoadedTiles(z + 1, childTileRange);
355
- }
356
- if (!covered) {
357
- tileGrid.forEachTileCoordParentTileRange(
358
- tile.tileCoord,
359
- findLoadedTiles,
360
- tmpTileRange,
361
- tmpExtent,
362
- );
683
+ if (coveredByParent) {
684
+ break;
363
685
  }
364
686
  }
365
687
  }
366
688
 
689
+ /**
690
+ * Part 3: Render loaded tiles
691
+ */
692
+
367
693
  const canvasScale =
368
694
  ((tileResolution / viewResolution) * pixelRatio) / tilePixelRatio;
369
695
 
@@ -381,7 +707,7 @@ class CanvasTileLayerRenderer extends CanvasLayerRenderer {
381
707
  -height / 2,
382
708
  );
383
709
 
384
- if (layerExtent) {
710
+ if (layerState.extent) {
385
711
  this.clipUnrotated(context, frameState, layerExtent);
386
712
  }
387
713
 
@@ -393,20 +719,12 @@ class CanvasTileLayerRenderer extends CanvasLayerRenderer {
393
719
 
394
720
  this.renderedTiles.length = 0;
395
721
  /** @type {Array<number>} */
396
- let zs = Object.keys(tilesToDrawByZ).map(Number);
722
+ const zs = Object.keys(tilesByZ).map(Number);
397
723
  zs.sort(ascending);
398
724
 
399
- let clips, clipZs, currentClip;
400
- if (
401
- layerState.opacity === 1 &&
402
- (!this.containerReused ||
403
- tileSource.getOpaque(frameState.viewState.projection))
404
- ) {
405
- zs = zs.reverse();
406
- } else {
407
- clips = [];
408
- clipZs = [];
409
- }
725
+ let currentClip;
726
+ const clips = [];
727
+ const clipZs = [];
410
728
  for (let i = zs.length - 1; i >= 0; --i) {
411
729
  const currentZ = zs[i];
412
730
  const currentTilePixelSize = tileSource.getTilePixelSize(
@@ -431,11 +749,10 @@ class CanvasTileLayerRenderer extends CanvasLayerRenderer {
431
749
  ]);
432
750
  const tileGutter =
433
751
  tilePixelRatio * tileSource.getGutterForProjection(projection);
434
- const tilesToDraw = tilesToDrawByZ[currentZ];
435
- for (const tileCoordKey in tilesToDraw) {
436
- const tile = /** @type {import("../../ImageTile.js").default} */ (
437
- tilesToDraw[tileCoordKey]
438
- );
752
+ for (const tile of tilesByZ[currentZ]) {
753
+ if (tile.getState() !== TileState.LOADED) {
754
+ continue;
755
+ }
439
756
  const tileCoord = tile.tileCoord;
440
757
 
441
758
  // Calculate integer positions and sizes so that tiles align
@@ -447,67 +764,50 @@ class CanvasTileLayerRenderer extends CanvasLayerRenderer {
447
764
  const y = Math.round(origin[1] - yIndex * dy);
448
765
  const w = nextX - x;
449
766
  const h = nextY - y;
450
- const transition = z === currentZ;
767
+ const transition = zs.length === 1;
451
768
 
452
- const inTransition =
453
- transition && tile.getAlpha(getUid(this), frameState.time) !== 1;
454
769
  let contextSaved = false;
455
- if (!inTransition) {
456
- if (clips) {
457
- // Clip mask for regions in this tile that already filled by a higher z tile
458
- currentClip = [x, y, x + w, y, x + w, y + h, x, y + h];
459
- for (let i = 0, ii = clips.length; i < ii; ++i) {
460
- if (z !== currentZ && currentZ < clipZs[i]) {
461
- const clip = clips[i];
462
- if (
463
- intersects(
464
- [x, y, x + w, y + h],
465
- [clip[0], clip[3], clip[4], clip[7]],
466
- )
467
- ) {
468
- if (!contextSaved) {
469
- context.save();
470
- contextSaved = true;
471
- }
472
- context.beginPath();
473
- // counter-clockwise (outer ring) for current tile
474
- context.moveTo(currentClip[0], currentClip[1]);
475
- context.lineTo(currentClip[2], currentClip[3]);
476
- context.lineTo(currentClip[4], currentClip[5]);
477
- context.lineTo(currentClip[6], currentClip[7]);
478
- // clockwise (inner ring) for higher z tile
479
- context.moveTo(clip[6], clip[7]);
480
- context.lineTo(clip[4], clip[5]);
481
- context.lineTo(clip[2], clip[3]);
482
- context.lineTo(clip[0], clip[1]);
483
- context.clip();
484
- }
770
+
771
+ // Clip mask for regions in this tile that already filled by a higher z tile
772
+ currentClip = [x, y, x + w, y, x + w, y + h, x, y + h];
773
+ for (let i = 0, ii = clips.length; i < ii; ++i) {
774
+ if (!transition && currentZ < clipZs[i]) {
775
+ const clip = clips[i];
776
+ if (
777
+ intersects(
778
+ [x, y, x + w, y + h],
779
+ [clip[0], clip[3], clip[4], clip[7]],
780
+ )
781
+ ) {
782
+ if (!contextSaved) {
783
+ context.save();
784
+ contextSaved = true;
485
785
  }
786
+ context.beginPath();
787
+ // counter-clockwise (outer ring) for current tile
788
+ context.moveTo(currentClip[0], currentClip[1]);
789
+ context.lineTo(currentClip[2], currentClip[3]);
790
+ context.lineTo(currentClip[4], currentClip[5]);
791
+ context.lineTo(currentClip[6], currentClip[7]);
792
+ // clockwise (inner ring) for higher z tile
793
+ context.moveTo(clip[6], clip[7]);
794
+ context.lineTo(clip[4], clip[5]);
795
+ context.lineTo(clip[2], clip[3]);
796
+ context.lineTo(clip[0], clip[1]);
797
+ context.clip();
486
798
  }
487
- clips.push(currentClip);
488
- clipZs.push(currentZ);
489
- } else {
490
- context.clearRect(x, y, w, h);
491
799
  }
492
800
  }
493
- this.drawTileImage(
494
- tile,
495
- frameState,
496
- x,
497
- y,
498
- w,
499
- h,
500
- tileGutter,
501
- transition,
502
- );
503
- if (clips && !inTransition) {
504
- if (contextSaved) {
505
- context.restore();
506
- }
507
- this.renderedTiles.unshift(tile);
508
- } else {
509
- this.renderedTiles.push(tile);
801
+ clips.push(currentClip);
802
+ clipZs.push(currentZ);
803
+
804
+ this.drawTile(tile, frameState, x, y, w, h, tileGutter, transition);
805
+ if (contextSaved) {
806
+ context.restore();
510
807
  }
808
+ this.renderedTiles.unshift(tile);
809
+
810
+ // TODO: decide if this is necessary
511
811
  this.updateUsedTiles(frameState.usedTiles, tileSource, tile);
512
812
  }
513
813
  }
@@ -520,18 +820,6 @@ class CanvasTileLayerRenderer extends CanvasLayerRenderer {
520
820
  this.renderedPixelRatio = pixelRatio;
521
821
  this.renderedProjection = projection;
522
822
 
523
- this.manageTilePyramid(
524
- frameState,
525
- tileSource,
526
- tileGrid,
527
- pixelRatio,
528
- projection,
529
- extent,
530
- z,
531
- tileLayer.getPreload(),
532
- );
533
- this.scheduleExpireCache(frameState, tileSource);
534
-
535
823
  this.postRender(this.context, frameState);
536
824
 
537
825
  if (layerState.extent) {
@@ -539,11 +827,39 @@ class CanvasTileLayerRenderer extends CanvasLayerRenderer {
539
827
  }
540
828
  context.imageSmoothingEnabled = true;
541
829
 
830
+ // TODO: let the renderers manage their own cache instead of managing the source cache
831
+ /**
832
+ * Here we unconditionally expire the source cache since the renderer maintains
833
+ * its own cache.
834
+ * @param {import("../../Map.js").default} map Map.
835
+ * @param {import("../../Map.js").FrameState} frameState Frame state.
836
+ */
837
+ const postRenderFunction = (map, frameState) => {
838
+ const tileSourceKey = getUid(tileSource);
839
+ const wantedTiles = frameState.wantedTiles[tileSourceKey];
840
+ const tilesCount = wantedTiles ? Object.keys(wantedTiles).length : 0;
841
+ this.updateCacheSize(tilesCount);
842
+ this.tileCache_.expireCache();
843
+ };
844
+
845
+ frameState.postRenderFunctions.push(postRenderFunction);
846
+
542
847
  return this.container;
543
848
  }
544
849
 
545
850
  /**
546
- * @param {import("../../ImageTile.js").default} tile Tile.
851
+ * Increases the cache size if needed
852
+ * @param {number} tileCount Minimum number of tiles needed.
853
+ */
854
+ updateCacheSize(tileCount) {
855
+ this.tileCache_.highWaterMark = Math.max(
856
+ this.tileCache_.highWaterMark,
857
+ tileCount * 2,
858
+ );
859
+ }
860
+
861
+ /**
862
+ * @param {import("../../Tile.js").default} tile Tile.
547
863
  * @param {import("../../Map.js").FrameState} frameState Frame state.
548
864
  * @param {number} x Left of the tile.
549
865
  * @param {number} y Top of the tile.
@@ -551,9 +867,20 @@ class CanvasTileLayerRenderer extends CanvasLayerRenderer {
551
867
  * @param {number} h Height of the tile.
552
868
  * @param {number} gutter Tile gutter.
553
869
  * @param {boolean} transition Apply an alpha transition.
870
+ * @protected
554
871
  */
555
- drawTileImage(tile, frameState, x, y, w, h, gutter, transition) {
556
- const image = this.getTileImage(tile);
872
+ drawTile(tile, frameState, x, y, w, h, gutter, transition) {
873
+ let image;
874
+ if (tile instanceof DataTile) {
875
+ image = asImageLike(tile.getData());
876
+ if (!image) {
877
+ throw new Error('Rendering array data is not yet supported');
878
+ }
879
+ } else {
880
+ image = this.getTileImage(
881
+ /** @type {import("../../ImageTile.js").default} */ (tile),
882
+ );
883
+ }
557
884
  if (!image) {
558
885
  return;
559
886
  }
@@ -608,36 +935,6 @@ class CanvasTileLayerRenderer extends CanvasLayerRenderer {
608
935
  return tile.getImage();
609
936
  }
610
937
 
611
- /**
612
- * @param {import("../../Map.js").FrameState} frameState Frame state.
613
- * @param {import("../../source/Tile.js").default} tileSource Tile source.
614
- * @protected
615
- */
616
- scheduleExpireCache(frameState, tileSource) {
617
- if (tileSource.canExpireCache()) {
618
- /**
619
- * @param {import("../../source/Tile.js").default} tileSource Tile source.
620
- * @param {import("../../Map.js").default} map Map.
621
- * @param {import("../../Map.js").FrameState} frameState Frame state.
622
- */
623
- const postRenderFunction = function (tileSource, map, frameState) {
624
- const tileSourceKey = getUid(tileSource);
625
- if (tileSourceKey in frameState.usedTiles) {
626
- tileSource.expireCache(
627
- frameState.viewState.projection,
628
- frameState.usedTiles[tileSourceKey],
629
- );
630
- }
631
- }.bind(null, tileSource);
632
-
633
- frameState.postRenderFunctions.push(
634
- /** @type {import("../../Map.js").PostRenderFunction} */ (
635
- postRenderFunction
636
- ),
637
- );
638
- }
639
- }
640
-
641
938
  /**
642
939
  * @param {!Object<string, !Object<string, boolean>>} usedTiles Used tiles.
643
940
  * @param {import("../../source/Tile.js").default} tileSource Tile source.
@@ -652,90 +949,6 @@ class CanvasTileLayerRenderer extends CanvasLayerRenderer {
652
949
  }
653
950
  usedTiles[tileSourceKey][tile.getKey()] = true;
654
951
  }
655
-
656
- /**
657
- * Manage tile pyramid.
658
- * This function performs a number of functions related to the tiles at the
659
- * current zoom and lower zoom levels:
660
- * - registers idle tiles in frameState.wantedTiles so that they are not
661
- * discarded by the tile queue
662
- * - enqueues missing tiles
663
- * @param {import("../../Map.js").FrameState} frameState Frame state.
664
- * @param {import("../../source/Tile.js").default} tileSource Tile source.
665
- * @param {import("../../tilegrid/TileGrid.js").default} tileGrid Tile grid.
666
- * @param {number} pixelRatio Pixel ratio.
667
- * @param {import("../../proj/Projection.js").default} projection Projection.
668
- * @param {import("../../extent.js").Extent} extent Extent.
669
- * @param {number} currentZ Current Z.
670
- * @param {number} preload Load low resolution tiles up to `preload` levels.
671
- * @param {function(import("../../Tile.js").default):void} [tileCallback] Tile callback.
672
- * @protected
673
- */
674
- manageTilePyramid(
675
- frameState,
676
- tileSource,
677
- tileGrid,
678
- pixelRatio,
679
- projection,
680
- extent,
681
- currentZ,
682
- preload,
683
- tileCallback,
684
- ) {
685
- const tileSourceKey = getUid(tileSource);
686
- if (!(tileSourceKey in frameState.wantedTiles)) {
687
- frameState.wantedTiles[tileSourceKey] = {};
688
- }
689
- const wantedTiles = frameState.wantedTiles[tileSourceKey];
690
- const tileQueue = frameState.tileQueue;
691
- const minZoom = tileGrid.getMinZoom();
692
- const rotation = frameState.viewState.rotation;
693
- const viewport = rotation
694
- ? getRotatedViewport(
695
- frameState.viewState.center,
696
- frameState.viewState.resolution,
697
- rotation,
698
- frameState.size,
699
- )
700
- : undefined;
701
- let tileCount = 0;
702
- let tile, tileRange, tileResolution, x, y, z;
703
- for (z = minZoom; z <= currentZ; ++z) {
704
- tileRange = tileGrid.getTileRangeForExtentAndZ(extent, z, tileRange);
705
- tileResolution = tileGrid.getResolution(z);
706
- for (x = tileRange.minX; x <= tileRange.maxX; ++x) {
707
- for (y = tileRange.minY; y <= tileRange.maxY; ++y) {
708
- if (
709
- rotation &&
710
- !tileGrid.tileCoordIntersectsViewport([z, x, y], viewport)
711
- ) {
712
- continue;
713
- }
714
- if (currentZ - z <= preload) {
715
- ++tileCount;
716
- tile = tileSource.getTile(z, x, y, pixelRatio, projection);
717
- if (tile.getState() == TileState.IDLE) {
718
- wantedTiles[tile.getKey()] = true;
719
- if (!tileQueue.isKeyQueued(tile.getKey())) {
720
- tileQueue.enqueue([
721
- tile,
722
- tileSourceKey,
723
- tileGrid.getTileCoordCenter(tile.tileCoord),
724
- tileResolution,
725
- ]);
726
- }
727
- }
728
- if (tileCallback !== undefined) {
729
- tileCallback(tile);
730
- }
731
- } else {
732
- tileSource.useTile(z, x, y, projection);
733
- }
734
- }
735
- }
736
- }
737
- tileSource.updateCacheSize(tileCount, projection);
738
- }
739
952
  }
740
953
 
741
954
  export default CanvasTileLayerRenderer;