@loaders.gl/tiles 5.0.0-alpha.5 → 5.0.0-alpha.7
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/dist.dev.js +5122 -2641
- package/dist/dist.min.js +4 -2
- package/dist/index.cjs +1670 -403
- package/dist/index.cjs.map +3 -3
- package/dist/index.d.ts +8 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -1
- package/dist/spatial/format-spatial-reference.d.ts +1 -0
- package/dist/spatial/format-spatial-reference.d.ts.map +1 -1
- package/dist/spatial/format-spatial-reference.js +104 -46
- package/dist/spatial/format-spatial-reference.js.map +1 -1
- package/dist/spatial/get-spatial-coordinate-frame.d.ts +12 -1
- package/dist/spatial/get-spatial-coordinate-frame.d.ts.map +1 -1
- package/dist/spatial/get-spatial-coordinate-frame.js +105 -24
- package/dist/spatial/get-spatial-coordinate-frame.js.map +1 -1
- package/dist/spatial/normalize-crs-identifier.d.ts +15 -0
- package/dist/spatial/normalize-crs-identifier.d.ts.map +1 -0
- package/dist/spatial/normalize-crs-identifier.js +22 -0
- package/dist/spatial/normalize-crs-identifier.js.map +1 -0
- package/dist/spatial/spatial-coordinate-transformer.d.ts +4 -0
- package/dist/spatial/spatial-coordinate-transformer.d.ts.map +1 -1
- package/dist/spatial/spatial-coordinate-transformer.js +89 -12
- package/dist/spatial/spatial-coordinate-transformer.js.map +1 -1
- package/dist/spatial/tiles-3d-spatial-transformer.d.ts +32 -0
- package/dist/spatial/tiles-3d-spatial-transformer.d.ts.map +1 -0
- package/dist/spatial/tiles-3d-spatial-transformer.js +175 -0
- package/dist/spatial/tiles-3d-spatial-transformer.js.map +1 -0
- package/dist/tileset-3d/common/indexed-archive-tileset-source.d.ts +3 -3
- package/dist/tileset-3d/common/indexed-archive-tileset-source.d.ts.map +1 -1
- package/dist/tileset-3d/common/indexed-archive-tileset-source.js.map +1 -1
- package/dist/tileset-3d/common/tile-3d-contracts.d.ts +63 -0
- package/dist/tileset-3d/common/tile-3d-contracts.d.ts.map +1 -0
- package/dist/tileset-3d/common/tile-3d-contracts.js +51 -0
- package/dist/tileset-3d/common/tile-3d-contracts.js.map +1 -0
- package/dist/tileset-3d/common/tile-3d.d.ts +29 -5
- package/dist/tileset-3d/common/tile-3d.d.ts.map +1 -1
- package/dist/tileset-3d/common/tile-3d.js +151 -20
- package/dist/tileset-3d/common/tile-3d.js.map +1 -1
- package/dist/tileset-3d/common/tileset-3d.d.ts +44 -2
- package/dist/tileset-3d/common/tileset-3d.d.ts.map +1 -1
- package/dist/tileset-3d/common/tileset-3d.js +31 -1
- package/dist/tileset-3d/common/tileset-3d.js.map +1 -1
- package/dist/tileset-3d/common/tileset-observability.d.ts +60 -0
- package/dist/tileset-3d/common/tileset-observability.d.ts.map +1 -0
- package/dist/tileset-3d/common/tileset-observability.js +39 -0
- package/dist/tileset-3d/common/tileset-observability.js.map +1 -0
- package/dist/tileset-3d/common/tileset-source.d.ts +11 -8
- package/dist/tileset-3d/common/tileset-source.d.ts.map +1 -1
- package/dist/tileset-3d/common/tileset-source.js.map +1 -1
- package/dist/tileset-3d/common/tileset-traverser.d.ts +14 -0
- package/dist/tileset-3d/common/tileset-traverser.d.ts.map +1 -1
- package/dist/tileset-3d/common/tileset-traverser.js +21 -5
- package/dist/tileset-3d/common/tileset-traverser.js.map +1 -1
- package/dist/tileset-3d/format-3d-tiles/implicit-tiling.d.ts +47 -1
- package/dist/tileset-3d/format-3d-tiles/implicit-tiling.d.ts.map +1 -1
- package/dist/tileset-3d/format-3d-tiles/implicit-tiling.js +327 -30
- package/dist/tileset-3d/format-3d-tiles/implicit-tiling.js.map +1 -1
- package/dist/tileset-3d/format-3d-tiles/tiles-3d-source.d.ts +34 -2
- package/dist/tileset-3d/format-3d-tiles/tiles-3d-source.d.ts.map +1 -1
- package/dist/tileset-3d/format-3d-tiles/tiles-3d-source.js +265 -14
- package/dist/tileset-3d/format-3d-tiles/tiles-3d-source.js.map +1 -1
- package/dist/tileset-3d/format-3d-tiles/tileset-3d-traverser.d.ts +70 -3
- package/dist/tileset-3d/format-3d-tiles/tileset-3d-traverser.d.ts.map +1 -1
- package/dist/tileset-3d/format-3d-tiles/tileset-3d-traverser.js +216 -4
- package/dist/tileset-3d/format-3d-tiles/tileset-3d-traverser.js.map +1 -1
- package/dist/tileset-3d/format-i3s/i3s-source.d.ts +5 -3
- package/dist/tileset-3d/format-i3s/i3s-source.d.ts.map +1 -1
- package/dist/tileset-3d/format-i3s/i3s-source.js +14 -7
- package/dist/tileset-3d/format-i3s/i3s-source.js.map +1 -1
- package/dist/tileset-3d/helpers/bounding-volume.d.ts +7 -3
- package/dist/tileset-3d/helpers/bounding-volume.d.ts.map +1 -1
- package/dist/tileset-3d/helpers/bounding-volume.js +19 -61
- package/dist/tileset-3d/helpers/bounding-volume.js.map +1 -1
- package/dist/tileset-3d/helpers/frame-state.d.ts +4 -0
- package/dist/tileset-3d/helpers/frame-state.d.ts.map +1 -1
- package/dist/tileset-3d/helpers/frame-state.js +6 -1
- package/dist/tileset-3d/helpers/frame-state.js.map +1 -1
- package/dist/tileset-3d/helpers/s2-bounding-volume.d.ts +17 -0
- package/dist/tileset-3d/helpers/s2-bounding-volume.d.ts.map +1 -0
- package/dist/tileset-3d/helpers/s2-bounding-volume.js +20 -0
- package/dist/tileset-3d/helpers/s2-bounding-volume.js.map +1 -0
- package/dist/types.d.ts +2 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +14 -13
- package/src/index.ts +13 -1
- package/src/spatial/format-spatial-reference.ts +127 -53
- package/src/spatial/get-spatial-coordinate-frame.ts +126 -31
- package/src/spatial/normalize-crs-identifier.ts +26 -0
- package/src/spatial/spatial-coordinate-transformer.ts +106 -15
- package/src/spatial/tiles-3d-spatial-transformer.ts +206 -0
- package/src/tileset-3d/common/indexed-archive-tileset-source.ts +4 -9
- package/src/tileset-3d/common/tile-3d-contracts.ts +115 -0
- package/src/tileset-3d/common/tile-3d.ts +178 -20
- package/src/tileset-3d/common/tileset-3d.ts +63 -3
- package/src/tileset-3d/common/tileset-observability.ts +100 -0
- package/src/tileset-3d/common/tileset-source.ts +11 -12
- package/src/tileset-3d/common/tileset-traverser.ts +32 -5
- package/src/tileset-3d/format-3d-tiles/implicit-tiling.ts +481 -34
- package/src/tileset-3d/format-3d-tiles/tiles-3d-source.ts +390 -17
- package/src/tileset-3d/format-3d-tiles/tileset-3d-traverser.ts +243 -4
- package/src/tileset-3d/format-i3s/i3s-source.ts +25 -9
- package/src/tileset-3d/helpers/bounding-volume.ts +29 -62
- package/src/tileset-3d/helpers/frame-state.ts +6 -1
- package/src/tileset-3d/helpers/s2-bounding-volume.ts +35 -0
- package/src/types.ts +2 -0
|
@@ -8,6 +8,8 @@
|
|
|
8
8
|
import {Vector3, Matrix4} from '@math.gl/core';
|
|
9
9
|
import {CullingVolume} from '@math.gl/culling';
|
|
10
10
|
import type {CullingResult} from '@math.gl/culling';
|
|
11
|
+
import {getTile3DFeatureIdSets} from './tile-3d-contracts';
|
|
12
|
+
import type {Tile3DContent, Tile3DFeatureIdSet, Tile3DMetadataContext} from './tile-3d-contracts';
|
|
11
13
|
|
|
12
14
|
// Note: circular dependency
|
|
13
15
|
import type {Tileset3D} from './tileset-3d';
|
|
@@ -85,6 +87,14 @@ export function getContentVisibility(
|
|
|
85
87
|
return intersectingVolume ? 'intersecting' : 'inside';
|
|
86
88
|
}
|
|
87
89
|
|
|
90
|
+
function isNestedTilesetPayload(payload: unknown): boolean {
|
|
91
|
+
if (!payload || typeof payload !== 'object') {
|
|
92
|
+
return false;
|
|
93
|
+
}
|
|
94
|
+
const record = payload as Record<string, any>;
|
|
95
|
+
return record.shape === 'tileset3d' || record.type === 'tileset3d' || Boolean(record.tileset);
|
|
96
|
+
}
|
|
97
|
+
|
|
88
98
|
function defined(x) {
|
|
89
99
|
return x !== undefined && x !== null;
|
|
90
100
|
}
|
|
@@ -137,6 +147,9 @@ export class Tile3D {
|
|
|
137
147
|
*/
|
|
138
148
|
private _unscaledLodMetricValue: number = 0;
|
|
139
149
|
|
|
150
|
+
/** Whether the source format defines geometric error in transform-local units. */
|
|
151
|
+
private _scaleGeometricError: boolean = true;
|
|
152
|
+
|
|
140
153
|
/** @todo math.gl is not exporting BoundingVolume base type? */
|
|
141
154
|
boundingVolume: any = null;
|
|
142
155
|
|
|
@@ -147,6 +160,14 @@ export class Tile3D {
|
|
|
147
160
|
content: any = null;
|
|
148
161
|
/** Loaded payloads in the same order as {@link contentUrls}; `content` remains the primary payload. */
|
|
149
162
|
contents: any[] = [];
|
|
163
|
+
/** Ordered renderer-neutral descriptors for each source content entry. */
|
|
164
|
+
contentEntries: Tile3DContent[] = [];
|
|
165
|
+
/** Raw metadata references inherited by this tile and its content. */
|
|
166
|
+
metadataContext: Tile3DMetadataContext = {};
|
|
167
|
+
/** Feature-id declarations normalized from supported content extensions. */
|
|
168
|
+
featureIdSets: Tile3DFeatureIdSet[] = [];
|
|
169
|
+
/** Decoded 3D Tiles vector topology for the primary loaded content, when present. */
|
|
170
|
+
vectorContent: unknown = null;
|
|
150
171
|
/**
|
|
151
172
|
* Metadata entity attached to this tile in the source tileset, if present.
|
|
152
173
|
*
|
|
@@ -228,6 +249,7 @@ export class Tile3D {
|
|
|
228
249
|
return this._viewerRequestVolume;
|
|
229
250
|
}
|
|
230
251
|
|
|
252
|
+
/** Accumulated tile transforms from the source JSON, excluding the tileset model matrix. */
|
|
231
253
|
_initialTransform: Matrix4 = new Matrix4();
|
|
232
254
|
|
|
233
255
|
// Used by traverser, cannot be marked private
|
|
@@ -283,11 +305,7 @@ export class Tile3D {
|
|
|
283
305
|
this.type = header.type;
|
|
284
306
|
this.contentUrl = header.contentUrl;
|
|
285
307
|
this.contentUrls = header.contentUrls || (header.contentUrl ? [header.contentUrl] : []);
|
|
286
|
-
this.
|
|
287
|
-
const contentHeaders = Array.isArray(header.content) ? header.content : [header.content];
|
|
288
|
-
this.contentMetadata = contentHeaders
|
|
289
|
-
.filter(Boolean)
|
|
290
|
-
.map(contentHeader => contentHeader.metadata || null);
|
|
308
|
+
this._initializeMetadata(header);
|
|
291
309
|
this.childrenState = header.implicitSubtree ? 'unloaded' : 'ready';
|
|
292
310
|
|
|
293
311
|
this._initializeLodMetric(header);
|
|
@@ -295,6 +313,7 @@ export class Tile3D {
|
|
|
295
313
|
this._updateLodMetricScale();
|
|
296
314
|
this._initializeBoundingVolumes(header);
|
|
297
315
|
this._initializeContent(header);
|
|
316
|
+
this._initializeContentEntries(header);
|
|
298
317
|
this._initializeRenderingState(header);
|
|
299
318
|
|
|
300
319
|
Object.seal(this);
|
|
@@ -407,12 +426,16 @@ export class Tile3D {
|
|
|
407
426
|
}
|
|
408
427
|
|
|
409
428
|
/**
|
|
410
|
-
* Get bounding
|
|
429
|
+
* Get the current world-space bounding volume in cartographic coordinates.
|
|
411
430
|
* @returns [min, max] each in [longitude, latitude, altitude]
|
|
412
431
|
*/
|
|
413
432
|
get boundingBox(): CartographicBounds {
|
|
414
433
|
if (!this._boundingBox) {
|
|
415
|
-
this._boundingBox = getCartographicBounds(
|
|
434
|
+
this._boundingBox = getCartographicBounds(
|
|
435
|
+
this.header.boundingVolume,
|
|
436
|
+
this.boundingVolume,
|
|
437
|
+
!this.computedTransform.equals(this._initialTransform)
|
|
438
|
+
);
|
|
416
439
|
}
|
|
417
440
|
return this._boundingBox;
|
|
418
441
|
}
|
|
@@ -495,7 +518,8 @@ export class Tile3D {
|
|
|
495
518
|
// eslint-disable-next-line complexity
|
|
496
519
|
private _calculateRequestPriority(allowUnloadedContent: boolean): number {
|
|
497
520
|
const traverser = this.tileset._traverser;
|
|
498
|
-
const
|
|
521
|
+
const skipLevelOfDetail =
|
|
522
|
+
traverser.options.skipLevelOfDetail && !traverser.disableSkipLevelOfDetail;
|
|
499
523
|
|
|
500
524
|
/*
|
|
501
525
|
* Tiles that are outside of the camera's frustum could be skipped if we are in 'ADD' mode
|
|
@@ -584,6 +608,8 @@ export class Tile3D {
|
|
|
584
608
|
this.contents = loadResult.contents;
|
|
585
609
|
this.content = this.contents[0] || null;
|
|
586
610
|
}
|
|
611
|
+
this.vectorContent =
|
|
612
|
+
this.contents.find(content => Boolean(content?.vectorContent))?.vectorContent || null;
|
|
587
613
|
|
|
588
614
|
if (this.tileset.options.contentLoader) {
|
|
589
615
|
await this.tileset.options.contentLoader(this);
|
|
@@ -591,6 +617,7 @@ export class Tile3D {
|
|
|
591
617
|
|
|
592
618
|
this.contentState = TILE_CONTENT_STATE.READY;
|
|
593
619
|
this._onContentLoaded();
|
|
620
|
+
this._updateContentEntriesAfterLoad(loadResult);
|
|
594
621
|
return loadResult;
|
|
595
622
|
} catch (error) {
|
|
596
623
|
// Tile is unloaded before the content finishes loading
|
|
@@ -664,34 +691,137 @@ export class Tile3D {
|
|
|
664
691
|
/**
|
|
665
692
|
* Replaces a contentless implicit placeholder with its materialized subtree-root header.
|
|
666
693
|
*
|
|
667
|
-
*
|
|
668
|
-
* transform
|
|
669
|
-
*
|
|
694
|
+
* The placeholder transform is preserved when the subtree omits a root transform. An explicit
|
|
695
|
+
* subtree-root transform replaces it and is recomposed against the runtime parent. Content,
|
|
696
|
+
* metadata, bounds, and raw geometric error are reinitialized from the available header.
|
|
670
697
|
*
|
|
671
698
|
* @param materializedHeader - Available root header produced from one subtree resource.
|
|
672
699
|
*/
|
|
673
700
|
applyImplicitSubtreeHeader(materializedHeader: Record<string, any>): void {
|
|
674
701
|
const existingTransform = this.header.transform;
|
|
675
702
|
const existingTransformMatrix = this.header.transformMatrix;
|
|
703
|
+
const hasMaterializedTransform = materializedHeader.transform !== undefined;
|
|
676
704
|
this.header = {
|
|
677
705
|
...this.header,
|
|
678
706
|
...materializedHeader,
|
|
679
|
-
transform: existingTransform,
|
|
680
|
-
transformMatrix:
|
|
707
|
+
transform: hasMaterializedTransform ? materializedHeader.transform : existingTransform,
|
|
708
|
+
transformMatrix: hasMaterializedTransform
|
|
709
|
+
? materializedHeader.transformMatrix
|
|
710
|
+
: existingTransformMatrix,
|
|
681
711
|
implicitSubtree: undefined
|
|
682
712
|
};
|
|
713
|
+
if (hasMaterializedTransform) {
|
|
714
|
+
this._initializeTransforms(this.header);
|
|
715
|
+
}
|
|
683
716
|
this.refine = this._getRefine(materializedHeader.refine);
|
|
684
717
|
this.type = materializedHeader.type;
|
|
685
718
|
this.contentUrl = materializedHeader.contentUrl;
|
|
686
719
|
this.contentUrls =
|
|
687
720
|
materializedHeader.contentUrls ||
|
|
688
721
|
(materializedHeader.contentUrl ? [materializedHeader.contentUrl] : []);
|
|
722
|
+
this._initializeMetadata(this.header);
|
|
689
723
|
this._initializeLodMetric(materializedHeader);
|
|
690
724
|
this._updateLodMetricScale();
|
|
691
725
|
this._initializeBoundingVolumes(this.header);
|
|
726
|
+
this._boundingBox = undefined;
|
|
692
727
|
// Use the merged header so inherited viewer-request-volume metadata remains available when a
|
|
693
728
|
// materialized implicit tile has no render content of its own.
|
|
694
729
|
this._initializeContent(this.header);
|
|
730
|
+
this._initializeContentEntries(this.header);
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
/** Refreshes tile and content metadata from a source or materialized tile header. */
|
|
734
|
+
private _initializeMetadata(header: Record<string, any>): void {
|
|
735
|
+
this.metadata = header.metadata || null;
|
|
736
|
+
const contentHeaders = Array.isArray(header.content) ? header.content : [header.content];
|
|
737
|
+
this.contentMetadata = contentHeaders
|
|
738
|
+
.filter(Boolean)
|
|
739
|
+
.map(contentHeader => contentHeader.metadata || null);
|
|
740
|
+
const firstContentHeader = contentHeaders.find(Boolean) || {};
|
|
741
|
+
const groupReference = firstContentHeader.group;
|
|
742
|
+
const group =
|
|
743
|
+
typeof groupReference === 'number'
|
|
744
|
+
? this.tileset.groups?.[groupReference] || null
|
|
745
|
+
: groupReference || null;
|
|
746
|
+
const metadataContext: Tile3DMetadataContext = {
|
|
747
|
+
tileset: this.tileset.metadata || null,
|
|
748
|
+
group,
|
|
749
|
+
tile: this.metadata,
|
|
750
|
+
content: this.contentMetadata[0] || null,
|
|
751
|
+
subtree: header.implicitMetadata || null
|
|
752
|
+
};
|
|
753
|
+
if (this.tileset.groups) {
|
|
754
|
+
metadataContext.groups = this.tileset.groups;
|
|
755
|
+
}
|
|
756
|
+
this.metadataContext = metadataContext;
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
/** Updates payload and renderability while retaining immutable metadata descriptors. */
|
|
760
|
+
private _updateContentEntriesAfterLoad(loadResult: TileContentLoadResult): void {
|
|
761
|
+
if (loadResult.contentEntries) {
|
|
762
|
+
this.contentEntries = loadResult.contentEntries.map((entry, index) => ({
|
|
763
|
+
...entry,
|
|
764
|
+
index,
|
|
765
|
+
boundingVolume:
|
|
766
|
+
entry.boundingVolume || this._contentBoundingVolumes[index] || this.boundingVolume,
|
|
767
|
+
payload: entry.payload ?? this.contents[index] ?? null,
|
|
768
|
+
featureIds: entry.featureIds?.length
|
|
769
|
+
? entry.featureIds
|
|
770
|
+
: getTile3DFeatureIdSets(entry.payload ?? this.contents[index]),
|
|
771
|
+
renderable: Boolean(
|
|
772
|
+
entry.renderable !== false &&
|
|
773
|
+
(entry.payload ?? this.contents[index]) &&
|
|
774
|
+
!isNestedTilesetPayload(entry.payload ?? this.contents[index])
|
|
775
|
+
)
|
|
776
|
+
}));
|
|
777
|
+
this.featureIdSets = this.contentEntries.flatMap(entry => entry.featureIds);
|
|
778
|
+
return;
|
|
779
|
+
}
|
|
780
|
+
this.contentEntries = this.contentEntries.map((entry, index) => ({
|
|
781
|
+
...entry,
|
|
782
|
+
payload: this.contents[index] ?? null,
|
|
783
|
+
featureIds: getTile3DFeatureIdSets(this.contents[index]),
|
|
784
|
+
renderable: Boolean(this.contents[index]) && !isNestedTilesetPayload(this.contents[index])
|
|
785
|
+
}));
|
|
786
|
+
this.featureIdSets = this.contentEntries.flatMap(entry => entry.featureIds);
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
/** Returns one ordered content descriptor, or null when the index is out of range. */
|
|
790
|
+
getContentEntry(index: number): Tile3DContent | null {
|
|
791
|
+
return this.contentEntries[index] || null;
|
|
792
|
+
}
|
|
793
|
+
|
|
794
|
+
/** Returns whether an ordered content entry currently has renderable payload. */
|
|
795
|
+
isContentRenderable(index: number): boolean {
|
|
796
|
+
return Boolean(this.contentEntries[index]?.renderable);
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
/** Initializes additive renderer-neutral descriptors from a normalized tile header. */
|
|
800
|
+
private _initializeContentEntries(tileHeader: Record<string, any>): void {
|
|
801
|
+
const headers = Array.isArray(tileHeader.content)
|
|
802
|
+
? tileHeader.content
|
|
803
|
+
: tileHeader.content
|
|
804
|
+
? [tileHeader.content]
|
|
805
|
+
: [];
|
|
806
|
+
this.contentEntries = headers.map((contentHeader, index) => {
|
|
807
|
+
const normalizedContentHeader = contentHeader || {};
|
|
808
|
+
return {
|
|
809
|
+
index,
|
|
810
|
+
uri: this.contentUrls[index] || normalizedContentHeader.uri || normalizedContentHeader.url,
|
|
811
|
+
group: normalizedContentHeader.group,
|
|
812
|
+
type: normalizedContentHeader.type,
|
|
813
|
+
payload: null,
|
|
814
|
+
metadata: normalizedContentHeader.metadata || null,
|
|
815
|
+
boundingVolume: this._contentBoundingVolumes[index] || this.boundingVolume,
|
|
816
|
+
featureIds: this._getFeatureIdSets(normalizedContentHeader),
|
|
817
|
+
renderable: false
|
|
818
|
+
};
|
|
819
|
+
});
|
|
820
|
+
}
|
|
821
|
+
|
|
822
|
+
/** Extracts renderer-neutral feature-id declarations without decoding property values. */
|
|
823
|
+
private _getFeatureIdSets(contentHeader: Record<string, any>): Tile3DFeatureIdSet[] {
|
|
824
|
+
return getTile3DFeatureIdSets(contentHeader);
|
|
695
825
|
}
|
|
696
826
|
|
|
697
827
|
// Unloads the tile's content.
|
|
@@ -703,6 +833,13 @@ export class Tile3D {
|
|
|
703
833
|
}
|
|
704
834
|
this.contents = [];
|
|
705
835
|
this.content = null;
|
|
836
|
+
this.vectorContent = null;
|
|
837
|
+
this.contentEntries = this.contentEntries.map(entry => ({
|
|
838
|
+
...entry,
|
|
839
|
+
payload: null,
|
|
840
|
+
renderable: false
|
|
841
|
+
}));
|
|
842
|
+
this.featureIdSets = this.contentEntries.flatMap(entry => entry.featureIds);
|
|
706
843
|
if (this.header.content && this.header.content.destroy) {
|
|
707
844
|
this.header.content.destroy();
|
|
708
845
|
}
|
|
@@ -790,9 +927,12 @@ export class Tile3D {
|
|
|
790
927
|
}
|
|
791
928
|
|
|
792
929
|
this._foveatedFactor = calculateFoveatedFactor(this.boundingVolume, frameState.camera);
|
|
930
|
+
const traverser = this.tileset._traverser;
|
|
931
|
+
const skipLevelOfDetail =
|
|
932
|
+
traverser.options.skipLevelOfDetail && !traverser.disableSkipLevelOfDetail;
|
|
793
933
|
const deferralEligible = isFoveatedRequestDeferred({
|
|
794
934
|
refinement: this.refine,
|
|
795
|
-
skipLevelOfDetail
|
|
935
|
+
skipLevelOfDetail,
|
|
796
936
|
foveatedScreenSpaceError: options.foveatedScreenSpaceError,
|
|
797
937
|
foveatedConeSize: options.foveatedConeSize,
|
|
798
938
|
minimumScreenSpaceErrorRelaxation: options.foveatedMinimumScreenSpaceErrorRelaxation,
|
|
@@ -844,9 +984,15 @@ export class Tile3D {
|
|
|
844
984
|
* @param frameState Current camera, culling, and optional clipping-plane state.
|
|
845
985
|
* @returns The content visibility classification.
|
|
846
986
|
*/
|
|
847
|
-
contentVisibility(frameState: FrameState): CullingResult {
|
|
987
|
+
contentVisibility(frameState: FrameState, contentIndex?: number): CullingResult {
|
|
988
|
+
const contentVolumes =
|
|
989
|
+
contentIndex === undefined
|
|
990
|
+
? this._contentBoundingVolumes
|
|
991
|
+
: this._contentBoundingVolumes[contentIndex]
|
|
992
|
+
? [this._contentBoundingVolumes[contentIndex]]
|
|
993
|
+
: [];
|
|
848
994
|
return getContentVisibility(
|
|
849
|
-
|
|
995
|
+
contentVolumes,
|
|
850
996
|
this.boundingVolume,
|
|
851
997
|
frameState.cullingVolume,
|
|
852
998
|
this._visibilityPlaneMask,
|
|
@@ -909,6 +1055,10 @@ export class Tile3D {
|
|
|
909
1055
|
|
|
910
1056
|
/** Initializes the tile's LOD metric from its header or its nearest available ancestor. */
|
|
911
1057
|
_initializeLodMetric(header: {[key: string]: any}): void {
|
|
1058
|
+
this._scaleGeometricError =
|
|
1059
|
+
typeof header._scaleGeometricError === 'boolean'
|
|
1060
|
+
? header._scaleGeometricError
|
|
1061
|
+
: (this.parent?._scaleGeometricError ?? true);
|
|
912
1062
|
if ('lodMetricType' in header) {
|
|
913
1063
|
this.lodMetricType = header.lodMetricType;
|
|
914
1064
|
} else {
|
|
@@ -965,7 +1115,7 @@ export class Tile3D {
|
|
|
965
1115
|
* I3S `maxScreenThreshold` is already a screen-space metric and must not be transform-scaled.
|
|
966
1116
|
*/
|
|
967
1117
|
_updateLodMetricScale(): void {
|
|
968
|
-
if (this.lodMetricType !== LOD_METRIC_TYPE.GEOMETRIC_ERROR) {
|
|
1118
|
+
if (this.lodMetricType !== LOD_METRIC_TYPE.GEOMETRIC_ERROR || !this._scaleGeometricError) {
|
|
969
1119
|
this.lodMetricValue = this._unscaledLodMetricValue;
|
|
970
1120
|
return;
|
|
971
1121
|
}
|
|
@@ -1070,11 +1220,13 @@ export class Tile3D {
|
|
|
1070
1220
|
}
|
|
1071
1221
|
|
|
1072
1222
|
_updateBoundingVolume(header) {
|
|
1223
|
+
this._boundingBox = undefined;
|
|
1073
1224
|
// Update the bounding volumes
|
|
1074
1225
|
this.boundingVolume = createBoundingVolume(
|
|
1075
1226
|
header.boundingVolume,
|
|
1076
1227
|
this.computedTransform,
|
|
1077
|
-
this.boundingVolume
|
|
1228
|
+
this.boundingVolume,
|
|
1229
|
+
this._initialTransform
|
|
1078
1230
|
);
|
|
1079
1231
|
|
|
1080
1232
|
// Viewer request volumes constrain traversal, not just render content. A contentless implicit
|
|
@@ -1084,7 +1236,8 @@ export class Tile3D {
|
|
|
1084
1236
|
this._viewerRequestVolume = createBoundingVolume(
|
|
1085
1237
|
header.viewerRequestVolume,
|
|
1086
1238
|
this.computedTransform,
|
|
1087
|
-
this._viewerRequestVolume
|
|
1239
|
+
this._viewerRequestVolume,
|
|
1240
|
+
this._initialTransform
|
|
1088
1241
|
);
|
|
1089
1242
|
}
|
|
1090
1243
|
|
|
@@ -1102,7 +1255,12 @@ export class Tile3D {
|
|
|
1102
1255
|
this._contentBoundingVolumes = contentHeaders.length
|
|
1103
1256
|
? contentHeaders.map(headerEntry =>
|
|
1104
1257
|
headerEntry?.boundingVolume
|
|
1105
|
-
? createBoundingVolume(
|
|
1258
|
+
? createBoundingVolume(
|
|
1259
|
+
headerEntry.boundingVolume,
|
|
1260
|
+
this.computedTransform,
|
|
1261
|
+
undefined,
|
|
1262
|
+
this._initialTransform
|
|
1263
|
+
)
|
|
1106
1264
|
: this.boundingVolume
|
|
1107
1265
|
)
|
|
1108
1266
|
: [this.boundingVolume];
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
import {Matrix4, Vector3} from '@math.gl/core';
|
|
8
8
|
import {Ellipsoid} from '@math.gl/geospatial';
|
|
9
9
|
import {Stats} from '@probe.gl/stats';
|
|
10
|
-
import {RequestScheduler,
|
|
10
|
+
import {RequestScheduler, Loader, LoaderOptions} from '@loaders.gl/loader-utils';
|
|
11
11
|
import {TilesetCache} from './tileset-cache';
|
|
12
12
|
import {calculateTransformProps} from '../helpers/transform-utils';
|
|
13
13
|
import {getFrameState, limitSelectedTiles, updateCameraMotionState} from '../helpers/frame-state';
|
|
@@ -87,6 +87,26 @@ export type Tileset3DProps = {
|
|
|
87
87
|
* @default false
|
|
88
88
|
*/
|
|
89
89
|
skipLevelOfDetail?: boolean;
|
|
90
|
+
/**
|
|
91
|
+
* SSE above which tiles remain part of the non-skipping base traversal.
|
|
92
|
+
* @default 1024
|
|
93
|
+
*/
|
|
94
|
+
baseScreenSpaceError?: number;
|
|
95
|
+
/**
|
|
96
|
+
* Required SSE reduction from the nearest requested or loaded ancestor before loading a tile.
|
|
97
|
+
* @default 16
|
|
98
|
+
*/
|
|
99
|
+
skipScreenSpaceErrorFactor?: number;
|
|
100
|
+
/**
|
|
101
|
+
* Minimum hierarchy levels between requested tiles during skip-LOD traversal.
|
|
102
|
+
* @default 1
|
|
103
|
+
*/
|
|
104
|
+
skipLevels?: number;
|
|
105
|
+
/**
|
|
106
|
+
* Requests only final tiles that meet the active SSE target when skip-LOD is enabled.
|
|
107
|
+
* @default false
|
|
108
|
+
*/
|
|
109
|
+
immediatelyLoadDesiredLevelOfDetail?: boolean;
|
|
90
110
|
/** Enables perspective dynamic SSE to reduce distant, horizon-facing refinement. */
|
|
91
111
|
dynamicScreenSpaceError?: boolean;
|
|
92
112
|
/** Base dynamic SSE fog density in inverse meters. */
|
|
@@ -154,6 +174,14 @@ type Props = {
|
|
|
154
174
|
maximumScreenSpaceError: number;
|
|
155
175
|
/** Whether replacement traversal may skip levels while retaining ancestor coverage. */
|
|
156
176
|
skipLevelOfDetail: boolean;
|
|
177
|
+
/** SSE above which tiles remain part of the non-skipping base traversal. */
|
|
178
|
+
baseScreenSpaceError: number;
|
|
179
|
+
/** Required SSE reduction between requested skip-LOD tiles. */
|
|
180
|
+
skipScreenSpaceErrorFactor: number;
|
|
181
|
+
/** Minimum hierarchy levels between requested skip-LOD tiles. */
|
|
182
|
+
skipLevels: number;
|
|
183
|
+
/** Whether skip-LOD requests only the final desired tiles. */
|
|
184
|
+
immediatelyLoadDesiredLevelOfDetail: boolean;
|
|
157
185
|
/** Whether perspective dynamic SSE is enabled. */
|
|
158
186
|
dynamicScreenSpaceError: boolean;
|
|
159
187
|
/** Base dynamic SSE fog density in inverse meters. */
|
|
@@ -282,6 +310,10 @@ const DEFAULT_PROPS: Props = {
|
|
|
282
310
|
foveatedTimeDelay: 0.2,
|
|
283
311
|
maximumScreenSpaceError: 8,
|
|
284
312
|
skipLevelOfDetail: false,
|
|
313
|
+
baseScreenSpaceError: 1024,
|
|
314
|
+
skipScreenSpaceErrorFactor: 16,
|
|
315
|
+
skipLevels: 1,
|
|
316
|
+
immediatelyLoadDesiredLevelOfDetail: false,
|
|
285
317
|
dynamicScreenSpaceError: true,
|
|
286
318
|
dynamicScreenSpaceErrorDensity: 2.0e-4,
|
|
287
319
|
dynamicScreenSpaceErrorFactor: 24,
|
|
@@ -322,7 +354,7 @@ export class Tileset3D {
|
|
|
322
354
|
|
|
323
355
|
type: TILESET_TYPE;
|
|
324
356
|
tileset: TilesetJSON | null;
|
|
325
|
-
loader:
|
|
357
|
+
loader: Loader;
|
|
326
358
|
url: string;
|
|
327
359
|
basePath: string;
|
|
328
360
|
modelMatrix: Matrix4;
|
|
@@ -485,6 +517,26 @@ export class Tileset3D {
|
|
|
485
517
|
return this._frameNumber;
|
|
486
518
|
}
|
|
487
519
|
|
|
520
|
+
/**
|
|
521
|
+
* Returns the tiles requested during the most recent completed traversal.
|
|
522
|
+
*
|
|
523
|
+
* A defensive copy makes request ordering deterministic for diagnostics and prevents callers
|
|
524
|
+
* from mutating the scheduler's working set.
|
|
525
|
+
*/
|
|
526
|
+
get requestedTiles(): readonly Tile3D[] {
|
|
527
|
+
return this._requestedTiles.slice();
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
/**
|
|
531
|
+
* Returns empty tiles considered during the most recent completed traversal.
|
|
532
|
+
*
|
|
533
|
+
* Empty tiles can still be useful hierarchy placeholders, but their IDs are included in
|
|
534
|
+
* observability snapshots so applications can distinguish traversal work from render content.
|
|
535
|
+
*/
|
|
536
|
+
get emptyTiles(): readonly Tile3D[] {
|
|
537
|
+
return this._emptyTiles.slice();
|
|
538
|
+
}
|
|
539
|
+
|
|
488
540
|
/**
|
|
489
541
|
* Gets or sets the soft target, in bytes, for cached tile content not needed this frame.
|
|
490
542
|
*
|
|
@@ -630,6 +682,14 @@ export class Tileset3D {
|
|
|
630
682
|
return;
|
|
631
683
|
}
|
|
632
684
|
const preparedViewports = viewports instanceof Array ? viewports : [viewports];
|
|
685
|
+
const activeViewportIds = new Set(preparedViewports.map(viewport => viewport.id));
|
|
686
|
+
// A viewport may be removed between frames. Discard its completed state before aggregating
|
|
687
|
+
// requested and selected tiles so observability reflects only the current traversal inputs.
|
|
688
|
+
for (const frameStateId of Object.keys(this.frameStateData)) {
|
|
689
|
+
if (!activeViewportIds.has(frameStateId)) {
|
|
690
|
+
delete this.frameStateData[frameStateId];
|
|
691
|
+
}
|
|
692
|
+
}
|
|
633
693
|
|
|
634
694
|
this._cache.reset();
|
|
635
695
|
this._frameNumber++;
|
|
@@ -877,7 +937,7 @@ export class Tileset3D {
|
|
|
877
937
|
if (
|
|
878
938
|
initialMetadata?.tileset?.root &&
|
|
879
939
|
typeof initialMetadata.tileset.root.then !== 'function' &&
|
|
880
|
-
!this.source.prepareTileset
|
|
940
|
+
(!this.source.prepareTileset || this.spatialReference?.status === 'native')
|
|
881
941
|
) {
|
|
882
942
|
this.root = this._initializeTileHeaders(initialMetadata.tileset, null);
|
|
883
943
|
this._applyViewState();
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
// Copyright (c) vis.gl contributors
|
|
3
|
+
|
|
4
|
+
import type {Tileset3D} from './tileset-3d';
|
|
5
|
+
import type {Tile3D} from './tile-3d';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* A deterministic, renderer-neutral snapshot of one {@link Tileset3D} traversal.
|
|
9
|
+
*
|
|
10
|
+
* Tile IDs are sorted so snapshots can be compared across runs even when child requests complete
|
|
11
|
+
* in a different order. Counts are reported for the completed traversal frame and cache values are
|
|
12
|
+
* expressed in bytes. This is an inspection contract; it does not alter traversal or request policy.
|
|
13
|
+
*/
|
|
14
|
+
export type Tileset3DTraversalSnapshot = {
|
|
15
|
+
/** Monotonically increasing traversal frame number. */
|
|
16
|
+
frameNumber: number;
|
|
17
|
+
/** IDs selected for rendering, sorted for stable comparisons. */
|
|
18
|
+
selectedTileIds: string[];
|
|
19
|
+
/** IDs whose content was requested, sorted for stable comparisons. */
|
|
20
|
+
requestedTileIds: string[];
|
|
21
|
+
/** IDs visited as hierarchy-only or empty tiles, sorted for stable comparisons. */
|
|
22
|
+
emptyTileIds: string[];
|
|
23
|
+
/** Number of selected tiles in the completed frame. */
|
|
24
|
+
visibleTileCount: number;
|
|
25
|
+
/** Number of selected tiles with renderable content. */
|
|
26
|
+
renderableTileCount: number;
|
|
27
|
+
/** Number of tile or subtree loads currently in flight. */
|
|
28
|
+
loadingTileCount: number;
|
|
29
|
+
/** Cumulative number of tiles loaded into the runtime cache. */
|
|
30
|
+
loadedTileCount: number;
|
|
31
|
+
/** Cumulative number of failed tile loads. */
|
|
32
|
+
failedTileCount: number;
|
|
33
|
+
/** Number of tiles currently retained in the runtime cache. */
|
|
34
|
+
cachedTileCount: number;
|
|
35
|
+
/** Estimated cached content bytes. */
|
|
36
|
+
cacheBytes: number;
|
|
37
|
+
/** Active memory-adjusted maximum SSE in logical/CSS pixels. */
|
|
38
|
+
maximumScreenSpaceError: number;
|
|
39
|
+
/** Source-specific implicit subtree counters, when the source exposes them. */
|
|
40
|
+
implicitTiling?: {
|
|
41
|
+
/** Number of subtree resources requested from the source. */
|
|
42
|
+
requestedSubtrees: number;
|
|
43
|
+
/** Number of subtree resources successfully materialized. */
|
|
44
|
+
loadedSubtrees: number;
|
|
45
|
+
/** Number of requests served by the parsed-subtree cache. */
|
|
46
|
+
cacheHits: number;
|
|
47
|
+
/** Number of parsed subtrees currently retained for reuse. */
|
|
48
|
+
cachedSubtrees: number;
|
|
49
|
+
/** Number of subtree requests currently in flight. */
|
|
50
|
+
pendingSubtrees: number;
|
|
51
|
+
/** Number of runtime tile headers created from materialized subtrees. */
|
|
52
|
+
materializedTiles: number;
|
|
53
|
+
};
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Creates a stable observability snapshot from public {@link Tileset3D} state.
|
|
58
|
+
*
|
|
59
|
+
* The helper intentionally reads only public runtime fields and source diagnostics. It is safe to
|
|
60
|
+
* call from an instrumentation loop and does not retain tile or content references.
|
|
61
|
+
*
|
|
62
|
+
* @param tileset - Runtime whose most recent traversal should be inspected.
|
|
63
|
+
* @returns A serializable snapshot suitable for logs, regression fixtures, and benchmark output.
|
|
64
|
+
*/
|
|
65
|
+
export function getTileset3DTraversalSnapshot(tileset: Tileset3D): Tileset3DTraversalSnapshot {
|
|
66
|
+
const selectedTiles = tileset.selectedTiles.slice();
|
|
67
|
+
const requestedTiles = tileset.requestedTiles.slice();
|
|
68
|
+
const emptyTiles = tileset.emptyTiles.slice();
|
|
69
|
+
|
|
70
|
+
const getTileIds = (tiles: readonly Tile3D[]): string[] =>
|
|
71
|
+
tiles.map(tile => String(tile.id)).sort();
|
|
72
|
+
|
|
73
|
+
const getStatCount = (name: string): number => {
|
|
74
|
+
const count = tileset.stats.get(name).count;
|
|
75
|
+
return typeof count === 'number' && Number.isFinite(count) ? count : 0;
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
const implicitTilingSource = tileset.source as Tileset3D['source'] & {
|
|
79
|
+
getImplicitTilingStats?: () => Tileset3DTraversalSnapshot['implicitTiling'];
|
|
80
|
+
};
|
|
81
|
+
const implicitTiling = implicitTilingSource.getImplicitTilingStats?.();
|
|
82
|
+
|
|
83
|
+
return {
|
|
84
|
+
frameNumber: tileset.frameNumber,
|
|
85
|
+
selectedTileIds: getTileIds(selectedTiles),
|
|
86
|
+
requestedTileIds: getTileIds(requestedTiles),
|
|
87
|
+
emptyTileIds: getTileIds(emptyTiles),
|
|
88
|
+
visibleTileCount: selectedTiles.length,
|
|
89
|
+
renderableTileCount: selectedTiles.filter(
|
|
90
|
+
tile => tile.contentAvailable && Boolean(tile.content)
|
|
91
|
+
).length,
|
|
92
|
+
loadingTileCount: getStatCount('Tiles Loading'),
|
|
93
|
+
loadedTileCount: getStatCount('Tiles Loaded'),
|
|
94
|
+
failedTileCount: getStatCount('Failed Tile Loads'),
|
|
95
|
+
cachedTileCount: getStatCount('Tiles In Memory'),
|
|
96
|
+
cacheBytes: tileset.gpuMemoryUsageInBytes,
|
|
97
|
+
maximumScreenSpaceError: tileset.memoryAdjustedScreenSpaceError,
|
|
98
|
+
...(implicitTiling ? {implicitTiling} : {})
|
|
99
|
+
};
|
|
100
|
+
}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// SPDX-License-Identifier: MIT
|
|
3
3
|
// Copyright (c) vis.gl contributors
|
|
4
4
|
|
|
5
|
-
import type {CoreAPI,
|
|
5
|
+
import type {CoreAPI, Loader, LoaderOptions} from '@loaders.gl/loader-utils';
|
|
6
6
|
import type {Vector3} from '@math.gl/core';
|
|
7
7
|
import type {TilesetTraverser, TilesetTraverserProps} from './tileset-traverser';
|
|
8
8
|
import type {Tile3D} from './tile-3d';
|
|
@@ -10,6 +10,7 @@ import type {Tileset3D} from './tileset-3d';
|
|
|
10
10
|
import type {FrameState} from '../helpers/frame-state';
|
|
11
11
|
import type {TILESET_TYPE} from '../../constants';
|
|
12
12
|
import type {TilesetSpatialReference} from '../../spatial/spatial-types';
|
|
13
|
+
import type {Tile3DContent} from './tile-3d-contracts';
|
|
13
14
|
|
|
14
15
|
/**
|
|
15
16
|
* Parsed top-level tileset payload consumed by {@link Tileset3D} or a {@link Tileset3DSource}.
|
|
@@ -24,18 +25,14 @@ export type TilesetSourceResolver = {
|
|
|
24
25
|
/**
|
|
25
26
|
* Loads and parses the root metadata resource for a source.
|
|
26
27
|
*/
|
|
27
|
-
loadRoot<DataT>(
|
|
28
|
-
url: string,
|
|
29
|
-
loader: LoaderWithParser<DataT>,
|
|
30
|
-
loadOptions: LoaderOptions
|
|
31
|
-
): Promise<DataT>;
|
|
28
|
+
loadRoot<DataT>(url: string, loader: Loader<DataT>, loadOptions: LoaderOptions): Promise<DataT>;
|
|
32
29
|
|
|
33
30
|
/**
|
|
34
31
|
* Loads and parses an arbitrary source-relative resource.
|
|
35
32
|
*/
|
|
36
33
|
loadResource<DataT>(
|
|
37
34
|
url: string,
|
|
38
|
-
loader:
|
|
35
|
+
loader: Loader<DataT>,
|
|
39
36
|
loadOptions: LoaderOptions
|
|
40
37
|
): Promise<DataT>;
|
|
41
38
|
};
|
|
@@ -47,7 +44,7 @@ export type TilesetSourceRequest = {
|
|
|
47
44
|
/** Absolute root metadata URL. */
|
|
48
45
|
url: string;
|
|
49
46
|
/** Loader used to fetch and parse the root metadata. */
|
|
50
|
-
loader:
|
|
47
|
+
loader: Loader;
|
|
51
48
|
/** Optional base path override for relative resources. */
|
|
52
49
|
basePath?: string;
|
|
53
50
|
/** Optional resource-loading strategy used for archive-backed datasets. */
|
|
@@ -72,6 +69,8 @@ export type TileContentLoadResult = {
|
|
|
72
69
|
loaded: boolean;
|
|
73
70
|
/** All payloads loaded for a tile, in source order. */
|
|
74
71
|
contents?: any[];
|
|
72
|
+
/** Optional normalized renderer-neutral descriptors for each content entry. */
|
|
73
|
+
contentEntries?: Tile3DContent[];
|
|
75
74
|
/** Nested tileset metadata to install below the loaded tile, if any. */
|
|
76
75
|
nestedTileset?: TilesetJSON;
|
|
77
76
|
/** Nested tilesets found among multiple content payloads. */
|
|
@@ -119,8 +118,8 @@ export type TilesetSourceViewState = {
|
|
|
119
118
|
export type TilesetSourceMetadata = {
|
|
120
119
|
/** Format discriminator. */
|
|
121
120
|
type: TILESET_TYPE;
|
|
122
|
-
/**
|
|
123
|
-
loader:
|
|
121
|
+
/** Primary format loader used for metadata and default content requests. */
|
|
122
|
+
loader: Loader;
|
|
124
123
|
/** Absolute root metadata URL. */
|
|
125
124
|
url: string;
|
|
126
125
|
/** Base path used for relative resource resolution. */
|
|
@@ -143,8 +142,8 @@ export type TilesetSourceMetadata = {
|
|
|
143
142
|
export interface Tileset3DSource {
|
|
144
143
|
/** Tileset format discriminator. */
|
|
145
144
|
readonly type: TILESET_TYPE;
|
|
146
|
-
/**
|
|
147
|
-
readonly loader:
|
|
145
|
+
/** Primary format loader used for metadata and default content requests. */
|
|
146
|
+
readonly loader: Loader;
|
|
148
147
|
/** Absolute URL of the root tileset resource. */
|
|
149
148
|
readonly url: string;
|
|
150
149
|
/** Base directory used for relative resource resolution. */
|