@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.
- package/dist/global-grid-layer/global-grid-cluster-layer.d.ts.map +1 -1
- package/dist/global-grid-layer/global-grid-cluster-layer.js +1 -0
- package/dist/global-grid-layer/global-grid-cluster-layer.js.map +1 -1
- package/dist/index.cjs +1799 -7
- package/dist/index.cjs.map +4 -4
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -1
- package/dist/shared-tile-2d-layer/deck-tile-traversal.d.ts +5 -0
- package/dist/shared-tile-2d-layer/deck-tile-traversal.d.ts.map +1 -0
- package/dist/shared-tile-2d-layer/deck-tile-traversal.js +173 -0
- package/dist/shared-tile-2d-layer/deck-tile-traversal.js.map +1 -0
- package/dist/shared-tile-2d-layer/deck-tileset-adapter.d.ts +26 -0
- package/dist/shared-tile-2d-layer/deck-tileset-adapter.d.ts.map +1 -0
- package/dist/shared-tile-2d-layer/deck-tileset-adapter.js +174 -0
- package/dist/shared-tile-2d-layer/deck-tileset-adapter.js.map +1 -0
- package/dist/shared-tile-2d-layer/index.d.ts +4 -0
- package/dist/shared-tile-2d-layer/index.d.ts.map +1 -0
- package/dist/shared-tile-2d-layer/index.js +6 -0
- package/dist/shared-tile-2d-layer/index.js.map +1 -0
- package/dist/shared-tile-2d-layer/shared-tile-2d-layer.d.ts +151 -0
- package/dist/shared-tile-2d-layer/shared-tile-2d-layer.d.ts.map +1 -0
- package/dist/shared-tile-2d-layer/shared-tile-2d-layer.js +422 -0
- package/dist/shared-tile-2d-layer/shared-tile-2d-layer.js.map +1 -0
- package/dist/shared-tile-2d-layer/shared-tile-2d-view.d.ts +57 -0
- package/dist/shared-tile-2d-layer/shared-tile-2d-view.d.ts.map +1 -0
- package/dist/shared-tile-2d-layer/shared-tile-2d-view.js +253 -0
- package/dist/shared-tile-2d-layer/shared-tile-2d-view.js.map +1 -0
- package/dist/shared-tile-2d-layer/url-template.d.ts +9 -0
- package/dist/shared-tile-2d-layer/url-template.d.ts.map +1 -0
- package/dist/shared-tile-2d-layer/url-template.js +25 -0
- package/dist/shared-tile-2d-layer/url-template.js.map +1 -0
- package/dist/tile-grid-layer/tile-grid-layer.d.ts +47 -0
- package/dist/tile-grid-layer/tile-grid-layer.d.ts.map +1 -0
- package/dist/tile-grid-layer/tile-grid-layer.js +94 -0
- package/dist/tile-grid-layer/tile-grid-layer.js.map +1 -0
- package/dist/tile-source-layer/tile-source-layer.d.ts +2 -2
- package/dist/tile-source-layer/tile-source-layer.d.ts.map +1 -1
- package/dist/tileset/adapter.d.ts +38 -0
- package/dist/tileset/adapter.d.ts.map +1 -0
- package/dist/tileset/adapter.js +5 -0
- package/dist/tileset/adapter.js.map +1 -0
- package/dist/tileset/index.cjs +721 -0
- package/dist/tileset/index.cjs.map +7 -0
- package/dist/tileset/index.d.ts +8 -0
- package/dist/tileset/index.d.ts.map +1 -0
- package/dist/tileset/index.js +7 -0
- package/dist/tileset/index.js.map +1 -0
- package/dist/tileset/tile-2d-header.d.ts +71 -0
- package/dist/tileset/tile-2d-header.d.ts.map +1 -0
- package/dist/tileset/tile-2d-header.js +168 -0
- package/dist/tileset/tile-2d-header.js.map +1 -0
- package/dist/tileset/tileset-2d.d.ts +210 -0
- package/dist/tileset/tileset-2d.d.ts.map +1 -0
- package/dist/tileset/tileset-2d.js +531 -0
- package/dist/tileset/tileset-2d.js.map +1 -0
- package/dist/tileset/types.d.ts +44 -0
- package/dist/tileset/types.d.ts.map +1 -0
- package/dist/tileset/types.js +5 -0
- package/dist/tileset/types.js.map +1 -0
- package/dist/utils/memoize.d.ts +2 -0
- package/dist/utils/memoize.d.ts.map +1 -0
- package/dist/utils/memoize.js +36 -0
- package/dist/utils/memoize.js.map +1 -0
- package/package.json +17 -11
- package/src/global-grid-layer/global-grid-cluster-layer.ts +1 -1
- package/src/index.ts +14 -0
- package/src/shared-tile-2d-layer/deck-tile-traversal.ts +247 -0
- package/src/shared-tile-2d-layer/deck-tileset-adapter.ts +262 -0
- package/src/shared-tile-2d-layer/index.ts +7 -0
- package/src/shared-tile-2d-layer/shared-tile-2d-layer.ts +681 -0
- package/src/shared-tile-2d-layer/shared-tile-2d-view.ts +339 -0
- package/src/shared-tile-2d-layer/url-template.ts +40 -0
- package/src/tile-grid-layer/tile-grid-layer.ts +158 -0
- package/src/tile-source-layer/tile-source-layer.ts +2 -2
- package/src/tileset/adapter.ts +47 -0
- package/src/tileset/index.ts +22 -0
- package/src/tileset/tile-2d-header.ts +210 -0
- package/src/tileset/tileset-2d.ts +705 -0
- package/src/tileset/types.ts +38 -0
- package/src/utils/memoize.ts +38 -0
|
@@ -0,0 +1,705 @@
|
|
|
1
|
+
// deck.gl-community
|
|
2
|
+
// SPDX-License-Identifier: MIT
|
|
3
|
+
// Copyright (c) vis.gl contributors
|
|
4
|
+
|
|
5
|
+
import {RequestScheduler, type TileSource, type TileSourceMetadata} from '@loaders.gl/loader-utils';
|
|
6
|
+
import type {Matrix4} from '@math.gl/core';
|
|
7
|
+
import {Stats} from '@probe.gl/stats';
|
|
8
|
+
import {SharedTile2DHeader} from './tile-2d-header';
|
|
9
|
+
import type {SharedTileset2DAdapter, SharedTileset2DTileContext} from './adapter';
|
|
10
|
+
import type {TileIndex, TileLoadProps, ZRange} from './types';
|
|
11
|
+
|
|
12
|
+
export const STRATEGY_NEVER = 'never';
|
|
13
|
+
export const STRATEGY_REPLACE = 'no-overlap';
|
|
14
|
+
export const STRATEGY_DEFAULT = 'best-available';
|
|
15
|
+
|
|
16
|
+
/** Function form of a refinement strategy. */
|
|
17
|
+
export type RefinementStrategyFunction = (tiles: SharedTile2DHeader[]) => void;
|
|
18
|
+
/** Strategy controlling how parent and child placeholder tiles are displayed while content loads. */
|
|
19
|
+
export type RefinementStrategy =
|
|
20
|
+
| typeof STRATEGY_NEVER
|
|
21
|
+
| typeof STRATEGY_REPLACE
|
|
22
|
+
| typeof STRATEGY_DEFAULT
|
|
23
|
+
| RefinementStrategyFunction;
|
|
24
|
+
|
|
25
|
+
/** Core configuration shared by all {@link SharedTileset2D} instances. */
|
|
26
|
+
export type Tileset2DProps<DataT = any, ViewStateT = unknown> = {
|
|
27
|
+
/** Callback used to load tile payloads. */
|
|
28
|
+
getTileData: (props: TileLoadProps) => Promise<DataT | null> | DataT | null;
|
|
29
|
+
/** Adapter used to compute tile traversal and tile metadata. */
|
|
30
|
+
adapter?: SharedTileset2DAdapter<ViewStateT> | null;
|
|
31
|
+
/** Bounding box limiting tile generation. */
|
|
32
|
+
extent?: number[] | null;
|
|
33
|
+
/** Tile size in pixels. */
|
|
34
|
+
tileSize?: number;
|
|
35
|
+
/** Maximum zoom level to request. */
|
|
36
|
+
maxZoom?: number | null;
|
|
37
|
+
/** Minimum zoom level to request. */
|
|
38
|
+
minZoom?: number | null;
|
|
39
|
+
/** Maximum number of tiles kept in cache. */
|
|
40
|
+
maxCacheSize?: number | null;
|
|
41
|
+
/** Maximum bytes kept in cache. */
|
|
42
|
+
maxCacheByteSize?: number | null;
|
|
43
|
+
/** Placeholder refinement strategy. */
|
|
44
|
+
refinementStrategy?: RefinementStrategy;
|
|
45
|
+
/** Elevation range used by geospatial tile selection. */
|
|
46
|
+
zRange?: ZRange | null;
|
|
47
|
+
/** Maximum concurrent tile requests. */
|
|
48
|
+
maxRequests?: number;
|
|
49
|
+
/** Debounce interval applied before issuing queued requests. */
|
|
50
|
+
debounceTime?: number;
|
|
51
|
+
/** Integer zoom offset applied when choosing tile levels. */
|
|
52
|
+
zoomOffset?: number;
|
|
53
|
+
/** Callback fired when a tile loads successfully. */
|
|
54
|
+
onTileLoad?: (tile: SharedTile2DHeader<DataT>) => void;
|
|
55
|
+
/** Callback fired when a tile is evicted from cache. */
|
|
56
|
+
onTileUnload?: (tile: SharedTile2DHeader<DataT>) => void;
|
|
57
|
+
/** Callback fired when a tile request fails. */
|
|
58
|
+
onTileError?: (err: any, tile: SharedTile2DHeader<DataT>) => void;
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
export const DEFAULT_TILESET2D_PROPS: Omit<Required<Tileset2DProps>, 'getTileData'> = {
|
|
62
|
+
adapter: null,
|
|
63
|
+
extent: null,
|
|
64
|
+
tileSize: 512,
|
|
65
|
+
maxZoom: null,
|
|
66
|
+
minZoom: null,
|
|
67
|
+
maxCacheSize: 100,
|
|
68
|
+
maxCacheByteSize: null,
|
|
69
|
+
refinementStrategy: 'best-available',
|
|
70
|
+
zRange: null,
|
|
71
|
+
maxRequests: 6,
|
|
72
|
+
debounceTime: 0,
|
|
73
|
+
zoomOffset: 0,
|
|
74
|
+
onTileLoad: () => {},
|
|
75
|
+
onTileUnload: () => {},
|
|
76
|
+
onTileError: () => {}
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
/** Subscription callbacks emitted by {@link SharedTileset2D}. */
|
|
80
|
+
type Tile2DListener<DataT = any> = {
|
|
81
|
+
/** Fired after a tile loads successfully. */
|
|
82
|
+
onTileLoad?: (tile: SharedTile2DHeader<DataT>) => void;
|
|
83
|
+
/** Fired after a tile request fails. */
|
|
84
|
+
onTileError?: (error: any, tile: SharedTile2DHeader<DataT>) => void;
|
|
85
|
+
/** Fired after a tile is evicted from cache. */
|
|
86
|
+
onTileUnload?: (tile: SharedTile2DHeader<DataT>) => void;
|
|
87
|
+
/** Fired when metadata or effective configuration changes. */
|
|
88
|
+
onUpdate?: () => void;
|
|
89
|
+
/** Fired when asynchronous metadata initialization fails. */
|
|
90
|
+
onError?: (error: Error) => void;
|
|
91
|
+
/** Fired after live tileset counters are recomputed. */
|
|
92
|
+
onStatsChange?: (stats: Stats) => void;
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
/** Per-consumer tile sets retained by the shared cache. */
|
|
96
|
+
type ConsumerState<DataT = any> = {
|
|
97
|
+
/** Tiles selected for this consumer's most recent traversal. */
|
|
98
|
+
selectedTiles: Set<SharedTile2DHeader<DataT>>;
|
|
99
|
+
/** Tiles currently marked render-visible for this consumer. */
|
|
100
|
+
visibleTiles: Set<SharedTile2DHeader<DataT>>;
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
/** Options for creating a shared tile cache that can be reused by multiple layers and views. */
|
|
104
|
+
export type SharedTileset2DProps<DataT = any, ViewStateT = unknown> = Omit<
|
|
105
|
+
Tileset2DProps<DataT, ViewStateT>,
|
|
106
|
+
'getTileData'
|
|
107
|
+
> & {
|
|
108
|
+
/** Optional tile loader used when not sourcing data from a loaders.gl TileSource. */
|
|
109
|
+
getTileData?: (props: TileLoadProps) => Promise<DataT | null> | DataT | null;
|
|
110
|
+
/** Optional loaders.gl TileSource backing this shared tileset. */
|
|
111
|
+
tileSource?: TileSource;
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
/** Shared tile cache and loading engine for one or more {@link SharedTile2DLayer} instances. */
|
|
115
|
+
export class SharedTileset2D<DataT = any, ViewStateT = unknown> {
|
|
116
|
+
/** Live counters describing shared tileset state. */
|
|
117
|
+
readonly stats: Stats;
|
|
118
|
+
/** Effective runtime options after defaults and metadata overrides have been applied. */
|
|
119
|
+
protected opts: Required<SharedTileset2DProps<DataT, ViewStateT>>;
|
|
120
|
+
/** Cached metadata returned by the backing TileSource, if any. */
|
|
121
|
+
protected sourceMetadata: TileSourceMetadata | null = null;
|
|
122
|
+
/** Scheduler shared across all tile requests for this tileset. */
|
|
123
|
+
private _requestScheduler: RequestScheduler;
|
|
124
|
+
/** Shared tile cache keyed by tile id. */
|
|
125
|
+
private _cache: Map<string, SharedTile2DHeader<DataT>>;
|
|
126
|
+
/** Tracks whether parent/child links need rebuilding. */
|
|
127
|
+
private _dirty: boolean;
|
|
128
|
+
/** Cached tiles sorted by zoom for traversal and rendering. */
|
|
129
|
+
private _tiles: SharedTile2DHeader<DataT>[];
|
|
130
|
+
/** Running total of cached payload byte size. */
|
|
131
|
+
private _cacheByteSize: number;
|
|
132
|
+
/** Cumulative number of tiles evicted from the shared cache. */
|
|
133
|
+
private _unloadedTileCount: number;
|
|
134
|
+
/** Subscribers watching tileset lifecycle events. */
|
|
135
|
+
private _listeners = new Set<Tile2DListener<DataT>>();
|
|
136
|
+
/** Selected and visible tiles tracked per consumer. */
|
|
137
|
+
private _consumers = new Map<symbol, ConsumerState<DataT>>();
|
|
138
|
+
/** Option names explicitly set by the caller. */
|
|
139
|
+
private _explicitOptionKeys = new Set<string>();
|
|
140
|
+
/** Caller-provided options before metadata-derived overrides are applied. */
|
|
141
|
+
private _baseOpts: Partial<SharedTileset2DProps<DataT, ViewStateT>> = {};
|
|
142
|
+
/** Derived overrides sourced from TileSource metadata. */
|
|
143
|
+
private _sourceMetadataOverrides: Partial<SharedTileset2DProps<DataT, ViewStateT>> = {};
|
|
144
|
+
/** Resolved maximum zoom level used by traversal. */
|
|
145
|
+
private _maxZoom?: number;
|
|
146
|
+
/** Resolved minimum zoom level used by traversal. */
|
|
147
|
+
private _minZoom?: number;
|
|
148
|
+
/** Most recent traversal context used to derive tile metadata. */
|
|
149
|
+
private _lastTileContext: SharedTileset2DTileContext<ViewStateT> | null = null;
|
|
150
|
+
|
|
151
|
+
/** Creates a tileset from either `getTileData` or a loaders.gl `TileSource`. */
|
|
152
|
+
constructor(opts: SharedTileset2DProps<DataT, ViewStateT>) {
|
|
153
|
+
this.stats = new Stats({
|
|
154
|
+
id: 'SharedTileset2D',
|
|
155
|
+
stats: [
|
|
156
|
+
{name: 'Tiles In Cache'},
|
|
157
|
+
{name: 'Cache Size'},
|
|
158
|
+
{name: 'Visible Tiles'},
|
|
159
|
+
{name: 'Selected Tiles'},
|
|
160
|
+
{name: 'Loading Tiles'},
|
|
161
|
+
{name: 'Unloaded Tiles'},
|
|
162
|
+
{name: 'Consumers'}
|
|
163
|
+
]
|
|
164
|
+
});
|
|
165
|
+
this.opts = {
|
|
166
|
+
...DEFAULT_TILESET2D_PROPS,
|
|
167
|
+
...opts,
|
|
168
|
+
getTileData: opts.getTileData || (() => null),
|
|
169
|
+
tileSource: opts.tileSource
|
|
170
|
+
} as Required<SharedTileset2DProps<DataT, ViewStateT>>;
|
|
171
|
+
|
|
172
|
+
this._requestScheduler = new RequestScheduler({
|
|
173
|
+
throttleRequests: this.opts.maxRequests > 0 || this.opts.debounceTime > 0,
|
|
174
|
+
maxRequests: this.opts.maxRequests,
|
|
175
|
+
debounceTime: this.opts.debounceTime
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
this._cache = new Map();
|
|
179
|
+
this._tiles = [];
|
|
180
|
+
this._dirty = false;
|
|
181
|
+
this._cacheByteSize = 0;
|
|
182
|
+
this._unloadedTileCount = 0;
|
|
183
|
+
|
|
184
|
+
if (!this.opts.tileSource && !opts.getTileData) {
|
|
185
|
+
throw new Error('SharedTileset2D requires either `getTileData` or `tileSource`.');
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
this.setOptions(opts);
|
|
189
|
+
this._updateStats();
|
|
190
|
+
|
|
191
|
+
if (this.opts.tileSource) {
|
|
192
|
+
this._initializeTileSource(this.opts.tileSource).catch(() => {});
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/** Convenience factory for wrapping a loaders.gl `TileSource`. */
|
|
197
|
+
static fromTileSource<DataT = any>(
|
|
198
|
+
tileSource: TileSource,
|
|
199
|
+
opts: Omit<SharedTileset2DProps<DataT>, 'tileSource' | 'getTileData'> = {}
|
|
200
|
+
): SharedTileset2D<DataT> {
|
|
201
|
+
return new SharedTileset2D<DataT>({...opts, tileSource});
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/** All tiles currently present in the shared cache. */
|
|
205
|
+
get tiles(): SharedTile2DHeader<DataT>[] {
|
|
206
|
+
return this._tiles;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/** Estimated byte size of all tile content currently retained in cache. */
|
|
210
|
+
get cacheByteSize(): number {
|
|
211
|
+
return this._cacheByteSize;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/** Union of tiles selected by all attached consumers. */
|
|
215
|
+
get selectedTiles(): SharedTile2DHeader<DataT>[] {
|
|
216
|
+
return Array.from(this._getSelectedTilesUnion());
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/** Union of tiles contributing to the visible result across all consumers and views, including unloaded selected tiles. */
|
|
220
|
+
get visibleTiles(): SharedTile2DHeader<DataT>[] {
|
|
221
|
+
const union = this._getVisibleTilesUnion();
|
|
222
|
+
for (const tile of this._getSelectedTilesUnion()) {
|
|
223
|
+
union.add(tile);
|
|
224
|
+
}
|
|
225
|
+
return Array.from(union);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/** Tiles currently loading anywhere in the shared cache. */
|
|
229
|
+
get loadingTiles(): SharedTile2DHeader<DataT>[] {
|
|
230
|
+
return Array.from(this._cache.values()).filter((tile) => tile.isLoading);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/** Tiles retained in cache that do not currently have loaded content. */
|
|
234
|
+
get unloadedTiles(): SharedTile2DHeader<DataT>[] {
|
|
235
|
+
return Array.from(this._cache.values()).filter((tile) => !tile.isLoaded);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/** Maximum resolved zoom level after applying metadata and explicit options. */
|
|
239
|
+
get maxZoom(): number | undefined {
|
|
240
|
+
return this._maxZoom;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/** Minimum resolved zoom level after applying metadata and explicit options. */
|
|
244
|
+
get minZoom(): number | undefined {
|
|
245
|
+
return this._minZoom;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/** Active refinement strategy for placeholder handling. */
|
|
249
|
+
get refinementStrategy(): RefinementStrategy {
|
|
250
|
+
return this.opts.refinementStrategy || STRATEGY_DEFAULT;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/** Adapter currently used for traversal and tile metadata. */
|
|
254
|
+
get adapter(): SharedTileset2DAdapter<ViewStateT> | null {
|
|
255
|
+
return this.opts.adapter;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/** Subscribes to tileset lifecycle events. */
|
|
259
|
+
subscribe(listener: Tile2DListener<DataT>): () => void {
|
|
260
|
+
this._listeners.add(listener);
|
|
261
|
+
return () => this._listeners.delete(listener);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/** Registers a consumer so cache pruning can account for its selected tiles. */
|
|
265
|
+
attachConsumer(id: symbol): void {
|
|
266
|
+
this._consumers.set(id, {selectedTiles: new Set(), visibleTiles: new Set()});
|
|
267
|
+
this._updateStats();
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/** Unregisters a consumer and prunes unused requests and tiles. */
|
|
271
|
+
detachConsumer(id: symbol): void {
|
|
272
|
+
this._consumers.delete(id);
|
|
273
|
+
this._pruneRequests();
|
|
274
|
+
this._resizeCache();
|
|
275
|
+
this._updateStats();
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/** Updates tileset options and reapplies TileSource metadata overrides. */
|
|
279
|
+
setOptions(opts: Partial<SharedTileset2DProps<DataT, ViewStateT>>): void {
|
|
280
|
+
this._rememberExplicitOptions(opts);
|
|
281
|
+
this._baseOpts = {...this._baseOpts, ...opts};
|
|
282
|
+
this._applyResolvedOptions();
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/** Aborts in-flight requests and clears the shared cache. */
|
|
286
|
+
finalize(): void {
|
|
287
|
+
for (const tile of this._cache.values()) {
|
|
288
|
+
if (tile.isLoading) {
|
|
289
|
+
tile.abort();
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
this._cache.clear();
|
|
293
|
+
this._tiles = [];
|
|
294
|
+
this._consumers.clear();
|
|
295
|
+
this._cacheByteSize = 0;
|
|
296
|
+
this._unloadedTileCount = 0;
|
|
297
|
+
this._updateStats();
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/** Marks all retained tiles stale and drops unused cached tiles. */
|
|
301
|
+
reloadAll(): void {
|
|
302
|
+
const selectedTiles = this._getSelectedTilesUnion();
|
|
303
|
+
for (const id of this._cache.keys()) {
|
|
304
|
+
const tile = this._cache.get(id);
|
|
305
|
+
if (tile && !selectedTiles.has(tile)) {
|
|
306
|
+
this._cache.delete(id);
|
|
307
|
+
} else if (tile) {
|
|
308
|
+
tile.setNeedsReload();
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
this._cacheByteSize = this._getCacheByteSize();
|
|
312
|
+
this.prepareTiles();
|
|
313
|
+
this._updateStats();
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
/** Updates the selected and visible tile sets for one consumer. */
|
|
317
|
+
updateConsumer(
|
|
318
|
+
id: symbol,
|
|
319
|
+
selectedTiles: SharedTile2DHeader<DataT>[],
|
|
320
|
+
visibleTiles: SharedTile2DHeader<DataT>[]
|
|
321
|
+
): void {
|
|
322
|
+
this._consumers.set(id, {
|
|
323
|
+
selectedTiles: new Set(selectedTiles),
|
|
324
|
+
visibleTiles: new Set(visibleTiles)
|
|
325
|
+
});
|
|
326
|
+
this._pruneRequests();
|
|
327
|
+
this._resizeCache();
|
|
328
|
+
this._updateStats();
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
/** Rebuilds parent/child links if the cache changed since the last traversal. */
|
|
332
|
+
prepareTiles(): void {
|
|
333
|
+
if (this._dirty) {
|
|
334
|
+
this._rebuildTree();
|
|
335
|
+
this._syncTiles();
|
|
336
|
+
this._dirty = false;
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
/** Returns tile indices needed to cover a viewport. */
|
|
341
|
+
getTileIndices({
|
|
342
|
+
viewState,
|
|
343
|
+
maxZoom,
|
|
344
|
+
minZoom,
|
|
345
|
+
zRange,
|
|
346
|
+
modelMatrix,
|
|
347
|
+
modelMatrixInverse
|
|
348
|
+
}: {
|
|
349
|
+
viewState: ViewStateT;
|
|
350
|
+
maxZoom?: number;
|
|
351
|
+
minZoom?: number;
|
|
352
|
+
zRange: ZRange | null;
|
|
353
|
+
modelMatrix?: Matrix4 | null;
|
|
354
|
+
modelMatrixInverse?: Matrix4 | null;
|
|
355
|
+
}): TileIndex[] {
|
|
356
|
+
const {adapter, tileSize, extent, zoomOffset} = this.opts;
|
|
357
|
+
if (!adapter) {
|
|
358
|
+
throw new Error('SharedTileset2D requires an adapter before tile traversal can be used.');
|
|
359
|
+
}
|
|
360
|
+
this._lastTileContext = {viewState, tileSize};
|
|
361
|
+
return adapter.getTileIndices({
|
|
362
|
+
viewState,
|
|
363
|
+
maxZoom,
|
|
364
|
+
minZoom,
|
|
365
|
+
zRange,
|
|
366
|
+
tileSize,
|
|
367
|
+
extent: normalizeBounds(extent),
|
|
368
|
+
modelMatrix,
|
|
369
|
+
modelMatrixInverse,
|
|
370
|
+
zoomOffset
|
|
371
|
+
});
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
/** Returns the stable cache id for a tile index. */
|
|
375
|
+
getTileId(index: TileIndex): string {
|
|
376
|
+
return `${index.x}-${index.y}-${index.z}`;
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
/** Returns the zoom level represented by a tile index. */
|
|
380
|
+
getTileZoom(index: TileIndex): number {
|
|
381
|
+
return index.z;
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
/** Returns derived metadata used to initialize a tile header. */
|
|
385
|
+
getTileMetadata(index: TileIndex): Record<string, any> {
|
|
386
|
+
if (!this._lastTileContext) {
|
|
387
|
+
throw new Error('SharedTileset2D metadata requested before traversal context was set.');
|
|
388
|
+
}
|
|
389
|
+
if (!this.opts.adapter) {
|
|
390
|
+
throw new Error('SharedTileset2D requires an adapter before tile metadata can be derived.');
|
|
391
|
+
}
|
|
392
|
+
return {bbox: this.opts.adapter.getTileBoundingBox(this._lastTileContext, index)};
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
/** Returns the parent tile index in the quadtree. */
|
|
396
|
+
getParentIndex(index: TileIndex): TileIndex {
|
|
397
|
+
return {x: Math.floor(index.x / 2), y: Math.floor(index.y / 2), z: index.z - 1};
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
/** Returns a cached tile and optionally creates and loads it on demand. */
|
|
401
|
+
getTile(index: TileIndex, create: true): SharedTile2DHeader<DataT>;
|
|
402
|
+
getTile(index: TileIndex, create?: false): SharedTile2DHeader<DataT> | undefined;
|
|
403
|
+
getTile(index: TileIndex, create?: boolean): SharedTile2DHeader<DataT> | undefined {
|
|
404
|
+
const id = this.getTileId(index);
|
|
405
|
+
let tile = this._cache.get(id);
|
|
406
|
+
let needsReload = false;
|
|
407
|
+
|
|
408
|
+
if (!tile && create) {
|
|
409
|
+
tile = new SharedTile2DHeader(index);
|
|
410
|
+
Object.assign(tile, this.getTileMetadata(tile.index));
|
|
411
|
+
Object.assign(tile, {id, zoom: this.getTileZoom(tile.index)});
|
|
412
|
+
needsReload = true;
|
|
413
|
+
this._cache.set(id, tile);
|
|
414
|
+
this._dirty = true;
|
|
415
|
+
this._updateStats();
|
|
416
|
+
} else if (tile && tile.needsReload) {
|
|
417
|
+
needsReload = true;
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
if (tile) {
|
|
421
|
+
this._touchTile(id, tile);
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
if (tile && needsReload) {
|
|
425
|
+
tile
|
|
426
|
+
.loadData({
|
|
427
|
+
getData: this.opts.getTileData,
|
|
428
|
+
requestScheduler: this._requestScheduler,
|
|
429
|
+
onLoad: this._handleTileLoad.bind(this),
|
|
430
|
+
onError: this._handleTileError.bind(this)
|
|
431
|
+
})
|
|
432
|
+
.catch(() => {});
|
|
433
|
+
this._updateStats();
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
return tile;
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
/** Loads metadata from a TileSource and reapplies derived option overrides. */
|
|
440
|
+
private async _initializeTileSource(tileSource: TileSource): Promise<void> {
|
|
441
|
+
try {
|
|
442
|
+
this.sourceMetadata = await tileSource.getMetadata();
|
|
443
|
+
this._sourceMetadataOverrides = this._getMetadataOverrides(this.sourceMetadata);
|
|
444
|
+
this._applyResolvedOptions();
|
|
445
|
+
this._notifyUpdate();
|
|
446
|
+
} catch (error: any) {
|
|
447
|
+
const normalizedError =
|
|
448
|
+
error instanceof Error ? error : new Error(`TileSource metadata error: ${String(error)}`);
|
|
449
|
+
this._notifyError(normalizedError);
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
/** Tracks which options were explicitly set by the caller. */
|
|
454
|
+
private _rememberExplicitOptions(opts: Partial<SharedTileset2DProps<DataT, ViewStateT>>): void {
|
|
455
|
+
for (const key of Object.keys(opts)) {
|
|
456
|
+
this._explicitOptionKeys.add(key);
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
/** Resolves defaults, metadata overrides, and caller options into runtime settings. */
|
|
461
|
+
private _applyResolvedOptions(): void {
|
|
462
|
+
const resolvedOpts = {
|
|
463
|
+
...DEFAULT_TILESET2D_PROPS,
|
|
464
|
+
...this._sourceMetadataOverrides,
|
|
465
|
+
...this._baseOpts
|
|
466
|
+
} as Required<SharedTileset2DProps<DataT, ViewStateT>>;
|
|
467
|
+
|
|
468
|
+
if (resolvedOpts.tileSource) {
|
|
469
|
+
const tileSource = resolvedOpts.tileSource;
|
|
470
|
+
resolvedOpts.getTileData = (loadProps: TileLoadProps) =>
|
|
471
|
+
tileSource.getTileData(loadProps) as Promise<DataT | null> | DataT | null;
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
this.opts = resolvedOpts;
|
|
475
|
+
this._maxZoom =
|
|
476
|
+
typeof this.opts.maxZoom === 'number' && Number.isFinite(this.opts.maxZoom)
|
|
477
|
+
? Math.floor(this.opts.maxZoom)
|
|
478
|
+
: undefined;
|
|
479
|
+
this._minZoom =
|
|
480
|
+
typeof this.opts.minZoom === 'number' && Number.isFinite(this.opts.minZoom)
|
|
481
|
+
? Math.ceil(this.opts.minZoom)
|
|
482
|
+
: undefined;
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
/** Maps TileSource metadata into supported tileset options. */
|
|
486
|
+
private _getMetadataOverrides(
|
|
487
|
+
metadata: TileSourceMetadata | null
|
|
488
|
+
): Partial<SharedTileset2DProps<DataT, ViewStateT>> {
|
|
489
|
+
if (!metadata) {
|
|
490
|
+
return {};
|
|
491
|
+
}
|
|
492
|
+
const overrides: Partial<SharedTileset2DProps<DataT, ViewStateT>> = {};
|
|
493
|
+
if (!this._explicitOptionKeys.has('minZoom') && Number.isFinite(metadata.minZoom)) {
|
|
494
|
+
overrides.minZoom = metadata.minZoom;
|
|
495
|
+
}
|
|
496
|
+
if (!this._explicitOptionKeys.has('maxZoom') && Number.isFinite(metadata.maxZoom)) {
|
|
497
|
+
overrides.maxZoom = metadata.maxZoom;
|
|
498
|
+
}
|
|
499
|
+
if (!this._explicitOptionKeys.has('extent') && metadata.boundingBox) {
|
|
500
|
+
overrides.extent = [
|
|
501
|
+
metadata.boundingBox[0][0],
|
|
502
|
+
metadata.boundingBox[0][1],
|
|
503
|
+
metadata.boundingBox[1][0],
|
|
504
|
+
metadata.boundingBox[1][1]
|
|
505
|
+
];
|
|
506
|
+
}
|
|
507
|
+
return overrides;
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
/** Handles successful tile loads. */
|
|
511
|
+
private _handleTileLoad(tile: SharedTile2DHeader<DataT>): void {
|
|
512
|
+
this.opts.onTileLoad?.(tile);
|
|
513
|
+
this._cacheByteSize = this._getCacheByteSize();
|
|
514
|
+
this._resizeCache();
|
|
515
|
+
for (const listener of this._listeners) {
|
|
516
|
+
listener.onTileLoad?.(tile);
|
|
517
|
+
}
|
|
518
|
+
this._updateStats();
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
/** Handles tile load failures. */
|
|
522
|
+
private _handleTileError(error: any, tile: SharedTile2DHeader<DataT>): void {
|
|
523
|
+
this.opts.onTileError?.(error, tile);
|
|
524
|
+
for (const listener of this._listeners) {
|
|
525
|
+
listener.onTileError?.(error, tile);
|
|
526
|
+
}
|
|
527
|
+
this._updateStats();
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
/** Handles tile eviction from cache. */
|
|
531
|
+
private _handleTileUnload(tile: SharedTile2DHeader<DataT>): void {
|
|
532
|
+
this._unloadedTileCount++;
|
|
533
|
+
this.opts.onTileUnload?.(tile);
|
|
534
|
+
for (const listener of this._listeners) {
|
|
535
|
+
listener.onTileUnload?.(tile);
|
|
536
|
+
}
|
|
537
|
+
this._updateStats();
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
/** Notifies listeners that metadata or effective options changed. */
|
|
541
|
+
private _notifyUpdate(): void {
|
|
542
|
+
for (const listener of this._listeners) {
|
|
543
|
+
listener.onUpdate?.();
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
/** Notifies listeners about asynchronous metadata errors. */
|
|
548
|
+
private _notifyError(error: Error): void {
|
|
549
|
+
for (const listener of this._listeners) {
|
|
550
|
+
listener.onError?.(error);
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
/** Recomputes absolute counter stats and notifies listeners. */
|
|
555
|
+
private _updateStats(): void {
|
|
556
|
+
this._setStatCount('Tiles In Cache', this._cache.size);
|
|
557
|
+
this._setStatCount('Cache Size', this.cacheByteSize);
|
|
558
|
+
this._setStatCount('Visible Tiles', this.visibleTiles.length);
|
|
559
|
+
this._setStatCount('Selected Tiles', this.selectedTiles.length);
|
|
560
|
+
this._setStatCount('Loading Tiles', this.loadingTiles.length);
|
|
561
|
+
this._setStatCount('Unloaded Tiles', this._unloadedTileCount);
|
|
562
|
+
this._setStatCount('Consumers', this._consumers.size);
|
|
563
|
+
|
|
564
|
+
for (const listener of this._listeners) {
|
|
565
|
+
listener.onStatsChange?.(this.stats);
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
/** Writes an absolute count into a probe.gl stat. */
|
|
570
|
+
private _setStatCount(name: string, value: number): void {
|
|
571
|
+
this.stats.get(name).reset().addCount(value);
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
/** Returns the union of selected tiles across all consumers. */
|
|
575
|
+
private _getSelectedTilesUnion(): Set<SharedTile2DHeader<DataT>> {
|
|
576
|
+
const union = new Set<SharedTile2DHeader<DataT>>();
|
|
577
|
+
for (const consumer of this._consumers.values()) {
|
|
578
|
+
for (const tile of consumer.selectedTiles) {
|
|
579
|
+
union.add(tile);
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
return union;
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
/** Returns the union of visible tiles across all consumers. */
|
|
586
|
+
private _getVisibleTilesUnion(): Set<SharedTile2DHeader<DataT>> {
|
|
587
|
+
const union = new Set<SharedTile2DHeader<DataT>>();
|
|
588
|
+
for (const consumer of this._consumers.values()) {
|
|
589
|
+
for (const tile of consumer.visibleTiles) {
|
|
590
|
+
union.add(tile);
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
return union;
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
/** Moves a touched tile to the back of the cache map for LRU eviction ordering. */
|
|
597
|
+
private _touchTile(id: string, tile: SharedTile2DHeader<DataT>): void {
|
|
598
|
+
this._cache.delete(id);
|
|
599
|
+
this._cache.set(id, tile);
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
/** Computes the total byte size of all cached tile content. */
|
|
603
|
+
private _getCacheByteSize(): number {
|
|
604
|
+
let byteLength = 0;
|
|
605
|
+
for (const tile of this._cache.values()) {
|
|
606
|
+
byteLength += tile.byteLength;
|
|
607
|
+
}
|
|
608
|
+
return byteLength;
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
/** Cancels low-priority requests when consumers no longer need them. */
|
|
612
|
+
private _pruneRequests(): void {
|
|
613
|
+
const {maxRequests = 0} = this.opts;
|
|
614
|
+
const selectedTiles = this._getSelectedTilesUnion();
|
|
615
|
+
const visibleTiles = this._getVisibleTilesUnion();
|
|
616
|
+
const abortCandidates: SharedTile2DHeader<DataT>[] = [];
|
|
617
|
+
let ongoingRequestCount = 0;
|
|
618
|
+
|
|
619
|
+
for (const tile of this._cache.values()) {
|
|
620
|
+
if (tile.isLoading) {
|
|
621
|
+
ongoingRequestCount++;
|
|
622
|
+
if (!selectedTiles.has(tile) && !visibleTiles.has(tile)) {
|
|
623
|
+
abortCandidates.push(tile);
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
while (maxRequests > 0 && ongoingRequestCount > maxRequests && abortCandidates.length > 0) {
|
|
629
|
+
const tile = abortCandidates.shift();
|
|
630
|
+
if (tile) {
|
|
631
|
+
tile.abort();
|
|
632
|
+
}
|
|
633
|
+
ongoingRequestCount--;
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
/** Rebuilds parent and child links for all cached tiles. */
|
|
638
|
+
private _rebuildTree(): void {
|
|
639
|
+
for (const tile of this._cache.values()) {
|
|
640
|
+
tile.parent = null;
|
|
641
|
+
if (tile.children) {
|
|
642
|
+
tile.children.length = 0;
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
for (const tile of this._cache.values()) {
|
|
646
|
+
const parent = this._getNearestAncestor(tile);
|
|
647
|
+
tile.parent = parent;
|
|
648
|
+
if (parent?.children) {
|
|
649
|
+
parent.children.push(tile);
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
/** Updates the sorted tile list used by traversal and rendering. */
|
|
655
|
+
private _syncTiles(): void {
|
|
656
|
+
this._tiles = Array.from(this._cache.values()).sort((t1, t2) => t1.zoom - t2.zoom);
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
/** Evicts unused cached tiles when configured cache limits are exceeded. */
|
|
660
|
+
private _resizeCache(): void {
|
|
661
|
+
const maxCacheSize = this.opts.maxCacheSize ?? 100;
|
|
662
|
+
const maxCacheByteSize = this.opts.maxCacheByteSize ?? Infinity;
|
|
663
|
+
const visibleTiles = this._getVisibleTilesUnion();
|
|
664
|
+
const selectedTiles = this._getSelectedTilesUnion();
|
|
665
|
+
const overflown = this._cache.size > maxCacheSize || this._cacheByteSize > maxCacheByteSize;
|
|
666
|
+
|
|
667
|
+
if (overflown) {
|
|
668
|
+
for (const [id, tile] of this._cache) {
|
|
669
|
+
if (!visibleTiles.has(tile) && !selectedTiles.has(tile)) {
|
|
670
|
+
this._cache.delete(id);
|
|
671
|
+
this._cacheByteSize = this._getCacheByteSize();
|
|
672
|
+
this._handleTileUnload(tile);
|
|
673
|
+
}
|
|
674
|
+
if (this._cache.size <= maxCacheSize && this._cacheByteSize <= maxCacheByteSize) {
|
|
675
|
+
break;
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
this._dirty = true;
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
if (this._dirty) {
|
|
682
|
+
this._rebuildTree();
|
|
683
|
+
this._syncTiles();
|
|
684
|
+
this._dirty = false;
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
/** Finds the nearest cached ancestor tile for placeholder rendering. */
|
|
689
|
+
private _getNearestAncestor(tile: SharedTile2DHeader<DataT>): SharedTile2DHeader<DataT> | null {
|
|
690
|
+
const {_minZoom = 0} = this;
|
|
691
|
+
let index = tile.index;
|
|
692
|
+
while (this.getTileZoom(index) > _minZoom) {
|
|
693
|
+
index = this.getParentIndex(index);
|
|
694
|
+
const parent = this.getTile(index);
|
|
695
|
+
if (parent) {
|
|
696
|
+
return parent;
|
|
697
|
+
}
|
|
698
|
+
}
|
|
699
|
+
return null;
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
function normalizeBounds(extent: number[] | null | undefined) {
|
|
704
|
+
return extent && extent.length === 4 ? (extent as [number, number, number, number]) : undefined;
|
|
705
|
+
}
|