@loaders.gl/tiles 5.0.0-alpha.6 → 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 +5160 -2896
- package/dist/dist.min.js +4 -2
- package/dist/index.cjs +1509 -454
- 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/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 +147 -18
- package/dist/tileset-3d/common/tile-3d.js.map +1 -1
- package/dist/tileset-3d/common/tileset-3d.d.ts +14 -0
- package/dist/tileset-3d/common/tileset-3d.d.ts.map +1 -1
- package/dist/tileset-3d/common/tileset-3d.js +27 -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 +3 -0
- 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.map +1 -1
- package/dist/tileset-3d/common/tileset-traverser.js +4 -3
- 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 +32 -0
- 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 +5 -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 +7 -4
- package/dist/tileset-3d/format-3d-tiles/tileset-3d-traverser.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 +13 -12
- 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/tile-3d-contracts.ts +115 -0
- package/src/tileset-3d/common/tile-3d.ts +172 -18
- package/src/tileset-3d/common/tileset-3d.ts +29 -1
- package/src/tileset-3d/common/tileset-observability.ts +100 -0
- package/src/tileset-3d/common/tileset-source.ts +3 -0
- package/src/tileset-3d/common/tileset-traverser.ts +4 -3
- package/src/tileset-3d/format-3d-tiles/implicit-tiling.ts +481 -34
- package/src/tileset-3d/format-3d-tiles/tiles-3d-source.ts +387 -14
- package/src/tileset-3d/format-3d-tiles/tileset-3d-traverser.ts +7 -4
- 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
|
@@ -3,9 +3,15 @@
|
|
|
3
3
|
// Copyright vis.gl contributors
|
|
4
4
|
|
|
5
5
|
import {LOD_METRIC_TYPE, TILE_REFINEMENT, TILE_TYPE} from '../../constants';
|
|
6
|
+
// TypeScript's legacy `node` resolution does not inspect package `exports`, but this public
|
|
7
|
+
// subpath is resolved by the package at runtime and avoids loading unrelated DGGS decoders.
|
|
8
|
+
// @ts-expect-error Conditional package subpath exports require a modern module resolver.
|
|
9
|
+
import {getS2DescendantIndex, getS2IndexFromToken, getS2TokenFromIndex} from '@math.gl/dggs/s2';
|
|
10
|
+
import {convertS2BoundingVolumeToOBB, type S2VolumeInfo} from '../helpers/s2-bounding-volume';
|
|
6
11
|
|
|
7
12
|
const QUADTREE_CHILD_COUNT = 4;
|
|
8
13
|
const OCTREE_CHILD_COUNT = 8;
|
|
14
|
+
const AVAILABILITY_RANK_CACHE = new WeakMap<ImplicitAvailability, Uint32Array>();
|
|
9
15
|
|
|
10
16
|
/** Global coordinates of a tile in an implicit quadtree or octree. */
|
|
11
17
|
export type ImplicitTileCoordinates = {
|
|
@@ -29,6 +35,8 @@ export type ImplicitTilingDescriptor = {
|
|
|
29
35
|
contentHeader?: Record<string, any>;
|
|
30
36
|
/** Metadata inherited by every implicit content stream, in source order. */
|
|
31
37
|
contentHeaders?: Array<Record<string, any>>;
|
|
38
|
+
/** Whether generated headers preserve the standard `contents` source representation. */
|
|
39
|
+
useCanonicalContents?: boolean;
|
|
32
40
|
/** Absolute template URL for subtree availability files. */
|
|
33
41
|
subtreesUrlTemplate: string;
|
|
34
42
|
/** Spatial subdivision used by the hierarchy. */
|
|
@@ -45,6 +53,28 @@ export type ImplicitTilingDescriptor = {
|
|
|
45
53
|
rootLodMetricValue: number;
|
|
46
54
|
/** Bounding volume of the complete implicit hierarchy. */
|
|
47
55
|
rootBoundingVolume: Record<string, any>;
|
|
56
|
+
/** Whether runtime transforms scale geometric error; false for draft 2.0. */
|
|
57
|
+
scaleGeometricError?: boolean;
|
|
58
|
+
/** Package files that may satisfy concrete content and subtree template references. */
|
|
59
|
+
resourceFiles?: ImplicitPackageFile[];
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
/** Structured-cloneable file retained from a draft glTF package. */
|
|
63
|
+
export type ImplicitPackageFile = {
|
|
64
|
+
name?: string;
|
|
65
|
+
mimeType: string;
|
|
66
|
+
uri?: string;
|
|
67
|
+
originalUri?: string;
|
|
68
|
+
data?: ArrayBuffer;
|
|
69
|
+
byteOffset: number;
|
|
70
|
+
byteLength: number;
|
|
71
|
+
bufferUri?: string;
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
/** Package reference attached to a generated content or subtree URL. */
|
|
75
|
+
export type ImplicitPackageResource = {
|
|
76
|
+
fileIndex: number;
|
|
77
|
+
files: ImplicitPackageFile[];
|
|
48
78
|
};
|
|
49
79
|
|
|
50
80
|
/** Lazy pointer from a runtime tile to the subtree that defines it and its descendants. */
|
|
@@ -55,6 +85,8 @@ export type ImplicitSubtreeReference = {
|
|
|
55
85
|
subtreeUrl: string;
|
|
56
86
|
/** Serializable hierarchy description used to materialize the subtree. */
|
|
57
87
|
descriptor: ImplicitTilingDescriptor;
|
|
88
|
+
/** Embedded package resource matching {@link subtreeUrl}, when available. */
|
|
89
|
+
resource?: ImplicitPackageResource;
|
|
58
90
|
};
|
|
59
91
|
|
|
60
92
|
/** Constant or bitstream-backed availability declaration from a parsed subtree. */
|
|
@@ -84,6 +116,24 @@ export type ParsedImplicitSubtree = {
|
|
|
84
116
|
contentMetadata?: unknown;
|
|
85
117
|
/** Metadata entity attached to the subtree itself. */
|
|
86
118
|
subtreeMetadata?: unknown;
|
|
119
|
+
/** Decoded standard attribute accessors keyed by tile semantic. */
|
|
120
|
+
tileAttributes?: Record<string, ArrayLike<number>>;
|
|
121
|
+
/** Decoded standard attribute accessors keyed by content semantic. */
|
|
122
|
+
contentAttributes?: Record<string, ArrayLike<number>>;
|
|
123
|
+
/** Index of the property table assigned to available tiles. */
|
|
124
|
+
tileProperties?: unknown;
|
|
125
|
+
/** Index of the property table assigned to available contents. */
|
|
126
|
+
contentProperties?: unknown;
|
|
127
|
+
/** Fully decoded property rows for available tiles, in availability order. */
|
|
128
|
+
tilePropertyRows?: Array<Record<string, unknown>>;
|
|
129
|
+
/** Fully decoded property rows for available contents, in availability order. */
|
|
130
|
+
contentPropertyRows?: Array<Record<string, unknown>>;
|
|
131
|
+
/** Tile property rows filtered to values permitted in URI templates. */
|
|
132
|
+
tileTemplatePropertyRows?: Array<Record<string, unknown>>;
|
|
133
|
+
/** Content property rows filtered to values permitted in URI templates. */
|
|
134
|
+
contentTemplatePropertyRows?: Array<Record<string, unknown>>;
|
|
135
|
+
/** Files retained by a glTF subtree package for later content/subtree resolution. */
|
|
136
|
+
resourceFiles?: ImplicitPackageFile[];
|
|
87
137
|
};
|
|
88
138
|
|
|
89
139
|
/** Runtime header produced for an available implicit tile. */
|
|
@@ -108,6 +158,10 @@ export type ImplicitSubtreeMetadata = {
|
|
|
108
158
|
contentMetadata?: unknown;
|
|
109
159
|
/** Subtree-level metadata entity. */
|
|
110
160
|
subtreeMetadata?: unknown;
|
|
161
|
+
/** Decoded property-table row for this tile. */
|
|
162
|
+
tileProperties?: Record<string, unknown>;
|
|
163
|
+
/** Decoded property-table row for this tile's available content. */
|
|
164
|
+
contentProperties?: Record<string, unknown>;
|
|
111
165
|
};
|
|
112
166
|
|
|
113
167
|
/** Result of materializing exactly one subtree resource. */
|
|
@@ -134,7 +188,11 @@ export function createImplicitSubtreeReference(
|
|
|
134
188
|
return {
|
|
135
189
|
coordinates,
|
|
136
190
|
subtreeUrl: replaceImplicitUrlTemplate(descriptor.subtreesUrlTemplate, coordinates),
|
|
137
|
-
descriptor
|
|
191
|
+
descriptor,
|
|
192
|
+
resource: findImplicitPackageResource(
|
|
193
|
+
descriptor.resourceFiles,
|
|
194
|
+
replaceImplicitUrlTemplate(descriptor.subtreesUrlTemplate, coordinates)
|
|
195
|
+
)
|
|
138
196
|
};
|
|
139
197
|
}
|
|
140
198
|
|
|
@@ -156,6 +214,7 @@ export function materializeImplicitSubtree(
|
|
|
156
214
|
): MaterializedImplicitSubtree {
|
|
157
215
|
const {descriptor} = reference;
|
|
158
216
|
validateImplicitDescriptor(descriptor);
|
|
217
|
+
validateImplicitSubtree(subtree, descriptor);
|
|
159
218
|
|
|
160
219
|
const counters = {tileCount: 0, childSubtreeCount: 0};
|
|
161
220
|
const root = materializeAvailableTile(
|
|
@@ -175,6 +234,101 @@ export function materializeImplicitSubtree(
|
|
|
175
234
|
return {root, ...counters};
|
|
176
235
|
}
|
|
177
236
|
|
|
237
|
+
/** Validates availability lengths and tightly packed draft attribute/property row counts. */
|
|
238
|
+
function validateImplicitSubtree(
|
|
239
|
+
subtree: ParsedImplicitSubtree,
|
|
240
|
+
descriptor: ImplicitTilingDescriptor
|
|
241
|
+
): void {
|
|
242
|
+
const childCount = getImplicitChildCount(descriptor.subdivisionScheme);
|
|
243
|
+
const tileElementCount = (childCount ** descriptor.subtreeLevels - 1) / (childCount - 1);
|
|
244
|
+
const childSubtreeElementCount = childCount ** descriptor.subtreeLevels;
|
|
245
|
+
validateAvailabilityLength(subtree.tileAvailability, tileElementCount, 'tile');
|
|
246
|
+
const contentAvailabilityStreams = Array.isArray(subtree.contentAvailability)
|
|
247
|
+
? subtree.contentAvailability
|
|
248
|
+
: subtree.contentAvailability
|
|
249
|
+
? [subtree.contentAvailability]
|
|
250
|
+
: [];
|
|
251
|
+
for (const contentAvailability of contentAvailabilityStreams) {
|
|
252
|
+
validateAvailabilityLength(contentAvailability, tileElementCount, 'content');
|
|
253
|
+
}
|
|
254
|
+
validateAvailabilityLength(
|
|
255
|
+
subtree.childSubtreeAvailability,
|
|
256
|
+
childSubtreeElementCount,
|
|
257
|
+
'child subtree'
|
|
258
|
+
);
|
|
259
|
+
const availableTileCount = countAvailableElements(subtree.tileAvailability, tileElementCount);
|
|
260
|
+
const availableContentCount = contentAvailabilityStreams[0]
|
|
261
|
+
? countAvailableElements(contentAvailabilityStreams[0], tileElementCount)
|
|
262
|
+
: 0;
|
|
263
|
+
validateAttributeLengths(subtree.tileAttributes, availableTileCount, 'tile');
|
|
264
|
+
validateAttributeLengths(subtree.contentAttributes, availableContentCount, 'content');
|
|
265
|
+
if (subtree.tilePropertyRows && subtree.tilePropertyRows.length !== availableTileCount) {
|
|
266
|
+
throw new Error('3DTILES_subtree: tile property-table count does not match availability');
|
|
267
|
+
}
|
|
268
|
+
if (subtree.contentPropertyRows && subtree.contentPropertyRows.length !== availableContentCount) {
|
|
269
|
+
throw new Error('3DTILES_subtree: content property-table count does not match availability');
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/** Verifies that a bitstream contains every bit required by its implicit subtree. */
|
|
274
|
+
function validateAvailabilityLength(
|
|
275
|
+
availability: ImplicitAvailability,
|
|
276
|
+
elementCount: number,
|
|
277
|
+
label: string
|
|
278
|
+
): void {
|
|
279
|
+
if (availability.constant === 0 || availability.constant === 1) {
|
|
280
|
+
return;
|
|
281
|
+
}
|
|
282
|
+
if (
|
|
283
|
+
!availability.explicitBitstream ||
|
|
284
|
+
availability.explicitBitstream.byteLength * 8 < elementCount
|
|
285
|
+
) {
|
|
286
|
+
throw new Error(`3DTILES_subtree: ${label} availability bitstream is too short`);
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
/** Counts available elements within the meaningful, unpadded part of an availability stream. */
|
|
291
|
+
function countAvailableElements(availability: ImplicitAvailability, elementCount: number): number {
|
|
292
|
+
if (availability.constant === 1) {
|
|
293
|
+
return elementCount;
|
|
294
|
+
}
|
|
295
|
+
if (availability.constant === 0) {
|
|
296
|
+
return 0;
|
|
297
|
+
}
|
|
298
|
+
let availableCount = 0;
|
|
299
|
+
for (let index = 0; index < elementCount; index++) {
|
|
300
|
+
if (getAvailabilityValue(availability, index)) {
|
|
301
|
+
availableCount++;
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
return availableCount;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
/** Validates standard subtree attribute accessor counts after availability expansion. */
|
|
308
|
+
function validateAttributeLengths(
|
|
309
|
+
attributes: Record<string, ArrayLike<number>> | undefined,
|
|
310
|
+
availableCount: number,
|
|
311
|
+
label: string
|
|
312
|
+
): void {
|
|
313
|
+
const componentCounts: Record<string, number> = {
|
|
314
|
+
TILE_BOUNDING_BOX: 16,
|
|
315
|
+
TILE_BOUNDING_SPHERE: 4,
|
|
316
|
+
TILE_GEOMETRIC_ERROR: 1,
|
|
317
|
+
TILE_REFINE: 1,
|
|
318
|
+
TILE_TRANSFORM: 16,
|
|
319
|
+
CONTENT_BOUNDING_BOX: 16,
|
|
320
|
+
CONTENT_BOUNDING_SPHERE: 4
|
|
321
|
+
};
|
|
322
|
+
for (const [semantic, values] of Object.entries(attributes || {})) {
|
|
323
|
+
const componentCount = componentCounts[semantic];
|
|
324
|
+
if (componentCount && values.length !== availableCount * componentCount) {
|
|
325
|
+
throw new Error(
|
|
326
|
+
`3DTILES_subtree: ${label} attribute ${semantic} count does not match availability`
|
|
327
|
+
);
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
|
|
178
332
|
/**
|
|
179
333
|
* Replaces implicit coordinate placeholders in a content or subtree URL.
|
|
180
334
|
*
|
|
@@ -184,17 +338,22 @@ export function materializeImplicitSubtree(
|
|
|
184
338
|
*/
|
|
185
339
|
export function replaceImplicitUrlTemplate(
|
|
186
340
|
templateUrl: string,
|
|
187
|
-
coordinates: ImplicitTileCoordinates
|
|
341
|
+
coordinates: ImplicitTileCoordinates,
|
|
342
|
+
properties: Record<string, unknown> = {}
|
|
188
343
|
): string {
|
|
189
|
-
const values: Record<string,
|
|
344
|
+
const values: Record<string, unknown> = {
|
|
190
345
|
level: coordinates.level,
|
|
191
346
|
x: coordinates.x,
|
|
192
347
|
y: coordinates.y,
|
|
193
|
-
z: coordinates.z
|
|
348
|
+
z: coordinates.z,
|
|
349
|
+
...properties
|
|
194
350
|
};
|
|
195
|
-
return templateUrl.replace(/{(
|
|
196
|
-
|
|
197
|
-
|
|
351
|
+
return templateUrl.replace(/{([^{}]+)}/g, (match, propertyName: string) => {
|
|
352
|
+
const value = Object.hasOwn(values, propertyName)
|
|
353
|
+
? values[propertyName]
|
|
354
|
+
: values[propertyName.toLowerCase()];
|
|
355
|
+
return value === undefined ? match : String(value);
|
|
356
|
+
});
|
|
198
357
|
}
|
|
199
358
|
|
|
200
359
|
/**
|
|
@@ -273,6 +432,7 @@ function materializeAvailableTile(
|
|
|
273
432
|
descriptor,
|
|
274
433
|
reference,
|
|
275
434
|
globalCoordinates,
|
|
435
|
+
availabilityIndex,
|
|
276
436
|
contentAvailability,
|
|
277
437
|
children
|
|
278
438
|
);
|
|
@@ -290,7 +450,9 @@ function createLazyImplicitTileHeader(
|
|
|
290
450
|
descriptor: ImplicitTilingDescriptor,
|
|
291
451
|
coordinates: ImplicitTileCoordinates
|
|
292
452
|
): ImplicitTileHeader {
|
|
293
|
-
const
|
|
453
|
+
const resourceFiles = mergeImplicitPackageFiles(descriptor.resourceFiles, subtree.resourceFiles);
|
|
454
|
+
const childDescriptor = resourceFiles ? {...descriptor, resourceFiles} : descriptor;
|
|
455
|
+
const reference = createImplicitSubtreeReference(childDescriptor, coordinates);
|
|
294
456
|
return {
|
|
295
457
|
id: getImplicitTileId(reference, coordinates),
|
|
296
458
|
children: [],
|
|
@@ -305,10 +467,175 @@ function createLazyImplicitTileHeader(
|
|
|
305
467
|
lodMetricValue: descriptor.rootLodMetricValue / 2 ** coordinates.level,
|
|
306
468
|
refine: descriptor.refine,
|
|
307
469
|
type: TILE_TYPE.EMPTY,
|
|
308
|
-
implicitMetadata: getImplicitSubtreeMetadata(subtree)
|
|
470
|
+
implicitMetadata: getImplicitSubtreeMetadata(subtree),
|
|
471
|
+
_scaleGeometricError: descriptor.scaleGeometricError !== false
|
|
309
472
|
};
|
|
310
473
|
}
|
|
311
474
|
|
|
475
|
+
type ImplicitAttributeOverrides = {
|
|
476
|
+
boundingVolume?: Record<string, any>;
|
|
477
|
+
geometricError?: number;
|
|
478
|
+
refine?: TILE_REFINEMENT;
|
|
479
|
+
transform?: number[];
|
|
480
|
+
};
|
|
481
|
+
|
|
482
|
+
/** Decodes supported standard subtree attributes for one tightly packed available row. */
|
|
483
|
+
function getSubtreeAttributeOverrides(
|
|
484
|
+
attributes: Record<string, ArrayLike<number>> | undefined,
|
|
485
|
+
rowIndex: number,
|
|
486
|
+
scope: 'tile' | 'content'
|
|
487
|
+
): ImplicitAttributeOverrides {
|
|
488
|
+
if (!attributes || rowIndex < 0) {
|
|
489
|
+
return {};
|
|
490
|
+
}
|
|
491
|
+
const box = getAttributeComponents(
|
|
492
|
+
attributes[scope === 'tile' ? 'TILE_BOUNDING_BOX' : 'CONTENT_BOUNDING_BOX'],
|
|
493
|
+
rowIndex,
|
|
494
|
+
16
|
|
495
|
+
);
|
|
496
|
+
const sphere = getAttributeComponents(
|
|
497
|
+
attributes[scope === 'tile' ? 'TILE_BOUNDING_SPHERE' : 'CONTENT_BOUNDING_SPHERE'],
|
|
498
|
+
rowIndex,
|
|
499
|
+
4
|
|
500
|
+
);
|
|
501
|
+
const boundingVolume = box
|
|
502
|
+
? {
|
|
503
|
+
box: [
|
|
504
|
+
box[12],
|
|
505
|
+
box[13],
|
|
506
|
+
box[14],
|
|
507
|
+
box[0] / 2,
|
|
508
|
+
box[1] / 2,
|
|
509
|
+
box[2] / 2,
|
|
510
|
+
box[4] / 2,
|
|
511
|
+
box[5] / 2,
|
|
512
|
+
box[6] / 2,
|
|
513
|
+
box[8] / 2,
|
|
514
|
+
box[9] / 2,
|
|
515
|
+
box[10] / 2
|
|
516
|
+
]
|
|
517
|
+
}
|
|
518
|
+
: sphere
|
|
519
|
+
? {sphere}
|
|
520
|
+
: undefined;
|
|
521
|
+
if (scope === 'content') {
|
|
522
|
+
return {boundingVolume};
|
|
523
|
+
}
|
|
524
|
+
const geometricError = getAttributeComponents(attributes.TILE_GEOMETRIC_ERROR, rowIndex, 1)?.[0];
|
|
525
|
+
if (geometricError !== undefined && (!Number.isFinite(geometricError) || geometricError < 0)) {
|
|
526
|
+
throw new Error('3DTILES_subtree: TILE_GEOMETRIC_ERROR must be nonnegative');
|
|
527
|
+
}
|
|
528
|
+
const refineValue = getAttributeComponents(attributes.TILE_REFINE, rowIndex, 1)?.[0];
|
|
529
|
+
if (refineValue !== undefined && refineValue !== 0 && refineValue !== 1) {
|
|
530
|
+
throw new Error('3DTILES_subtree: TILE_REFINE must be 0 or 1');
|
|
531
|
+
}
|
|
532
|
+
return {
|
|
533
|
+
boundingVolume,
|
|
534
|
+
geometricError,
|
|
535
|
+
refine:
|
|
536
|
+
refineValue === undefined
|
|
537
|
+
? undefined
|
|
538
|
+
: refineValue === 0
|
|
539
|
+
? TILE_REFINEMENT.ADD
|
|
540
|
+
: TILE_REFINEMENT.REPLACE,
|
|
541
|
+
transform: getAttributeComponents(attributes.TILE_TRANSFORM, rowIndex, 16)
|
|
542
|
+
};
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
/** Copies one logical accessor element and validates its available-row count. */
|
|
546
|
+
function getAttributeComponents(
|
|
547
|
+
values: ArrayLike<number> | undefined,
|
|
548
|
+
rowIndex: number,
|
|
549
|
+
componentCount: number
|
|
550
|
+
): number[] | undefined {
|
|
551
|
+
if (!values) {
|
|
552
|
+
return undefined;
|
|
553
|
+
}
|
|
554
|
+
const offset = rowIndex * componentCount;
|
|
555
|
+
if (offset + componentCount > values.length) {
|
|
556
|
+
throw new Error('3DTILES_subtree: attribute accessor is shorter than availability');
|
|
557
|
+
}
|
|
558
|
+
return Array.from({length: componentCount}, (_unused, componentIndex) =>
|
|
559
|
+
Number(values[offset + componentIndex])
|
|
560
|
+
);
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
/** Returns the tightly packed property/attribute row index for an available element. */
|
|
564
|
+
function getAvailabilityRank(availability: ImplicitAvailability, index: number): number {
|
|
565
|
+
if (availability.constant === 1) {
|
|
566
|
+
return index;
|
|
567
|
+
}
|
|
568
|
+
if (availability.constant === 0) {
|
|
569
|
+
return 0;
|
|
570
|
+
}
|
|
571
|
+
let ranks = AVAILABILITY_RANK_CACHE.get(availability);
|
|
572
|
+
if (!ranks) {
|
|
573
|
+
const bitCount = (availability.explicitBitstream?.byteLength || 0) * 8;
|
|
574
|
+
ranks = new Uint32Array(bitCount + 1);
|
|
575
|
+
for (let candidateIndex = 0; candidateIndex < bitCount; candidateIndex++) {
|
|
576
|
+
ranks[candidateIndex + 1] =
|
|
577
|
+
ranks[candidateIndex] + Number(getAvailabilityValue(availability, candidateIndex));
|
|
578
|
+
}
|
|
579
|
+
AVAILABILITY_RANK_CACHE.set(availability, ranks);
|
|
580
|
+
}
|
|
581
|
+
return ranks[index];
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
/** Locates a concrete generated URI in a retained glTF package. */
|
|
585
|
+
function findImplicitPackageResource(
|
|
586
|
+
files: ImplicitPackageFile[] | undefined,
|
|
587
|
+
resourceUrl: string
|
|
588
|
+
): ImplicitPackageResource | undefined {
|
|
589
|
+
if (!files?.length) {
|
|
590
|
+
return undefined;
|
|
591
|
+
}
|
|
592
|
+
const urlWithoutQuery = resourceUrl.split('?')[0];
|
|
593
|
+
const packageRelativePath = /^gltf-package:\/\/[^/]+\/(.*)$/.exec(urlWithoutQuery)?.[1];
|
|
594
|
+
const exactFileIndex = files.findIndex(file => {
|
|
595
|
+
const reference = file.originalUri || file.name;
|
|
596
|
+
return (
|
|
597
|
+
file.uri === urlWithoutQuery ||
|
|
598
|
+
Boolean(reference && (urlWithoutQuery === reference || packageRelativePath === reference))
|
|
599
|
+
);
|
|
600
|
+
});
|
|
601
|
+
if (exactFileIndex >= 0) {
|
|
602
|
+
return {fileIndex: exactFileIndex, files};
|
|
603
|
+
}
|
|
604
|
+
const fileIndex = files.findIndex(file => {
|
|
605
|
+
const reference = file.originalUri || file.name;
|
|
606
|
+
return Boolean(
|
|
607
|
+
reference &&
|
|
608
|
+
(urlWithoutQuery.endsWith(`/${reference}`) || urlWithoutQuery.endsWith(`:${reference}`))
|
|
609
|
+
);
|
|
610
|
+
});
|
|
611
|
+
return fileIndex < 0 ? undefined : {fileIndex, files};
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
/** Layers files declared by a child package over inherited parent-package files. */
|
|
615
|
+
function mergeImplicitPackageFiles(
|
|
616
|
+
parentFiles: ImplicitPackageFile[] | undefined,
|
|
617
|
+
childFiles: ImplicitPackageFile[] | undefined
|
|
618
|
+
): ImplicitPackageFile[] | undefined {
|
|
619
|
+
if (!childFiles?.length) {
|
|
620
|
+
return parentFiles;
|
|
621
|
+
}
|
|
622
|
+
if (!parentFiles?.length) {
|
|
623
|
+
return childFiles;
|
|
624
|
+
}
|
|
625
|
+
const childReferences = new Set(
|
|
626
|
+
childFiles.flatMap(file => [file.uri, file.originalUri, file.name].filter(Boolean) as string[])
|
|
627
|
+
);
|
|
628
|
+
return [
|
|
629
|
+
...childFiles,
|
|
630
|
+
...parentFiles.filter(
|
|
631
|
+
file =>
|
|
632
|
+
![file.uri, file.originalUri, file.name].some(
|
|
633
|
+
reference => reference && childReferences.has(reference)
|
|
634
|
+
)
|
|
635
|
+
)
|
|
636
|
+
];
|
|
637
|
+
}
|
|
638
|
+
|
|
312
639
|
/**
|
|
313
640
|
* Formats an available implicit tile as a runtime header.
|
|
314
641
|
*
|
|
@@ -324,9 +651,36 @@ function formatImplicitTileHeader(
|
|
|
324
651
|
descriptor: ImplicitTilingDescriptor,
|
|
325
652
|
reference: ImplicitSubtreeReference,
|
|
326
653
|
coordinates: ImplicitTileCoordinates,
|
|
654
|
+
availabilityIndex: number,
|
|
327
655
|
contentAvailability: boolean[],
|
|
328
656
|
children: ImplicitTileHeader[]
|
|
329
657
|
): ImplicitTileHeader {
|
|
658
|
+
const tilePropertyIndex = getAvailabilityRank(subtree.tileAvailability, availabilityIndex);
|
|
659
|
+
const tileProperties = subtree.tilePropertyRows?.[tilePropertyIndex];
|
|
660
|
+
const primaryContentAvailability = Array.isArray(subtree.contentAvailability)
|
|
661
|
+
? subtree.contentAvailability[0]
|
|
662
|
+
: subtree.contentAvailability;
|
|
663
|
+
const contentPropertyIndex =
|
|
664
|
+
primaryContentAvailability &&
|
|
665
|
+
getAvailabilityValue(primaryContentAvailability, availabilityIndex)
|
|
666
|
+
? getAvailabilityRank(primaryContentAvailability, availabilityIndex)
|
|
667
|
+
: -1;
|
|
668
|
+
const contentProperties = subtree.contentPropertyRows?.[contentPropertyIndex];
|
|
669
|
+
const tileTemplateProperties =
|
|
670
|
+
subtree.tileTemplatePropertyRows?.[tilePropertyIndex] || tileProperties;
|
|
671
|
+
const contentTemplateProperties =
|
|
672
|
+
subtree.contentTemplatePropertyRows?.[contentPropertyIndex] || contentProperties;
|
|
673
|
+
const templateProperties = {...tileTemplateProperties, ...contentTemplateProperties};
|
|
674
|
+
const tileAttributes = getSubtreeAttributeOverrides(
|
|
675
|
+
subtree.tileAttributes,
|
|
676
|
+
tilePropertyIndex,
|
|
677
|
+
'tile'
|
|
678
|
+
);
|
|
679
|
+
const contentAttributes = getSubtreeAttributeOverrides(
|
|
680
|
+
subtree.contentAttributes,
|
|
681
|
+
contentPropertyIndex,
|
|
682
|
+
'content'
|
|
683
|
+
);
|
|
330
684
|
const contentUrlTemplates = descriptor.contentUrlTemplates?.length
|
|
331
685
|
? descriptor.contentUrlTemplates
|
|
332
686
|
: [descriptor.contentUrlTemplate];
|
|
@@ -335,39 +689,57 @@ function formatImplicitTileHeader(
|
|
|
335
689
|
if (!contentAvailability[contentIndex] || !templateUrl) {
|
|
336
690
|
return null;
|
|
337
691
|
}
|
|
692
|
+
const contentUrl = replaceImplicitUrlTemplate(templateUrl, coordinates, templateProperties);
|
|
693
|
+
if (/{[^{}]+}/.test(contentUrl)) {
|
|
694
|
+
throw new Error(`Implicit content URI has unresolved template values: ${contentUrl}`);
|
|
695
|
+
}
|
|
338
696
|
return {
|
|
339
697
|
contentIndex,
|
|
340
|
-
contentUrl
|
|
698
|
+
contentUrl
|
|
341
699
|
};
|
|
342
700
|
})
|
|
343
701
|
.filter((entry): entry is {contentIndex: number; contentUrl: string} => Boolean(entry));
|
|
344
702
|
const availableContentUrls = contentEntries.map(entry => entry.contentUrl);
|
|
345
|
-
const
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
703
|
+
const resourceFiles = mergeImplicitPackageFiles(descriptor.resourceFiles, subtree.resourceFiles);
|
|
704
|
+
const content = contentEntries.map(({contentIndex, contentUrl}) => {
|
|
705
|
+
const packageResource = findImplicitPackageResource(resourceFiles, contentUrl);
|
|
706
|
+
return {
|
|
707
|
+
...(descriptor.contentHeaders?.[contentIndex] ||
|
|
708
|
+
(contentIndex === 0 ? descriptor.contentHeader : {}) ||
|
|
709
|
+
{}),
|
|
710
|
+
...contentAttributes,
|
|
711
|
+
uri: contentUrl,
|
|
712
|
+
...(contentProperties ? {metadata: {properties: contentProperties}} : {}),
|
|
713
|
+
...(packageResource ? {_resource: packageResource} : {})
|
|
714
|
+
};
|
|
715
|
+
});
|
|
351
716
|
const contentUrl = availableContentUrls[0];
|
|
352
|
-
const
|
|
717
|
+
const computedLodMetricValue = descriptor.rootLodMetricValue / 2 ** coordinates.level;
|
|
718
|
+
const lodMetricValue = tileAttributes.geometricError ?? computedLodMetricValue;
|
|
719
|
+
const computedBoundingVolume = calculateImplicitBoundingVolume(
|
|
720
|
+
descriptor.rootBoundingVolume,
|
|
721
|
+
coordinates,
|
|
722
|
+
descriptor.subdivisionScheme
|
|
723
|
+
);
|
|
353
724
|
|
|
354
725
|
return {
|
|
355
726
|
id: getImplicitTileId(reference, coordinates),
|
|
356
727
|
children,
|
|
357
728
|
contentUrl,
|
|
358
729
|
content: content.length > 1 ? content : content[0],
|
|
730
|
+
contents: descriptor.useCanonicalContents && content.length ? content : undefined,
|
|
359
731
|
contentUrls: availableContentUrls,
|
|
360
|
-
refine: descriptor.refine,
|
|
732
|
+
refine: tileAttributes.refine || descriptor.refine,
|
|
361
733
|
type: getImplicitTileType(contentUrl),
|
|
362
734
|
lodMetricType: descriptor.lodMetricType,
|
|
363
735
|
lodMetricValue,
|
|
364
736
|
geometricError: lodMetricValue,
|
|
365
|
-
boundingVolume:
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
),
|
|
370
|
-
|
|
737
|
+
boundingVolume: tileAttributes.boundingVolume || computedBoundingVolume,
|
|
738
|
+
transform: tileAttributes.transform,
|
|
739
|
+
transformMatrix: tileAttributes.transform,
|
|
740
|
+
metadata: tileProperties ? {properties: tileProperties} : undefined,
|
|
741
|
+
implicitMetadata: getImplicitSubtreeMetadata(subtree, tileProperties, contentProperties),
|
|
742
|
+
_scaleGeometricError: descriptor.scaleGeometricError !== false
|
|
371
743
|
};
|
|
372
744
|
}
|
|
373
745
|
|
|
@@ -381,13 +753,17 @@ function formatImplicitTileHeader(
|
|
|
381
753
|
* @returns Metadata references, or `undefined` when the subtree declares none.
|
|
382
754
|
*/
|
|
383
755
|
function getImplicitSubtreeMetadata(
|
|
384
|
-
subtree: ParsedImplicitSubtree
|
|
756
|
+
subtree: ParsedImplicitSubtree,
|
|
757
|
+
tileProperties?: Record<string, unknown>,
|
|
758
|
+
contentProperties?: Record<string, unknown>
|
|
385
759
|
): ImplicitSubtreeMetadata | undefined {
|
|
386
760
|
if (
|
|
387
761
|
!subtree.propertyTables &&
|
|
388
762
|
subtree.tileMetadata === undefined &&
|
|
389
763
|
!subtree.contentMetadata &&
|
|
390
|
-
subtree.subtreeMetadata === undefined
|
|
764
|
+
subtree.subtreeMetadata === undefined &&
|
|
765
|
+
!tileProperties &&
|
|
766
|
+
!contentProperties
|
|
391
767
|
) {
|
|
392
768
|
return undefined;
|
|
393
769
|
}
|
|
@@ -395,7 +771,9 @@ function getImplicitSubtreeMetadata(
|
|
|
395
771
|
propertyTables: subtree.propertyTables,
|
|
396
772
|
tileMetadata: subtree.tileMetadata,
|
|
397
773
|
contentMetadata: subtree.contentMetadata,
|
|
398
|
-
subtreeMetadata: subtree.subtreeMetadata
|
|
774
|
+
subtreeMetadata: subtree.subtreeMetadata,
|
|
775
|
+
tileProperties,
|
|
776
|
+
contentProperties
|
|
399
777
|
};
|
|
400
778
|
}
|
|
401
779
|
|
|
@@ -493,9 +871,8 @@ function getImplicitChildCount(subdivisionScheme: 'QUADTREE' | 'OCTREE'): number
|
|
|
493
871
|
* Calculates the bounding volume for global implicit coordinates.
|
|
494
872
|
*
|
|
495
873
|
* Region volumes are divided in longitude/latitude and, for octrees, height. Oriented boxes are
|
|
496
|
-
* divided along their half-axis vectors. S2
|
|
497
|
-
*
|
|
498
|
-
* by `@loaders.gl/3d-tiles`, and a conservative volume avoids incorrect culling.
|
|
874
|
+
* divided along their half-axis vectors. S2 roots use math.gl's Hilbert-aware descendant indexing
|
|
875
|
+
* to produce the exact child token and a corresponding geospatial oriented box.
|
|
499
876
|
*
|
|
500
877
|
* @param rootBoundingVolume - Bounding volume of the complete implicit hierarchy.
|
|
501
878
|
* @param coordinates - Global tile coordinates.
|
|
@@ -510,9 +887,11 @@ function calculateImplicitBoundingVolume(
|
|
|
510
887
|
if (rootBoundingVolume.region) {
|
|
511
888
|
const [west, south, east, north, minimumHeight, maximumHeight] = rootBoundingVolume.region;
|
|
512
889
|
const divisionCount = 2 ** coordinates.level;
|
|
513
|
-
const
|
|
890
|
+
const longitudeSpan = east >= west ? east - west : east + 2 * Math.PI - west;
|
|
891
|
+
const longitudeSize = longitudeSpan / divisionCount;
|
|
514
892
|
const latitudeSize = (north - south) / divisionCount;
|
|
515
|
-
const childWest = west + longitudeSize * coordinates.x;
|
|
893
|
+
const childWest = normalizeRegionLongitude(west + longitudeSize * coordinates.x);
|
|
894
|
+
const childEast = normalizeRegionLongitude(west + longitudeSize * (coordinates.x + 1));
|
|
516
895
|
const childSouth = south + latitudeSize * coordinates.y;
|
|
517
896
|
let childMinimumHeight = minimumHeight;
|
|
518
897
|
let childMaximumHeight = maximumHeight;
|
|
@@ -525,7 +904,7 @@ function calculateImplicitBoundingVolume(
|
|
|
525
904
|
region: [
|
|
526
905
|
childWest,
|
|
527
906
|
childSouth,
|
|
528
|
-
|
|
907
|
+
childEast,
|
|
529
908
|
childSouth + latitudeSize,
|
|
530
909
|
childMinimumHeight,
|
|
531
910
|
childMaximumHeight
|
|
@@ -535,7 +914,13 @@ function calculateImplicitBoundingVolume(
|
|
|
535
914
|
|
|
536
915
|
if (rootBoundingVolume.box) {
|
|
537
916
|
if (rootBoundingVolume.s2VolumeInfo) {
|
|
538
|
-
return
|
|
917
|
+
return calculateS2ImplicitBoundingVolume(rootBoundingVolume, coordinates, subdivisionScheme);
|
|
918
|
+
}
|
|
919
|
+
if (rootBoundingVolume.extensions?.['3DTILES_shape_cylinder_region']) {
|
|
920
|
+
return {
|
|
921
|
+
...rootBoundingVolume,
|
|
922
|
+
box: [...rootBoundingVolume.box]
|
|
923
|
+
};
|
|
539
924
|
}
|
|
540
925
|
const box = rootBoundingVolume.box;
|
|
541
926
|
const divisionCount = 2 ** coordinates.level;
|
|
@@ -569,6 +954,68 @@ function calculateImplicitBoundingVolume(
|
|
|
569
954
|
);
|
|
570
955
|
}
|
|
571
956
|
|
|
957
|
+
/**
|
|
958
|
+
* Calculates an implicit descendant of an S2 root using math.gl's canonical Hilbert hierarchy.
|
|
959
|
+
*
|
|
960
|
+
* @param rootBoundingVolume - Normalized S2 root volume.
|
|
961
|
+
* @param coordinates - Global implicit tile coordinates.
|
|
962
|
+
* @param subdivisionScheme - Quadtree or octree subdivision.
|
|
963
|
+
* @returns Child volume with updated S2 metadata and oriented box.
|
|
964
|
+
*/
|
|
965
|
+
function calculateS2ImplicitBoundingVolume(
|
|
966
|
+
rootBoundingVolume: Record<string, any>,
|
|
967
|
+
coordinates: ImplicitTileCoordinates,
|
|
968
|
+
subdivisionScheme: 'QUADTREE' | 'OCTREE'
|
|
969
|
+
): Record<string, any> {
|
|
970
|
+
const rootS2VolumeInfo = rootBoundingVolume.s2VolumeInfo as S2VolumeInfo;
|
|
971
|
+
const rootIndex = getS2IndexFromToken(rootS2VolumeInfo.token);
|
|
972
|
+
const descendantIndex = getS2DescendantIndex(
|
|
973
|
+
rootIndex,
|
|
974
|
+
coordinates.level,
|
|
975
|
+
coordinates.x,
|
|
976
|
+
coordinates.y
|
|
977
|
+
);
|
|
978
|
+
const divisionCount = 2 ** coordinates.level;
|
|
979
|
+
const heightSize =
|
|
980
|
+
(rootS2VolumeInfo.maximumHeight - rootS2VolumeInfo.minimumHeight) / divisionCount;
|
|
981
|
+
const childS2VolumeInfo: S2VolumeInfo = {
|
|
982
|
+
token: getS2TokenFromIndex(descendantIndex),
|
|
983
|
+
minimumHeight:
|
|
984
|
+
rootS2VolumeInfo.minimumHeight +
|
|
985
|
+
(subdivisionScheme === 'OCTREE' ? heightSize * coordinates.z : 0),
|
|
986
|
+
maximumHeight:
|
|
987
|
+
rootS2VolumeInfo.minimumHeight +
|
|
988
|
+
(subdivisionScheme === 'OCTREE'
|
|
989
|
+
? heightSize * (coordinates.z + 1)
|
|
990
|
+
: heightSize * divisionCount)
|
|
991
|
+
};
|
|
992
|
+
const extensions = rootBoundingVolume.extensions ? {...rootBoundingVolume.extensions} : undefined;
|
|
993
|
+
if (extensions?.['3DTILES_bounding_volume_S2']) {
|
|
994
|
+
extensions['3DTILES_bounding_volume_S2'] = childS2VolumeInfo;
|
|
995
|
+
}
|
|
996
|
+
if (extensions?.['3DTILES_shape_s2']) {
|
|
997
|
+
extensions['3DTILES_shape_s2'] = childS2VolumeInfo;
|
|
998
|
+
}
|
|
999
|
+
return {
|
|
1000
|
+
...rootBoundingVolume,
|
|
1001
|
+
...(extensions ? {extensions} : {}),
|
|
1002
|
+
s2VolumeInfo: childS2VolumeInfo,
|
|
1003
|
+
box: convertS2BoundingVolumeToOBB(childS2VolumeInfo)
|
|
1004
|
+
};
|
|
1005
|
+
}
|
|
1006
|
+
|
|
1007
|
+
/** Normalize a longitude while preserving the positive pi boundary for non-wrapped regions. */
|
|
1008
|
+
function normalizeRegionLongitude(longitude: number): number {
|
|
1009
|
+
let normalizedLongitude = longitude;
|
|
1010
|
+
while (normalizedLongitude > Math.PI) {
|
|
1011
|
+
normalizedLongitude -= 2 * Math.PI;
|
|
1012
|
+
}
|
|
1013
|
+
while (normalizedLongitude < -Math.PI) {
|
|
1014
|
+
normalizedLongitude += 2 * Math.PI;
|
|
1015
|
+
}
|
|
1016
|
+
return normalizedLongitude;
|
|
1017
|
+
}
|
|
1018
|
+
|
|
572
1019
|
/**
|
|
573
1020
|
* Detects the runtime tile category from an available content URL.
|
|
574
1021
|
*
|