@deck.gl-community/geo-layers 9.2.8 → 9.3.0-beta.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 (82) hide show
  1. package/dist/global-grid-layer/global-grid-cluster-layer.d.ts.map +1 -1
  2. package/dist/global-grid-layer/global-grid-cluster-layer.js +1 -0
  3. package/dist/global-grid-layer/global-grid-cluster-layer.js.map +1 -1
  4. package/dist/index.cjs +1799 -7
  5. package/dist/index.cjs.map +4 -4
  6. package/dist/index.d.ts +7 -0
  7. package/dist/index.d.ts.map +1 -1
  8. package/dist/index.js +3 -0
  9. package/dist/index.js.map +1 -1
  10. package/dist/shared-tile-2d-layer/deck-tile-traversal.d.ts +5 -0
  11. package/dist/shared-tile-2d-layer/deck-tile-traversal.d.ts.map +1 -0
  12. package/dist/shared-tile-2d-layer/deck-tile-traversal.js +173 -0
  13. package/dist/shared-tile-2d-layer/deck-tile-traversal.js.map +1 -0
  14. package/dist/shared-tile-2d-layer/deck-tileset-adapter.d.ts +26 -0
  15. package/dist/shared-tile-2d-layer/deck-tileset-adapter.d.ts.map +1 -0
  16. package/dist/shared-tile-2d-layer/deck-tileset-adapter.js +174 -0
  17. package/dist/shared-tile-2d-layer/deck-tileset-adapter.js.map +1 -0
  18. package/dist/shared-tile-2d-layer/index.d.ts +4 -0
  19. package/dist/shared-tile-2d-layer/index.d.ts.map +1 -0
  20. package/dist/shared-tile-2d-layer/index.js +6 -0
  21. package/dist/shared-tile-2d-layer/index.js.map +1 -0
  22. package/dist/shared-tile-2d-layer/shared-tile-2d-layer.d.ts +151 -0
  23. package/dist/shared-tile-2d-layer/shared-tile-2d-layer.d.ts.map +1 -0
  24. package/dist/shared-tile-2d-layer/shared-tile-2d-layer.js +422 -0
  25. package/dist/shared-tile-2d-layer/shared-tile-2d-layer.js.map +1 -0
  26. package/dist/shared-tile-2d-layer/shared-tile-2d-view.d.ts +57 -0
  27. package/dist/shared-tile-2d-layer/shared-tile-2d-view.d.ts.map +1 -0
  28. package/dist/shared-tile-2d-layer/shared-tile-2d-view.js +253 -0
  29. package/dist/shared-tile-2d-layer/shared-tile-2d-view.js.map +1 -0
  30. package/dist/shared-tile-2d-layer/url-template.d.ts +9 -0
  31. package/dist/shared-tile-2d-layer/url-template.d.ts.map +1 -0
  32. package/dist/shared-tile-2d-layer/url-template.js +25 -0
  33. package/dist/shared-tile-2d-layer/url-template.js.map +1 -0
  34. package/dist/tile-grid-layer/tile-grid-layer.d.ts +47 -0
  35. package/dist/tile-grid-layer/tile-grid-layer.d.ts.map +1 -0
  36. package/dist/tile-grid-layer/tile-grid-layer.js +94 -0
  37. package/dist/tile-grid-layer/tile-grid-layer.js.map +1 -0
  38. package/dist/tile-source-layer/tile-source-layer.d.ts +2 -2
  39. package/dist/tile-source-layer/tile-source-layer.d.ts.map +1 -1
  40. package/dist/tileset/adapter.d.ts +38 -0
  41. package/dist/tileset/adapter.d.ts.map +1 -0
  42. package/dist/tileset/adapter.js +5 -0
  43. package/dist/tileset/adapter.js.map +1 -0
  44. package/dist/tileset/index.cjs +721 -0
  45. package/dist/tileset/index.cjs.map +7 -0
  46. package/dist/tileset/index.d.ts +8 -0
  47. package/dist/tileset/index.d.ts.map +1 -0
  48. package/dist/tileset/index.js +7 -0
  49. package/dist/tileset/index.js.map +1 -0
  50. package/dist/tileset/tile-2d-header.d.ts +71 -0
  51. package/dist/tileset/tile-2d-header.d.ts.map +1 -0
  52. package/dist/tileset/tile-2d-header.js +168 -0
  53. package/dist/tileset/tile-2d-header.js.map +1 -0
  54. package/dist/tileset/tileset-2d.d.ts +210 -0
  55. package/dist/tileset/tileset-2d.d.ts.map +1 -0
  56. package/dist/tileset/tileset-2d.js +531 -0
  57. package/dist/tileset/tileset-2d.js.map +1 -0
  58. package/dist/tileset/types.d.ts +44 -0
  59. package/dist/tileset/types.d.ts.map +1 -0
  60. package/dist/tileset/types.js +5 -0
  61. package/dist/tileset/types.js.map +1 -0
  62. package/dist/utils/memoize.d.ts +2 -0
  63. package/dist/utils/memoize.d.ts.map +1 -0
  64. package/dist/utils/memoize.js +36 -0
  65. package/dist/utils/memoize.js.map +1 -0
  66. package/package.json +17 -11
  67. package/src/global-grid-layer/global-grid-cluster-layer.ts +1 -1
  68. package/src/index.ts +14 -0
  69. package/src/shared-tile-2d-layer/deck-tile-traversal.ts +247 -0
  70. package/src/shared-tile-2d-layer/deck-tileset-adapter.ts +262 -0
  71. package/src/shared-tile-2d-layer/index.ts +7 -0
  72. package/src/shared-tile-2d-layer/shared-tile-2d-layer.ts +681 -0
  73. package/src/shared-tile-2d-layer/shared-tile-2d-view.ts +339 -0
  74. package/src/shared-tile-2d-layer/url-template.ts +40 -0
  75. package/src/tile-grid-layer/tile-grid-layer.ts +158 -0
  76. package/src/tile-source-layer/tile-source-layer.ts +2 -2
  77. package/src/tileset/adapter.ts +47 -0
  78. package/src/tileset/index.ts +22 -0
  79. package/src/tileset/tile-2d-header.ts +210 -0
  80. package/src/tileset/tileset-2d.ts +705 -0
  81. package/src/tileset/types.ts +38 -0
  82. package/src/utils/memoize.ts +38 -0
