@deck.gl/geo-layers 8.7.7 → 8.8.0-alpha.2

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 (48) hide show
  1. package/dist/dist.dev.js +62897 -30614
  2. package/dist/es5/index.js +8 -0
  3. package/dist/es5/index.js.map +1 -1
  4. package/dist/es5/mvt-layer/mvt-layer.js +9 -12
  5. package/dist/es5/mvt-layer/mvt-layer.js.map +1 -1
  6. package/dist/es5/tile-3d-layer/tile-3d-layer.js +13 -10
  7. package/dist/es5/tile-3d-layer/tile-3d-layer.js.map +1 -1
  8. package/dist/es5/tile-layer/tile-2d-header.js +36 -17
  9. package/dist/es5/tile-layer/tile-2d-header.js.map +1 -1
  10. package/dist/es5/tile-layer/tile-2d-traversal.js +13 -3
  11. package/dist/es5/tile-layer/tile-2d-traversal.js.map +1 -1
  12. package/dist/es5/tile-layer/tile-layer.js +18 -12
  13. package/dist/es5/tile-layer/tile-layer.js.map +1 -1
  14. package/dist/es5/tile-layer/tileset-2d.js +122 -96
  15. package/dist/es5/tile-layer/tileset-2d.js.map +1 -1
  16. package/dist/es5/tile-layer/types.js +2 -0
  17. package/dist/es5/tile-layer/types.js.map +1 -0
  18. package/dist/es5/tile-layer/utils.js +27 -9
  19. package/dist/es5/tile-layer/utils.js.map +1 -1
  20. package/dist/esm/index.js +1 -0
  21. package/dist/esm/index.js.map +1 -1
  22. package/dist/esm/mvt-layer/mvt-layer.js +9 -13
  23. package/dist/esm/mvt-layer/mvt-layer.js.map +1 -1
  24. package/dist/esm/tile-3d-layer/tile-3d-layer.js +8 -7
  25. package/dist/esm/tile-3d-layer/tile-3d-layer.js.map +1 -1
  26. package/dist/esm/tile-layer/tile-2d-header.js +54 -18
  27. package/dist/esm/tile-layer/tile-2d-header.js.map +1 -1
  28. package/dist/esm/tile-layer/tile-2d-traversal.js +17 -3
  29. package/dist/esm/tile-layer/tile-2d-traversal.js.map +1 -1
  30. package/dist/esm/tile-layer/tile-layer.js +20 -13
  31. package/dist/esm/tile-layer/tile-layer.js.map +1 -1
  32. package/dist/esm/tile-layer/tileset-2d.js +88 -54
  33. package/dist/esm/tile-layer/tileset-2d.js.map +1 -1
  34. package/dist/esm/tile-layer/types.js +2 -0
  35. package/dist/esm/tile-layer/types.js.map +1 -0
  36. package/dist/esm/tile-layer/utils.js +26 -11
  37. package/dist/esm/tile-layer/utils.js.map +1 -1
  38. package/dist.min.js +2 -2
  39. package/package.json +11 -11
  40. package/src/index.ts +5 -0
  41. package/src/mvt-layer/mvt-layer.js +6 -6
  42. package/src/tile-3d-layer/tile-3d-layer.js +6 -5
  43. package/src/tile-layer/{tile-2d-header.js → tile-2d-header.ts} +50 -16
  44. package/src/tile-layer/{tile-2d-traversal.js → tile-2d-traversal.ts} +49 -12
  45. package/src/tile-layer/{tile-layer.js → tile-layer.ts} +147 -28
  46. package/src/tile-layer/{tileset-2d.js → tileset-2d.ts} +143 -66
  47. package/src/tile-layer/types.ts +17 -0
  48. package/src/tile-layer/{utils.js → utils.ts} +62 -25
@@ -2,6 +2,9 @@ import Tile2DHeader from './tile-2d-header';
2
2
  import {getTileIndices, tileToBoundingBox} from './utils';
3
3
  import {RequestScheduler} from '@loaders.gl/loader-utils';
4
4
  import {Matrix4} from '@math.gl/core';