@@ -0,0 +1,681 @@
1
+ // deck.gl-community
2
+ // SPDX-License-Identifier: MIT
3
+ // Copyright (c) vis.gl contributors
4
+
5
+ import {
6
+ CompositeLayer,
7
+ type CompositeLayerProps,
8
+ Layer,
9
+ type LayersList,
10
+ type LayerProps,
11
+ type UpdateParameters,
12
+ type PickingInfo,
13
+ type GetPickingInfoParams,
14
+ type DefaultProps,
15
+ type FilterContext,
16
+ type Viewport,
17
+ _flatten as flatten
18
+ } from '@deck.gl/core';
19
+ import {GeoJsonLayer} from '@deck.gl/layers';
20
+ import {Matrix4} from '@math.gl/core';
21
+ import type {TileSource} from '@loaders.gl/loader-utils';
22
+
23
+ import {
24
+ SharedTileset2D,
25
+ SharedTile2DHeader,
26
+ type TileLoadProps,
27
+ type ZRange,
28
+ type RefinementStrategy,
29
+ STRATEGY_DEFAULT
30
+ } from '../tileset/index';
31
+ import {SharedTile2DView} from './shared-tile-2d-view';
32
+ import {sharedTile2DDeckAdapter} from './deck-tileset-adapter';
33
+ import {type URLTemplate, getURLFromTemplate} from './url-template';
34
+
35
+ /** Tests whether a value looks like a loaders.gl {@link TileSource}. */
36
+ function isTileSource(value: unknown): value is TileSource {
37
+ return Boolean(
38
+ value &&
39
+ typeof value === 'object' &&
40
+ 'getTileData' in value &&
41
+ typeof (value as TileSource).getTileData === 'function' &&
42
+ 'getMetadata' in value &&
43
+ typeof (value as TileSource).getMetadata === 'function'
44
+ );
45
+ }
46
+
47
+ /** Tests whether a value is a supported URL-template input for tiled requests. */
48
+ function isURLTemplate(value: unknown): value is URLTemplate {
49
+ return (
50
+ value === null ||
51
+ typeof value === 'string' ||
52
+ (Array.isArray(value) && value.every((url) => typeof url === 'string'))
53
+ );
54
+ }
55
+
56
+ /** Prop-type validator for `data` inputs accepted by {@link SharedTile2DLayer}. */
57
+ const tile2DDataType = {
58
+ type: 'object' as const,
59
+ value: null as URLTemplate | SharedTileset2D | TileSource,
60
+ validate: (value, propType) =>
61
+ (propType.optional && value === null) ||
62
+ value instanceof SharedTileset2D ||
63
+ isTileSource(value) ||
64
+ isURLTemplate(value),
65
+ equal: (value1, value2) => value1 === value2
66
+ };
67
+
68
+ /** Default prop values for {@link SharedTile2DLayer}. */
69
+ const defaultProps: DefaultProps<SharedTile2DLayerProps> = {
70
+ TilesetClass: SharedTileset2D,
71
+ data: tile2DDataType,
72
+ dataComparator: tile2DDataType.equal,
73
+ renderSubLayers: {type: 'function', value: (props: any) => new GeoJsonLayer(props)},
74
+ getTileData: {type: 'function', optional: true, value: null},
75
+ onViewportLoad: {type: 'function', optional: true, value: null},
76
+ onTileLoad: {type: 'function', value: () => {}},
77
+ onTileUnload: {type: 'function', value: () => {}},
78
+ onTileError: {type: 'function', value: () => {}},
79
+ extent: {type: 'array', optional: true, value: null, compare: true},
80
+ tileSize: 512,
81
+ maxZoom: null,
82
+ minZoom: 0,
83
+ maxCacheSize: null,
84
+ maxCacheByteSize: null,
85
+ refinementStrategy: STRATEGY_DEFAULT,
86
+ zRange: null,
87
+ maxRequests: 6,
88
+ debounceTime: 0,
89
+ zoomOffset: 0
90
+ };
91
+
92
+ /** Internal defaults used to detect whether layer props were explicitly overridden. */
93
+ const TILE2D_LAYER_DEFAULT_OPTION_VALUES = {
94
+ maxCacheSize: null,
95
+ maxCacheByteSize: null,
96
+ maxZoom: null,
97
+ minZoom: 0,
98
+ tileSize: 512,
99
+ refinementStrategy: STRATEGY_DEFAULT,
100
+ extent: null,
101
+ maxRequests: 6,
102
+ debounceTime: 0,
103
+ zoomOffset: 0
104
+ } as const;
105
+
106
+ /** Props for {@link SharedTile2DLayer}. */
107
+ export type SharedTile2DLayerProps<DataT = unknown> = CompositeLayerProps & {
108
+ /** URL template, shared tileset, or loaders.gl TileSource backing the layer. */
109
+ data: URLTemplate | SharedTileset2D<DataT, any> | TileSource;
110
+ /** Tileset class used when the layer creates its own internal tileset. */
111
+ TilesetClass?: typeof SharedTileset2D;
112
+ /** Sub-layer factory invoked for each loaded tile. */
113
+ renderSubLayers?: (
114
+ props: SharedTile2DLayerProps<DataT> & {
115
+ id: string;
116
+ data: DataT;
117
+ _offset: number;
118
+ tile: SharedTile2DHeader<DataT>;
119
+ }
120
+ ) => Layer | null | LayersList;
121
+ /** Optional tile loader used with URL-template data. */
122
+ getTileData?: ((props: TileLoadProps) => Promise<DataT> | DataT) | null;
123
+ /** Callback fired when the current viewport's selected tiles are loaded. */
124
+ onViewportLoad?: ((tiles: SharedTile2DHeader<DataT>[]) => void) | null;
125
+ /** Callback fired when any tile loads. */
126
+ onTileLoad?: (tile: SharedTile2DHeader<DataT>) => void;
127
+ /** Callback fired when any tile is evicted. */
128
+ onTileUnload?: (tile: SharedTile2DHeader<DataT>) => void;
129
+ /** Callback fired when any tile fails to load. */
130
+ onTileError?: (err: any, tile?: SharedTile2DHeader<DataT>) => void;
131
+ /** Bounding box limiting tile generation. */
132
+ extent?: number[] | null;
133
+ /** Tile size in pixels. */
134
+ tileSize?: number;
135
+ /** Maximum zoom level to request. */
136
+ maxZoom?: number | null;
137
+ /** Minimum zoom level to request. */
138
+ minZoom?: number | null;
139
+ /** Maximum tile count kept in cache. */
140
+ maxCacheSize?: number | null;
141
+ /** Maximum byte size kept in cache. */
142
+ maxCacheByteSize?: number | null;
143
+ /** Placeholder refinement strategy. */
144
+ refinementStrategy?: RefinementStrategy;
145
+ /** Elevation bounds used during geospatial tile selection. */
146
+ zRange?: ZRange | null;
147
+ /** Maximum concurrent requests. */
148
+ maxRequests?: number;
149
+ /** Debounce interval before issuing queued requests. */
150
+ debounceTime?: number;
151
+ /** Integer zoom offset applied during tile selection. */
152
+ zoomOffset?: number;
153
+ };
154
+
155
+ /** Picking info returned from {@link SharedTile2DLayer}. */
156
+ export type SharedTile2DLayerPickingInfo<
157
+ DataT = any,
158
+ SubLayerPickingInfo = PickingInfo
159
+ > = SubLayerPickingInfo & {
160
+ /** Picked tile when a tile sub-layer is hit. */
161
+ tile?: SharedTile2DHeader<DataT>;
162
+ /** Tile that produced the picked sub-layer. */
163
+ sourceTile: SharedTile2DHeader<DataT>;
164
+ /** Concrete sub-layer instance that handled the pick. */
165
+ sourceTileSubLayer: Layer;
166
+ };
167
+
168
+ /** Internal mutable state owned by {@link SharedTile2DLayer}. */
169
+ type SharedTile2DLayerState<DataT> = {
170
+ /** Shared or owned tileset used by the layer. */
171
+ tileset: SharedTileset2D<DataT, any> | null;
172
+ /** Per-viewport traversal state. */
173
+ tilesetViews: Map<string, SharedTile2DView<DataT>>;
174
+ /** Whether the layer owns and should finalize the tileset. */
175
+ ownsTileset: boolean;
176
+ /** Cached aggregate load state. */
177
+ isLoaded: boolean;
178
+ /** Last frame number observed for each viewport. */
179
+ frameNumbers: Map<string, number>;
180
+ /** Cached sub-layers per tile id. */
181
+ tileLayers: Map<string, Layer[]>;
182
+ /** Subscription disposer for tileset events. */
183
+ unsubscribeTilesetEvents: (() => void) | null;
184
+ };
185
+
186
+ /** Composite layer that can reuse a shared {@link SharedTileset2D} across layers and views. */
187
+ export class SharedTile2DLayer<DataT = any, ExtraPropsT extends {} = {}> extends CompositeLayer<
188
+ ExtraPropsT & Required<SharedTile2DLayerProps<DataT>>
189
+ > {
190
+ /** Layer default props consumed by deck.gl prop management. */
191
+ static defaultProps: DefaultProps = defaultProps;
192
+ /** Stable layer name used in logs and devtools. */
193
+ static layerName = 'SharedTile2DLayer';
194
+
195
+ /** Viewports currently known to this layer during multi-view rendering. */
196
+ private _knownViewports: Map<string, Viewport> = new Map();
197
+
198
+ /** Internal layer state shared across render passes. */
199
+ state = null as unknown as SharedTile2DLayerState<DataT>;
200
+
201
+ /** Initializes layer-owned tileset state. */
202
+ initializeState(): void {
203
+ this._knownViewports.clear();
204
+ if (this.context.viewport) {
205
+ this._knownViewports.set(this.context.viewport.id || 'default', this.context.viewport);
206
+ }
207
+ this.state = {
208
+ tileset: null,
209
+ tilesetViews: new Map(),
210
+ ownsTileset: false,
211
+ isLoaded: false,
212
+ frameNumbers: new Map(),
213
+ tileLayers: new Map(),
214
+ unsubscribeTilesetEvents: null
215
+ };
216
+ }
217
+
218
+ /** Finalizes owned resources and detaches from any shared tileset. */
219
+ finalizeState(): void {
220
+ this.state.unsubscribeTilesetEvents?.();
221
+ for (const tilesetView of this.state.tilesetViews.values()) {
222
+ tilesetView.finalize();
223
+ }
224
+ if (this.state.ownsTileset) {
225
+ this.state.tileset?.finalize();
226
+ }
227
+ }
228
+
229
+ /** Returns whether all visible sub-layers for all tracked views are loaded. */
230
+ get isLoaded(): boolean {
231
+ const {tilesetViews, tileLayers} = this.state;
232
+ if (!tilesetViews.size) {
233
+ return false;
234
+ }
235
+ return Boolean(
236
+ Array.from(tilesetViews.values()).every((tilesetView) =>
237
+ tilesetView.selectedTiles?.every((tile) => {
238
+ const cachedLayers = tileLayers.get(tile.id);
239
+ return (
240
+ tile.isLoaded &&
241
+ (!tile.content || !cachedLayers || cachedLayers.every((layer) => layer.isLoaded))
242
+ );
243
+ })
244
+ )
245
+ );
246
+ }
247
+
248
+ /** Triggers updates whenever props, data, or update triggers change. */
249
+ shouldUpdateState({changeFlags}: UpdateParameters<this>): boolean {
250
+ return changeFlags.somethingChanged;
251
+ }
252
+
253
+ /** Creates, reuses, or reconfigures the backing shared tileset and per-view state. */
254
+ updateState({changeFlags}: UpdateParameters<this>): void {
255
+ if (this.context.viewport) {
256
+ this._knownViewports.set(this._getViewportKey(), this.context.viewport);
257
+ }
258
+ const propsChanged = Boolean(
259
+ changeFlags.propsOrDataChanged || changeFlags.updateTriggersChanged
260
+ );
261
+ const dataChanged =
262
+ changeFlags.dataChanged ||
263
+ (changeFlags.updateTriggersChanged &&
264
+ (changeFlags.updateTriggersChanged.all || changeFlags.updateTriggersChanged.getTileData));
265
+
266
+ let {tileset, ownsTileset} = this.state;
267
+ const nextExternalTileset = this.props.data instanceof SharedTileset2D ? this.props.data : null;
268
+ if (nextExternalTileset && !nextExternalTileset.adapter) {
269
+ nextExternalTileset.setOptions({adapter: sharedTile2DDeckAdapter});
270
+ }
271
+ const nextOwnsTileset = !nextExternalTileset;
272
+ const nextTileset = this._resolveTileset(tileset, ownsTileset, nextExternalTileset);
273
+
274
+ const tilesetChanged = nextTileset !== tileset || nextOwnsTileset !== ownsTileset;
275
+
276
+ if (tilesetChanged) {
277
+ this._releaseTileset(tileset, ownsTileset);
278
+ tileset = nextTileset;
279
+ ownsTileset = nextOwnsTileset;
280
+ this.setState({
281
+ tileset,
282
+ tilesetViews: new Map(),
283
+ ownsTileset,
284
+ tileLayers: new Map(),
285
+ frameNumbers: new Map(),
286
+ unsubscribeTilesetEvents: nextTileset.subscribe({
287
+ onTileLoad: this._onTileLoad.bind(this),
288
+ onTileError: this._onTileError.bind(this),
289
+ onTileUnload: this._onTileUnload.bind(this),
290
+ onUpdate: () => this.setNeedsUpdate(),
291
+ onError: (error) => this.raiseError(error, 'loading TileSource metadata')
292
+ })
293
+ });
294
+ } else {
295
+ this._updateExistingTileset(propsChanged, ownsTileset, Boolean(dataChanged), nextTileset);
296
+ }
297
+
298
+ this._updateTileset();
299
+ }
300
+
301
+ /** Resolves whether to reuse a shared tileset, reuse an owned tileset, or create a new one. */
302
+ private _resolveTileset(
303
+ currentTileset: SharedTileset2D<DataT, any> | null,
304
+ ownsCurrentTileset: boolean,
305
+ nextExternalTileset: SharedTileset2D<DataT, any> | null
306
+ ): SharedTileset2D<DataT, any> {
307
+ if (nextExternalTileset) {
308
+ return nextExternalTileset;
309
+ }
310
+ if (currentTileset && ownsCurrentTileset) {
311
+ return currentTileset;
312
+ }
313
+ return new this.props.TilesetClass(this._getTilesetOptions());
314
+ }
315
+
316
+ /** Tears down subscriptions and per-view state for the outgoing tileset. */
317
+ private _releaseTileset(tileset: SharedTileset2D<DataT, any> | null, ownsTileset: boolean): void {
318
+ this.state.unsubscribeTilesetEvents?.();
319
+ for (const tilesetView of this.state.tilesetViews.values()) {
320
+ tilesetView.finalize();
321
+ }
322
+ if (ownsTileset) {
323
+ tileset?.finalize();
324
+ }
325
+ }
326
+
327
+ /** Applies prop updates to an existing tileset without replacing it. */
328
+ private _updateExistingTileset(
329
+ propsChanged: boolean,
330
+ ownsTileset: boolean,
331
+ dataChanged: boolean,
332
+ tileset: SharedTileset2D<DataT, any>
333
+ ): void {
334
+ if (!propsChanged) {
335
+ return;
336
+ }
337
+ if (ownsTileset) {
338
+ tileset.setOptions(this._getTilesetOptions());
339
+ if (dataChanged) {
340
+ tileset.reloadAll();
341
+ return;
342
+ }
343
+ }
344
+ this.state.tileLayers.clear();
345
+ }
346
+
347
+ /** Resolves the current tileset configuration from layer props. */
348
+ _getTilesetOptions(): Record<string, unknown> {
349
+ const {
350
+ tileSize,
351
+ maxCacheSize,
352
+ maxCacheByteSize,
353
+ refinementStrategy,
354
+ extent,
355
+ maxZoom,
356
+ minZoom,
357
+ maxRequests,
358
+ debounceTime,
359
+ zoomOffset
360
+ } = this.props;
361
+
362
+ const tileSource = isTileSource(this.props.data) ? this.props.data : undefined;
363
+ const options = {
364
+ tileSource,
365
+ adapter: sharedTile2DDeckAdapter,
366
+ getTileData: tileSource ? undefined : this.getTileData.bind(this),
367
+ onTileLoad: () => {},
368
+ onTileError: () => {},
369
+ onTileUnload: () => {}
370
+ } as Record<string, unknown>;
371
+
372
+ this._assignTilesetOptionIfExplicit(
373
+ options,
374
+ 'maxCacheSize',
375
+ maxCacheSize,
376
+ TILE2D_LAYER_DEFAULT_OPTION_VALUES.maxCacheSize
377
+ );
378
+ this._assignTilesetOptionIfExplicit(
379
+ options,
380
+ 'maxCacheByteSize',
381
+ maxCacheByteSize,
382
+ TILE2D_LAYER_DEFAULT_OPTION_VALUES.maxCacheByteSize
383
+ );
384
+ this._assignTilesetOptionIfExplicit(
385
+ options,
386
+ 'maxZoom',
387
+ maxZoom,
388
+ TILE2D_LAYER_DEFAULT_OPTION_VALUES.maxZoom
389
+ );
390
+ this._assignTilesetOptionIfExplicit(
391
+ options,
392
+ 'minZoom',
393
+ minZoom,
394
+ TILE2D_LAYER_DEFAULT_OPTION_VALUES.minZoom
395
+ );
396
+ this._assignTilesetOptionIfExplicit(
397
+ options,
398
+ 'tileSize',
399
+ tileSize,
400
+ TILE2D_LAYER_DEFAULT_OPTION_VALUES.tileSize
401
+ );
402
+ this._assignTilesetOptionIfExplicit(
403
+ options,
404
+ 'refinementStrategy',
405
+ refinementStrategy,
406
+ TILE2D_LAYER_DEFAULT_OPTION_VALUES.refinementStrategy
407
+ );
408
+ this._assignTilesetOptionIfExplicit(
409
+ options,
410
+ 'extent',
411
+ extent,
412
+ TILE2D_LAYER_DEFAULT_OPTION_VALUES.extent
413
+ );
414
+ this._assignTilesetOptionIfExplicit(
415
+ options,
416
+ 'maxRequests',
417
+ maxRequests,
418
+ TILE2D_LAYER_DEFAULT_OPTION_VALUES.maxRequests
419
+ );
420
+ this._assignTilesetOptionIfExplicit(
421
+ options,
422
+ 'debounceTime',
423
+ debounceTime,
424
+ TILE2D_LAYER_DEFAULT_OPTION_VALUES.debounceTime
425
+ );
426
+ this._assignTilesetOptionIfExplicit(
427
+ options,
428
+ 'zoomOffset',
429
+ zoomOffset,
430
+ TILE2D_LAYER_DEFAULT_OPTION_VALUES.zoomOffset
431
+ );
432
+
433
+ return options;
434
+ }
435
+
436
+ /** Updates per-view traversal state for all known viewports. */
437
+ private _updateTileset(): void {
438
+ const {zRange, modelMatrix} = this.props;
439
+ let anyTilesetChanged = false;
440
+
441
+ for (const [viewportKey, viewport] of this._knownViewports) {
442
+ this._prunePlaceholderViewportView(viewportKey);
443
+ const tilesetView = this._getOrCreateTilesetView(viewportKey);
444
+ const frameNumber = tilesetView.update(viewport, {zRange, modelMatrix});
445
+ const previousFrameNumber = this.state.frameNumbers.get(viewportKey);
446
+ const tilesetChanged = previousFrameNumber !== frameNumber;
447
+ anyTilesetChanged ||= tilesetChanged;
448
+
449
+ if (tilesetView.isLoaded && tilesetChanged) {
450
+ this._onViewportLoad(tilesetView);
451
+ }
452
+ if (tilesetChanged) {
453
+ this.state.frameNumbers.set(viewportKey, frameNumber);
454
+ }
455
+ }
456
+
457
+ const nextIsLoaded = this.isLoaded;
458
+ const loadingStateChanged = this.state.isLoaded !== nextIsLoaded;
459
+ if (loadingStateChanged) {
460
+ for (const tilesetView of this.state.tilesetViews.values()) {
461
+ if (tilesetView.isLoaded) {
462
+ this._onViewportLoad(tilesetView);
463
+ }
464
+ }
465
+ }
466
+
467
+ if (anyTilesetChanged) {
468
+ this.setState({frameNumbers: new Map(this.state.frameNumbers)});
469
+ }
470
+ this.state.isLoaded = nextIsLoaded;
471
+ }
472
+
473
+ /** Emits the viewport-load callback for one view. */
474
+ _onViewportLoad(tilesetView: SharedTile2DView<DataT>): void {
475
+ if (tilesetView.selectedTiles) {
476
+ this.props.onViewportLoad?.(tilesetView.selectedTiles);
477
+ }
478
+ }
479
+
480
+ /** Clears cached sub-layers when a tile loads. */
481
+ _onTileLoad(tile: SharedTile2DHeader<DataT>): void {
482
+ this.state.tileLayers.delete(tile.id);
483
+ this.props.onTileLoad(tile);
484
+ this.setNeedsUpdate();
485
+ }
486
+
487
+ /** Clears cached sub-layers when a tile errors. */
488
+ _onTileError(error: any, tile: SharedTile2DHeader<DataT>): void {
489
+ this.state.tileLayers.delete(tile.id);
490
+ this.props.onTileError(error, tile);
491
+ this.setNeedsUpdate();
492
+ }
493
+
494
+ /** Removes cached sub-layers when a tile is evicted. */
495
+ _onTileUnload(tile: SharedTile2DHeader<DataT>): void {
496
+ this.state.tileLayers.delete(tile.id);
497
+ this.props.onTileUnload(tile);
498
+ }
499
+
500
+ /** Calls the URL-template loader path for a tile when the layer owns the tileset. */
501
+ getTileData(tile: TileLoadProps): Promise<DataT> | DataT | null {
502
+ const {data, getTileData, fetch} = this.props;
503
+ const {signal} = tile;
504
+ if (!isURLTemplate(data)) {
505
+ return null;
506
+ }
507
+ tile.url = getURLFromTemplate(data, tile);
508
+ if (getTileData) {
509
+ return getTileData(tile);
510
+ }
511
+ if (fetch && tile.url) {
512
+ return fetch(tile.url, {propName: 'data', layer: this, signal});
513
+ }
514
+ return null;
515
+ }
516
+
517
+ /** Default tile sub-layer renderer, delegating to `renderSubLayers`. */
518
+ renderSubLayers(
519
+ props: SharedTile2DLayer['props'] & {
520
+ id: string;
521
+ data: DataT;
522
+ _offset: number;
523
+ tile: SharedTile2DHeader<DataT>;
524
+ }
525
+ ): Layer | null | LayersList {
526
+ return this.props.renderSubLayers(props);
527
+ }
528
+
529
+ /** Hook for subclasses to provide extra sub-layer props per tile. */
530
+ getSubLayerPropsByTile(_tile: SharedTile2DHeader<DataT>): Partial<LayerProps> | null {
531
+ return null;
532
+ }
533
+
534
+ /** Adds tile references to picking info returned from sub-layers. */
535
+ getPickingInfo(params: GetPickingInfoParams): SharedTile2DLayerPickingInfo<DataT> {
536
+ const {sourceLayer} = params;
537
+ if (!sourceLayer) {
538
+ throw new Error('SharedTile2DLayer picking info requires a source layer.');
539
+ }
540
+ const sourceTile: SharedTile2DHeader<DataT> = (sourceLayer.props as any).tile;
541
+ const info = params.info as SharedTile2DLayerPickingInfo<DataT>;
542
+ if (info.picked) {
543
+ info.tile = sourceTile;
544
+ }
545
+ info.sourceTile = sourceTile;
546
+ info.sourceTileSubLayer = sourceLayer;
547
+ return info;
548
+ }
549
+
550
+ /** Forwards auto-highlight updates to the picked sub-layer. */
551
+ protected _updateAutoHighlight(info: SharedTile2DLayerPickingInfo<DataT>): void {
552
+ info.sourceTileSubLayer.updateAutoHighlight(info);
553
+ }
554
+
555
+ /** Renders cached or newly generated sub-layers for each loaded tile. */
556
+ renderLayers(): Layer | null | LayersList {
557
+ const {tileset, tileLayers} = this.state;
558
+ if (!tileset) {
559
+ return null;
560
+ }
561
+ return tileset.tiles.map((tile) => {
562
+ const subLayerProps = this.getSubLayerPropsByTile(tile);
563
+ let layers = tileLayers.get(tile.id);
564
+ if (!tile.isLoaded && !tile.content) {
565
+ return layers;
566
+ }
567
+ if (!layers) {
568
+ const rendered = this.renderSubLayers({
569
+ ...this.props,
570
+ ...this.getSubLayerProps({
571
+ id: tile.id,
572
+ updateTriggers: this.props.updateTriggers
573
+ }),
574
+ data: tile.content,
575
+ _offset: 0,
576
+ tile
577
+ });
578
+ layers = this._flattenTileLayers(rendered).map((layer) =>
579
+ layer.clone({tile, ...subLayerProps})
580
+ );
581
+ tileLayers.set(tile.id, layers);
582
+ } else if (
583
+ subLayerProps &&
584
+ layers[0] &&
585
+ Object.keys(subLayerProps).some(
586
+ (propName) => layers[0].props[propName] !== subLayerProps[propName]
587
+ )
588
+ ) {
589
+ layers = layers.map((layer) => layer.clone(subLayerProps));
590
+ tileLayers.set(tile.id, layers);
591
+ }
592
+ return layers;
593
+ });
594
+ }
595
+
596
+ /** Filters tile sub-layers based on the active view-specific visibility state. */
597
+ filterSubLayer({layer, cullRect}: FilterContext) {
598
+ const {tile} = (layer as Layer<{tile: SharedTile2DHeader<DataT>}>).props;
599
+ const tilesetView = this._getOrCreateTilesetView(this._getViewportKey());
600
+ return tilesetView.isTileVisible(
601
+ tile,
602
+ cullRect,
603
+ this.props.modelMatrix ? new Matrix4(this.props.modelMatrix) : null
604
+ );
605
+ }
606
+
607
+ /** Returns the active viewport key used to isolate per-view traversal state. */
608
+ private _getViewportKey(): string {
609
+ return this.context.viewport?.id || 'default';
610
+ }
611
+
612
+ /** Copies a tileset option only when the layer prop was explicitly set. */
613
+ private _assignTilesetOptionIfExplicit(
614
+ options: Record<string, unknown>,
615
+ key: string,
616
+ value: unknown,
617
+ defaultValue: unknown
618
+ ): void {
619
+ if (!this._isDefaultOptionValue(value, defaultValue)) {
620
+ options[key] = value;
621
+ }
622
+ }
623
+
624
+ /** Tests whether a layer prop still has its default value. */
625
+ private _isDefaultOptionValue(value: unknown, defaultValue: unknown): boolean {
626
+ if (Array.isArray(value) || Array.isArray(defaultValue)) {
627
+ return (
628
+ Array.isArray(value) &&
629
+ Array.isArray(defaultValue) &&
630
+ value.length === defaultValue.length &&
631
+ value.every((entry, index) => entry === defaultValue[index])
632
+ );
633
+ }
634
+ return value === defaultValue;
635
+ }
636
+
637
+ /** Drops the bootstrap placeholder viewport after a real viewport is known. */
638
+ private _prunePlaceholderViewportView(viewportKey: string): void {
639
+ const placeholderViewportKey = 'DEFAULT-INITIAL-VIEWPORT';
640
+ if (viewportKey !== placeholderViewportKey) {
641
+ const placeholderView = this.state.tilesetViews.get(placeholderViewportKey);
642
+ if (placeholderView) {
643
+ placeholderView.finalize();
644
+ this.state.tilesetViews.delete(placeholderViewportKey);
645
+ this.state.frameNumbers.delete(placeholderViewportKey);
646
+ }
647
+ }
648
+ }
649
+
650
+ /** Returns the per-viewport traversal state, creating it on demand. */
651
+ private _getOrCreateTilesetView(viewportKey: string): SharedTile2DView<DataT> {
652
+ let tilesetView = this.state.tilesetViews.get(viewportKey);
653
+ if (!tilesetView) {
654
+ const tileset = this.state.tileset;
655
+ if (!tileset) {
656
+ throw new Error('SharedTile2DLayer tileset was not initialized.');
657
+ }
658
+ tilesetView = new SharedTile2DView(tileset);
659
+ this.state.tilesetViews.set(viewportKey, tilesetView);
660
+ }
661
+ return tilesetView;
662
+ }
663
+
664
+ /** Registers additional viewports in multi-view rendering scenarios. */
665
+ activateViewport(viewport: Viewport): void {
666
+ const viewportKey = viewport.id || 'default';
667
+ const previousViewport = this._knownViewports.get(viewportKey);
668
+ this._knownViewports.set(viewportKey, viewport);
669
+ if (!previousViewport || !viewport.equals(previousViewport)) {
670
+ this.setNeedsUpdate();
671
+ }
672
+ super.activateViewport(viewport);
673
+ }
674
+
675
+ /** Normalizes nested render output into a flat tile sub-layer array. */
676
+ private _flattenTileLayers(
677
+ rendered: Layer | null | LayersList
678
+ ): Layer<{tile?: SharedTile2DHeader<DataT>}>[] {
679
+ return flatten(rendered as any, Boolean) as Layer<{tile?: SharedTile2DHeader<DataT>}>[];
680
+ }
681
+ }