5
+ import {assert, Viewport} from '@deck.gl/core';
6
+ import {Bounds, TileIndex, ZRange} from './types';
7
+ import {TileLayerProps} from './tile-layer';
5
8
 
6
9
  // bit masks
7
10
  const TILE_STATE_VISITED = 1;
@@ -29,6 +32,13 @@ export const STRATEGY_NEVER = 'never';
29
32
  export const STRATEGY_REPLACE = 'no-overlap';
30
33
  export const STRATEGY_DEFAULT = 'best-available';
31
34
 
35
+ export type RefinementStrategyFunction = (tiles: Tile2DHeader[]) => void;
36
+ export type RefinementStrategy =
37
+ | typeof STRATEGY_NEVER
38
+ | typeof STRATEGY_REPLACE
39
+ | typeof STRATEGY_DEFAULT
40
+ | RefinementStrategyFunction;
41
+
32
42
  const DEFAULT_CACHE_SCALE = 5;
33
43
 
34
44
  const STRATEGIES = {
@@ -37,17 +47,52 @@ const STRATEGIES = {
37
47
  [STRATEGY_NEVER]: () => {}
38
48
  };
39
49
 
50
+ export type Tileset2DProps = Pick<
51
+ TileLayerProps,
52
+ | 'getTileData'
53
+ | 'tileSize'
54
+ | 'maxCacheSize'
55
+ | 'maxCacheByteSize'
56
+ | 'refinementStrategy'
57
+ | 'extent'
58
+ | 'maxZoom'
59
+ | 'minZoom'
60
+ | 'maxRequests'
61
+ | 'zoomOffset'
62
+ > & {
63
+ onTileLoad: (tile: Tile2DHeader) => void;
64
+ onTileUnload: (tile: Tile2DHeader) => void;
65
+ onTileError: (error: any, tile: Tile2DHeader) => void;
66
+ };
67
+
40
68
  /**
41
- * Manages loading and purging of tiles data. This class caches recently visited tiles
42
- * and only create new tiles if they are present.
69
+ * Manages loading and purging of tile data. This class caches recently visited tiles
70
+ * and only creates new tiles if they are present.
43
71
  */
44
-
45
72
  export default class Tileset2D {
73
+ private opts: Tileset2DProps;
74
+ private _requestScheduler: RequestScheduler;
75
+ private _cache: Map<string, Tile2DHeader>;
76
+ private _dirty: boolean;
77
+ private _tiles: Tile2DHeader[];
78
+
79
+ private _cacheByteSize: number;
80
+ private _viewport: Viewport | null;
81
+ private _selectedTiles: Tile2DHeader[] | null;
82
+ private _frameNumber: number;
83
+ private _modelMatrix: Matrix4;
84
+ private _modelMatrixInverse: Matrix4;
85
+
86
+ private _maxZoom?: number;
87
+ private _minZoom?: number;
88
+
89
+ private onTileLoad: (tile: Tile2DHeader) => void;
90
+
46
91
  /**
47
92
  * Takes in a function that returns tile data, a cache size, and a max and a min zoom level.
48
93
  * Cache size defaults to 5 * number of tiles in the current viewport
49
94
  */
50
- constructor(opts) {
95
+ constructor(opts: Tileset2DProps) {
51
96
  this.opts = opts;
52
97
 
53
98
  this.onTileLoad = tile => {
@@ -85,19 +130,19 @@ export default class Tileset2D {
85
130
  return this._tiles;
86
131
  }
87
132
 
88
- get selectedTiles() {
133
+ get selectedTiles(): Tile2DHeader[] | null {
89
134
  return this._selectedTiles;
90
135
  }
91
136
 
92
- get isLoaded() {
93
- return this._selectedTiles.every(tile => tile.isLoaded);
137
+ get isLoaded(): boolean {
138
+ return this._selectedTiles !== null && this._selectedTiles.every(tile => tile.isLoaded);
94
139
  }
95
140
 
96
- get needsReload() {
97
- return this._selectedTiles.some(tile => tile.needsReload);
141
+ get needsReload(): boolean {
142
+ return this._selectedTiles !== null && this._selectedTiles.some(tile => tile.needsReload);
98
143
  }
99
144
 
100
- setOptions(opts) {
145
+ setOptions(opts: Tileset2DProps): void {
101
146
  Object.assign(this.opts, opts);
102
147
  if (Number.isFinite(opts.maxZoom)) {
103
148
  this._maxZoom = Math.floor(opts.maxZoom);
@@ -108,7 +153,7 @@ export default class Tileset2D {
108
153
  }
109
154
 
110
155
  // Clean up any outstanding tile requests.
111
- finalize() {
156
+ finalize(): void {
112
157
  for (const tile of this._cache.values()) {
113
158
  if (tile.isLoading) {
114
159
  tile.abort();
@@ -119,11 +164,11 @@ export default class Tileset2D {
119
164
  this._selectedTiles = null;
120
165
  }
121
166
 
122
- reloadAll() {
123
- for (const tileId of this._cache.keys()) {
124
- const tile = this._cache.get(tileId);
125
- if (!this._selectedTiles.includes(tile)) {
126
- this._cache.delete(tileId);
167
+ reloadAll(): void {
168
+ for (const id of this._cache.keys()) {
169
+ const tile = this._cache.get(id) as Tile2DHeader;
170
+ if (!this._selectedTiles || !this._selectedTiles.includes(tile)) {
171
+ this._cache.delete(id);
127
172
  } else {
128
173
  tile.setNeedsReload();
129
174
  }
@@ -132,14 +177,14 @@ export default class Tileset2D {
132
177
 
133
178
  /**
134
179
  * Update the cache with the given viewport and model matrix and triggers callback onUpdate.
135
- * @param {*} viewport
136
- * @param {*} onUpdate
137
- * @param {*} modelMatrix
138
180
  */
139
- update(viewport, {zRange, modelMatrix} = {}) {
181
+ update(
182
+ viewport: Viewport,
183
+ {zRange, modelMatrix}: {zRange?: ZRange; modelMatrix?: Matrix4} = {}
184
+ ): number {
140
185
  const modelMatrixAsMatrix4 = new Matrix4(modelMatrix);
141
186
  const isModelMatrixNew = !modelMatrixAsMatrix4.equals(this._modelMatrix);
142
- if (!viewport.equals(this._viewport) || isModelMatrixNew) {
187
+ if (!this._viewport || !viewport.equals(this._viewport) || isModelMatrixNew) {
143
188
  if (isModelMatrixNew) {
144
189
  this._modelMatrixInverse = modelMatrixAsMatrix4.clone().invert();
145
190
  this._modelMatrix = modelMatrixAsMatrix4;
@@ -161,9 +206,7 @@ export default class Tileset2D {
161
206
  }
162
207
  // Check for needed reloads explicitly even if the view/matrix has not changed.
163
208
  } else if (this.needsReload) {
164
- this._selectedTiles = this._selectedTiles.map(tile =>
165
- this._getTile({x: tile.x, y: tile.y, z: tile.z})
166
- );
209
+ this._selectedTiles = this._selectedTiles!.map(tile => this._getTile(tile.index));
167
210
  }
168
211
 
169
212
  // Update tile states
@@ -184,8 +227,24 @@ export default class Tileset2D {
184
227
 
185
228
  /* Public interface for subclassing */
186
229
 
187
- // Returns array of {x, y, z}
188
- getTileIndices({viewport, maxZoom, minZoom, zRange, modelMatrix, modelMatrixInverse}) {
230
+ /** Returns array of tile indices in the current viewport */
231
+ getTileIndices({
232
+ viewport,
233
+ maxZoom,
234
+ minZoom,
235
+ zRange,
236
+ modelMatrix,
237
+ modelMatrixInverse
238
+ }: {
239
+ viewport: Viewport;
240
+ maxZoom?: number;
241
+ minZoom?: number;
242
+ zRange: ZRange | undefined;
243
+ tileSize?: number;
244
+ modelMatrix?: Matrix4;
245
+ modelMatrixInverse?: Matrix4;
246
+ zoomOffset?: number;
247
+ }): TileIndex[] {
189
248
  const {tileSize, extent, zoomOffset} = this.opts;
190
249
  return getTileIndices({
191
250
  viewport,
@@ -193,30 +252,41 @@ export default class Tileset2D {
193
252
  minZoom,
194
253
  zRange,
195
254
  tileSize,
196
- extent,
255
+ extent: extent as Bounds | undefined,
197
256
  modelMatrix,
198
257
  modelMatrixInverse,
199
258
  zoomOffset
200
259
  });
201
260
  }
202
261
 
203
- // Add custom metadata to tiles
204
- getTileMetadata({x, y, z}) {
262
+ /** Returns unique string key for a tile index */
263
+ getTileId(index: TileIndex) {
264
+ return `${index.x}-${index.y}-${index.z}`;
265
+ }
266
+
267
+ /** Returns a zoom level for a tile index */
268
+ getTileZoom(index: TileIndex) {
269
+ return index.z;
270
+ }
271
+
272
+ /** Returns additional metadata to add to tile, bbox by default */
273
+ getTileMetadata(index: TileIndex) {
274
+ assert(this._viewport);
205
275
  const {tileSize} = this.opts;
206
- return {bbox: tileToBoundingBox(this._viewport, x, y, z, tileSize)};
276
+ return {bbox: tileToBoundingBox(this._viewport, index.x, index.y, index.z, tileSize)};
207
277
  }
208
278
 
209
- // Returns {x, y, z} of the parent tile
210
- getParentIndex(tileIndex) {
211
- // Perf: mutate the input object to avoid GC
212
- tileIndex.x = Math.floor(tileIndex.x / 2);
213
- tileIndex.y = Math.floor(tileIndex.y / 2);
214
- tileIndex.z -= 1;
215
- return tileIndex;
279
+ /** Returns index of the parent tile */
280
+ getParentIndex(index: TileIndex) {
281
+ const x = Math.floor(index.x / 2);
282
+ const y = Math.floor(index.y / 2);
283
+ const z = index.z - 1;
284
+ return {x, y, z};
216
285
  }
217
286
 
218
287
  // Returns true if any tile's visibility changed
219
288
  updateTileStates() {
289
+ assert(this._selectedTiles);
220
290
  const refinementStrategy = this.opts.refinementStrategy || STRATEGY_DEFAULT;
221
291
 
222
292
  const visibilities = new Array(this._cache.size);
@@ -251,10 +321,10 @@ export default class Tileset2D {
251
321
 
252
322
  /* Private methods */
253
323
 
254
- _pruneRequests() {
324
+ _pruneRequests(): void {
255
325
  const {maxRequests} = this.opts;
256
326
 
257
- const abortCandidates = [];
327
+ const abortCandidates: Tile2DHeader[] = [];
258
328
  let ongoingRequestCount = 0;
259
329
  for (const tile of this._cache.values()) {
260
330
  // Keep track of all the ongoing requests
@@ -268,7 +338,7 @@ export default class Tileset2D {
268
338
 
269
339
  while (maxRequests > 0 && ongoingRequestCount > maxRequests && abortCandidates.length > 0) {
270
340
  // There are too many ongoing requests, so abort some that are unselected
271
- const tile = abortCandidates.shift();
341
+ const tile = abortCandidates.shift()!;
272
342
  tile.abort();
273
343
  ongoingRequestCount--;
274
344
  }
@@ -281,14 +351,16 @@ export default class Tileset2D {
281
351
  // Reset states
282
352
  for (const tile of _cache.values()) {
283
353
  tile.parent = null;
284
- tile.children.length = 0;
354
+ if (tile.children) {
355
+ tile.children.length = 0;
356
+ }
285
357
  }
286
358
 
287
359
  // Rebuild tree
288
360
  for (const tile of _cache.values()) {
289
- const parent = this._getNearestAncestor(tile.x, tile.y, tile.z);
361
+ const parent = this._getNearestAncestor(tile);
290
362
  tile.parent = parent;
291
- if (parent) {
363
+ if (parent?.children) {
292
364
  parent.children.push(tile);
293
365
  }
294
366
  }
@@ -299,6 +371,7 @@ export default class Tileset2D {
299
371
  */
300
372
  /* eslint-disable complexity */
301
373
  _resizeCache() {
374
+ assert(this.selectedTiles);
302
375
  const {_cache, opts} = this;
303
376
 
304
377
  const maxCacheSize =
@@ -309,11 +382,11 @@ export default class Tileset2D {
309
382
  const overflown = _cache.size > maxCacheSize || this._cacheByteSize > maxCacheByteSize;
310
383
 
311
384
  if (overflown) {
312
- for (const [tileId, tile] of _cache) {
385
+ for (const [id, tile] of _cache) {
313
386
  if (!tile.isVisible) {
314
387
  // delete tile
315
388
  this._cacheByteSize -= opts.maxCacheByteSize ? tile.byteLength : 0;
316
- _cache.delete(tileId);
389
+ _cache.delete(id);
317
390
  this.opts.onTileUnload(tile);
318
391
  }
319
392
  if (_cache.size <= maxCacheSize && this._cacheByteSize <= maxCacheByteSize) {
@@ -324,30 +397,33 @@ export default class Tileset2D {
324
397
  this._dirty = true;
325
398
  }
326
399
  if (this._dirty) {
327
- this._tiles = Array.from(this._cache.values())
328
- // sort by zoom level so that smaller tiles are displayed on top
329
- .sort((t1, t2) => t1.z - t2.z);
400
+ // sort by zoom level so that smaller tiles are displayed on top
401
+ this._tiles = Array.from(this._cache.values()).sort((t1, t2) => t1.zoom - t2.zoom);
330
402
 
331
403
  this._dirty = false;
332
404
  }
333
405
  }
334
406
  /* eslint-enable complexity */
335
407
 
336
- _getTile({x, y, z}, create) {
337
- const tileId = `${x},${y},${z}`;
338
- let tile = this._cache.get(tileId);
408
+ _getTile(index: TileIndex, create: true): Tile2DHeader;
409
+ _getTile(index: TileIndex, create?: false): Tile2DHeader | undefined;
410
+ _getTile(index: TileIndex, create?: boolean): Tile2DHeader | undefined {
411
+ const id = this.getTileId(index);
412
+ let tile = this._cache.get(id);
339
413
  let needsReload = false;
340
414
 
341
415
  if (!tile && create) {
342
- tile = new Tile2DHeader({x, y, z});
343
- Object.assign(tile, this.getTileMetadata(tile));
416
+ tile = new Tile2DHeader(index);
417
+ Object.assign(tile, this.getTileMetadata(tile.index));
418
+ Object.assign(tile, {id, zoom: this.getTileZoom(tile.index)});
344
419
  needsReload = true;
345
- this._cache.set(tileId, tile);
420
+ this._cache.set(id, tile);
346
421
  this._dirty = true;
347
422
  } else if (tile && tile.needsReload) {
348
423
  needsReload = true;
349
424
  }
350
- if (needsReload) {
425
+ if (tile && needsReload) {
426
+ // eslint-disable-next-line @typescript-eslint/no-floating-promises
351
427
  tile.loadData({
352
428
  getData: this.opts.getTileData,
353
429
  requestScheduler: this._requestScheduler,
@@ -359,11 +435,11 @@ export default class Tileset2D {
359
435
  return tile;
360
436
  }
361
437
 
362
- _getNearestAncestor(x, y, z) {
438
+ _getNearestAncestor(tile: Tile2DHeader): Tile2DHeader | null {
363
439
  const {_minZoom = 0} = this;
364
- let index = {x, y, z};
365
440
 
366
- while (index.z > _minZoom) {
441
+ let index = tile.index;
442
+ while (this.getTileZoom(index) > _minZoom) {
367
443
  index = this.getParentIndex(index);
368
444
  const parent = this._getTile(index);
369
445
  if (parent) {
@@ -380,7 +456,7 @@ export default class Tileset2D {
380
456
  // For all the selected && pending tiles:
381
457
  // - pick the closest ancestor as placeholder
382
458
  // - if no ancestor is visible, pick the closest children as placeholder
383
- function updateTileStateDefault(allTiles) {
459
+ function updateTileStateDefault(allTiles: Tile2DHeader[]) {
384
460
  for (const tile of allTiles) {
385
461
  tile.state = 0;
386
462
  }
@@ -390,12 +466,12 @@ function updateTileStateDefault(allTiles) {
390
466
  }
391
467
  }
392
468
  for (const tile of allTiles) {
393
- tile.isVisible = Boolean(tile.state & TILE_STATE_VISIBLE);
469
+ tile.isVisible = Boolean(tile.state! & TILE_STATE_VISIBLE);
394
470
  }
395
471
  }
396
472
 
397
473
  // Until a selected tile and all its selected siblings are loaded, use the closest ancestor as placeholder
398
- function updateTileStateReplace(allTiles) {
474
+ function updateTileStateReplace(allTiles: Tile2DHeader[]) {
399
475
  for (const tile of allTiles) {
400
476
  tile.state = 0;
401
477
  }
@@ -405,11 +481,11 @@ function updateTileStateReplace(allTiles) {
405
481
  }
406
482
  }
407
483
  // Always process parents first
408
- const sortedTiles = Array.from(allTiles).sort((t1, t2) => t1.z - t2.z);
484
+ const sortedTiles = Array.from(allTiles).sort((t1, t2) => t1.zoom - t2.zoom);
409
485
  for (const tile of sortedTiles) {
410
- tile.isVisible = Boolean(tile.state & TILE_STATE_VISIBLE);
486
+ tile.isVisible = Boolean(tile.state! & TILE_STATE_VISIBLE);
411
487
 
412
- if (tile.isVisible || tile.state & TILE_STATE_VISITED) {
488
+ if (tile.children && (tile.isVisible || tile.state! & TILE_STATE_VISITED)) {
413
489
  // If the tile is rendered, or if the tile has been explicitly hidden, hide all of its children
414
490
  for (const child of tile.children) {
415
491
  child.state = TILE_STATE_VISITED;
@@ -421,10 +497,11 @@ function updateTileStateReplace(allTiles) {
421
497
  }
422
498
 
423
499
  // Walk up the tree until we find one ancestor that is loaded. Returns true if successful.
424
- function getPlaceholderInAncestors(tile) {
500
+ function getPlaceholderInAncestors(startTile: Tile2DHeader) {
501
+ let tile: Tile2DHeader | null = startTile;
425
502
  while (tile) {
426
503
  if (tile.isLoaded || tile.content) {
427
- tile.state |= TILE_STATE_VISIBLE;
504
+ tile.state! |= TILE_STATE_VISIBLE;
428
505
  return true;
429
506
  }
430
507
  tile = tile.parent;
@@ -0,0 +1,17 @@
1
+ export type ZRange = [minZ: number, maxZ: number];
2
+
3
+ export type Bounds = [minX: number, minY: number, maxX: number, maxY: number];
4
+
5
+ export type TileBoundingBox =
6
+ | {left: number; top: number; right: number; bottom: number}
7
+ | {west: number; north: number; east: number; south: number};
8
+
9
+ export type TileIndex = {x: number; y: number; z: number};
10
+
11
+ export type TileLoadProps = {
12
+ index: TileIndex;
13
+ id: string;
14
+ bbox: TileBoundingBox;
15
+ url?: string | null;
16
+ signal?: AbortSignal;
17
+ };
@@ -1,7 +1,11 @@
1
+ import {Viewport} from '@deck.gl/core';
2
+ import {Matrix4} from '@math.gl/core';
3
+ import Tile2DHeader from './tile-2d-header';
1
4
  import {getOSMTileIndices} from './tile-2d-traversal';
5
+ import {Bounds, TileBoundingBox, TileIndex, ZRange} from './types';
2
6
 
3
7
  const TILE_SIZE = 512;
4
- const DEFAULT_EXTENT = [-Infinity, -Infinity, Infinity, Infinity];
8
+ const DEFAULT_EXTENT: Bounds = [-Infinity, -Infinity, Infinity, Infinity];
5
9
 
6
10
  export const urlType = {
7
11
  type: 'url',
@@ -30,7 +34,7 @@ export const urlType = {
30
34
  }
31
35
  };
32
36
 
33
- function transformBox(bbox, modelMatrix) {
37
+ function transformBox(bbox: Bounds, modelMatrix: Matrix4): Bounds {
34
38
  const transformedCoords = [
35
39
  // top-left
36
40
  modelMatrix.transformAsPoint([bbox[0], bbox[1]]),
@@ -41,7 +45,7 @@ function transformBox(bbox, modelMatrix) {
41
45
  // bottom-right
42
46
  modelMatrix.transformAsPoint([bbox[2], bbox[3]])
43
47
  ];
44
- const transformedBox = [
48
+ const transformedBox: Bounds = [
45
49
  // Minimum x coord
46
50
  Math.min(...transformedCoords.map(i => i[0])),
47
51
  // Minimum y coord
@@ -54,27 +58,38 @@ function transformBox(bbox, modelMatrix) {
54
58
  return transformedBox;
55
59
  }
56
60
 
57
- export function getURLFromTemplate(template, properties) {
61
+ function stringHash(s: string): number {
62
+ return Math.abs(s.split('').reduce((a, b) => ((a << 5) - a + b.charCodeAt(0)) | 0, 0));
63
+ }
64
+
65
+ export function getURLFromTemplate(template: string | string[], tile: Tile2DHeader): string | null {
58
66
  if (!template || !template.length) {
59
67
  return null;
60
68
  }
69
+ const {index, id} = tile;
70
+
61
71
  if (Array.isArray(template)) {
62
- const index = Math.abs(properties.x + properties.y) % template.length;
63
- template = template[index];
72
+ const i = stringHash(id) % template.length;
73
+ template = template[i];
64
74
  }
65
75
 
66
- const {x, y, z} = properties;
67
- return template
68
- .replace(/\{x\}/g, x)
69
- .replace(/\{y\}/g, y)
70
- .replace(/\{z\}/g, z)
71
- .replace(/\{-y\}/g, Math.pow(2, z) - y - 1);
76
+ let url = template;
77
+ for (const key of Object.keys(index)) {
78
+ const regex = new RegExp(`{${key}}`, 'g');
79
+ url = url.replace(regex, String(index[key]));
80
+ }
81
+
82
+ // Back-compatible support for {-y}
83
+ if (Number.isInteger(index.y) && Number.isInteger(index.z)) {
84
+ url = url.replace(/\{-y\}/g, String(Math.pow(2, index.z) - index.y - 1));
85
+ }
86
+ return url;
72
87
  }
73
88
 
74
89
  /**
75
90
  * gets the bounding box of a viewport
76
91
  */
77
- function getBoundingBox(viewport, zRange, extent) {
92
+ function getBoundingBox(viewport: Viewport, zRange: number[] | null, extent: Bounds): Bounds {
78
93
  let bounds;
79
94
  if (zRange && zRange.length === 2) {
80
95
  const [minZ, maxZ] = zRange;
@@ -107,22 +122,22 @@ function getBoundingBox(viewport, zRange, extent) {
107
122
  ];
108
123
  }
109
124
 
110
- function getIndexingCoords(bbox, scale, modelMatrixInverse) {
125
+ function getIndexingCoords(bbox: Bounds, scale: number, modelMatrixInverse?: Matrix4): Bounds {
111
126
  if (modelMatrixInverse) {
112
127
  const transformedTileIndex = transformBox(bbox, modelMatrixInverse).map(
113
128
  i => (i * scale) / TILE_SIZE
114
129
  );
115
- return transformedTileIndex;
130
+ return transformedTileIndex as Bounds;
116
131
  }
117
- return bbox.map(i => (i * scale) / TILE_SIZE);
132
+ return bbox.map(i => (i * scale) / TILE_SIZE) as Bounds;
118
133
  }
119
134
 
120
- function getScale(z, tileSize) {
135
+ function getScale(z: number, tileSize: number): number {
121
136
  return (Math.pow(2, z) * TILE_SIZE) / tileSize;
122
137
  }
123
138
 
124
139
  // https://wiki.openstreetmap.org/wiki/Slippy_map_tilenames#Lon..2Flat._to_tile_numbers_2
125
- export function osmTile2lngLat(x, y, z) {
140
+ export function osmTile2lngLat(x: number, y: number, z: number): [number, number] {
126
141
  const scale = getScale(z, TILE_SIZE);
127
142
  const lng = (x / scale) * 360 - 180;
128
143
  const n = Math.PI - (2 * Math.PI * y) / scale;
@@ -130,11 +145,17 @@ export function osmTile2lngLat(x, y, z) {
130
145
  return [lng, lat];
131
146
  }
132
147
 
133
- function tile2XY(x, y, z, tileSize) {
148
+ function tile2XY(x: number, y: number, z: number, tileSize: number): [number, number] {
134
149
  const scale = getScale(z, tileSize);
135
150
  return [(x / scale) * TILE_SIZE, (y / scale) * TILE_SIZE];
136
151
  }
137
- export function tileToBoundingBox(viewport, x, y, z, tileSize = TILE_SIZE) {
152
+ export function tileToBoundingBox(
153
+ viewport: Viewport,
154
+ x: number,
155
+ y: number,
156
+ z: number,
157
+ tileSize: number = TILE_SIZE
158
+ ): TileBoundingBox {
138
159
  if (viewport.isGeospatial) {
139
160
  const [west, north] = osmTile2lngLat(x, y, z);
140
161
  const [east, south] = osmTile2lngLat(x + 1, y + 1, z);
@@ -145,11 +166,17 @@ export function tileToBoundingBox(viewport, x, y, z, tileSize = TILE_SIZE) {
145
166
  return {left, top, right, bottom};
146
167
  }
147
168
 
148
- function getIdentityTileIndices(viewport, z, tileSize, extent, modelMatrixInverse) {
169
+ function getIdentityTileIndices(
170
+ viewport: Viewport,
171
+ z: number,
172
+ tileSize: number,
173
+ extent: Bounds,
174
+ modelMatrixInverse?: Matrix4
175
+ ) {
149
176
  const bbox = getBoundingBox(viewport, null, extent);
150
177
  const scale = getScale(z, tileSize);
151
178
  const [minX, minY, maxX, maxY] = getIndexingCoords(bbox, scale, modelMatrixInverse);
152
- const indices = [];
179
+ const indices: TileIndex[] = [];
153
180
 
154
181
  /*
155
182
  | TILE | TILE | TILE |
@@ -179,17 +206,27 @@ export function getTileIndices({
179
206
  modelMatrix,
180
207
  modelMatrixInverse,
181
208
  zoomOffset = 0
209
+ }: {
210
+ viewport: Viewport;
211
+ maxZoom?: number;
212
+ minZoom?: number;
213
+ zRange: ZRange | undefined;
214
+ extent?: Bounds;
215
+ tileSize?: number;
216
+ modelMatrix?: Matrix4;
217
+ modelMatrixInverse?: Matrix4;
218
+ zoomOffset?: number;
182
219
  }) {
183
220
  let z = viewport.isGeospatial
184
221
  ? Math.round(viewport.zoom + Math.log2(TILE_SIZE / tileSize)) + zoomOffset
185
222
  : Math.ceil(viewport.zoom) + zoomOffset;
186
- if (Number.isFinite(minZoom) && z < minZoom) {
223
+ if (typeof minZoom === 'number' && Number.isFinite(minZoom) && z < minZoom) {
187
224
  if (!extent) {
188
225
  return [];
189
226
  }
190
227
  z = minZoom;
191
228
  }
192
- if (Number.isFinite(maxZoom) && z > maxZoom) {
229
+ if (typeof maxZoom === 'number' && Number.isFinite(maxZoom) && z > maxZoom) {
193
230
  z = maxZoom;
194
231
  }
195
232
  let transformedExtent = extent;
@@ -210,6 +247,6 @@ export function getTileIndices({
210
247
  /**
211
248
  * Returns true if s is a valid URL template
212
249
  */
213
- export function isURLTemplate(s) {
250
+ export function isURLTemplate(s: string): boolean {
214
251
  return /(?=.*{z})(?=.*{x})(?=.*({y}|{-y}))/.test(s);
215
252
  }