@loaders.gl/3d-tiles
Version:
3D Tiles, an open standard for streaming massive heterogeneous 3D geospatial datasets.
195 lines (194 loc) • 7.35 kB
JavaScript
// loaders.gl
// SPDX-License-Identifier: MIT
// Copyright vis.gl contributors
import { path } from '@loaders.gl/loader-utils';
import { Tile3DSubtreeLoader } from "../../tile-3d-subtree-loader.js";
import { load } from '@loaders.gl/core';
import { LOD_METRIC_TYPE, TILE_REFINEMENT, TILE_TYPE } from '@loaders.gl/tiles';
import { parseImplicitTiles, replaceContentUrlTemplate } from "./helpers/parse-3d-implicit-tiles.js";
import { convertS2BoundingVolumetoOBB } from "../utils/obb/s2-corners-to-obb.js";
function getTileType(tile, tileContentUrl = '') {
if (!tileContentUrl) {
return TILE_TYPE.EMPTY;
}
const contentUrl = tileContentUrl.split('?')[0]; // Discard query string
const fileExtension = contentUrl.split('.').pop();
switch (fileExtension) {
case 'pnts':
return TILE_TYPE.POINTCLOUD;
case 'i3dm':
case 'b3dm':
case 'glb':
case 'gltf':
return TILE_TYPE.SCENEGRAPH;
default:
return fileExtension || TILE_TYPE.EMPTY;
}
}
function getRefine(refine) {
switch (refine) {
case 'REPLACE':
case 'replace':
return TILE_REFINEMENT.REPLACE;
case 'ADD':
case 'add':
return TILE_REFINEMENT.ADD;
default:
return refine;
}
}
function resolveUri(uri, basePath) {
// url scheme per RFC3986
const urlSchemeRegex = /^[a-z][0-9a-z+.-]*:/i;
if (urlSchemeRegex.test(basePath)) {
const url = new URL(uri, `${basePath}/`);
return decodeURI(url.toString());
}
else if (uri.startsWith('/')) {
return uri;
}
return path.resolve(basePath, uri);
}
export function normalizeTileData(tile, basePath) {
if (!tile) {
return null;
}
let tileContentUrl;
if (tile.content) {
const contentUri = tile.content.uri || tile.content?.url;
if (typeof contentUri !== 'undefined') {
// sparse implicit tilesets may not define content for all nodes
tileContentUrl = resolveUri(contentUri, basePath);
}
}
const tilePostprocessed = {
...tile,
id: tileContentUrl,
contentUrl: tileContentUrl,
lodMetricType: LOD_METRIC_TYPE.GEOMETRIC_ERROR,
lodMetricValue: tile.geometricError,
transformMatrix: tile.transform,
type: getTileType(tile, tileContentUrl),
refine: getRefine(tile.refine)
};
return tilePostprocessed;
}
// normalize tile headers
export async function normalizeTileHeaders(tileset, basePath, options) {
let root = null;
const rootImplicitTilingExtension = getImplicitTilingExtensionData(tileset.root);
if (rootImplicitTilingExtension && tileset.root) {
root = await normalizeImplicitTileHeaders(tileset.root, tileset, basePath, rootImplicitTilingExtension, options);
}
else {
root = normalizeTileData(tileset.root, basePath);
}
const stack = [];
stack.push(root);
while (stack.length > 0) {
const tile = stack.pop() || {};
const children = tile.children || [];
const childrenPostprocessed = [];
for (const childHeader of children) {
const childImplicitTilingExtension = getImplicitTilingExtensionData(childHeader);
let childHeaderPostprocessed;
if (childImplicitTilingExtension) {
childHeaderPostprocessed = await normalizeImplicitTileHeaders(childHeader, tileset, basePath, childImplicitTilingExtension, options);
}
else {
childHeaderPostprocessed = normalizeTileData(childHeader, basePath);
}
if (childHeaderPostprocessed) {
childrenPostprocessed.push(childHeaderPostprocessed);
stack.push(childHeaderPostprocessed);
}
}
tile.children = childrenPostprocessed;
}
return root;
}
/**
* Do normalisation of implicit tile headers
* TODO Check if Tile3D class can be a return type here.
* @param tileset
*/
export async function normalizeImplicitTileHeaders(tile, tileset, basePath, implicitTilingExtension, options) {
const { subdivisionScheme, maximumLevel, availableLevels, subtreeLevels, subtrees: { uri: subtreesUriTemplate } } = implicitTilingExtension;
const replacedUrlTemplate = replaceContentUrlTemplate(subtreesUriTemplate, 0, 0, 0, 0);
const subtreeUrl = resolveUri(replacedUrlTemplate, basePath);
const subtree = await load(subtreeUrl, Tile3DSubtreeLoader, options);
const tileContentUri = tile.content?.uri;
const contentUrlTemplate = tileContentUri ? resolveUri(tileContentUri, basePath) : '';
const refine = tileset?.root?.refine;
// @ts-ignore
const rootLodMetricValue = tile.geometricError;
// Replace tile.boundingVolume with the the bounding volume specified by the extensions['3DTILES_bounding_volume_S2']
const s2VolumeInfo = tile.boundingVolume.extensions?.['3DTILES_bounding_volume_S2'];
if (s2VolumeInfo) {
const box = convertS2BoundingVolumetoOBB(s2VolumeInfo);
const s2VolumeBox = { box, s2VolumeInfo };
tile.boundingVolume = s2VolumeBox;
}
const rootBoundingVolume = tile.boundingVolume;
const implicitOptions = {
contentUrlTemplate,
subtreesUriTemplate,
subdivisionScheme,
subtreeLevels,
maximumLevel: Number.isFinite(availableLevels) ? availableLevels - 1 : maximumLevel,
refine,
basePath,
lodMetricType: LOD_METRIC_TYPE.GEOMETRIC_ERROR,
rootLodMetricValue,
rootBoundingVolume,
getTileType,
getRefine
};
return await normalizeImplicitTileData(tile, basePath, subtree, implicitOptions, options);
}
/**
* Do implicit data normalisation to create hierarchical tile structure
* @param tile
* @param rootSubtree
* @param options
* @returns
*/
export async function normalizeImplicitTileData(tile, basePath, rootSubtree, implicitOptions, loaderOptions) {
if (!tile) {
return null;
}
const { children, contentUrl } = await parseImplicitTiles({
subtree: rootSubtree,
implicitOptions,
loaderOptions
});
let tileContentUrl;
let tileContent = null;
if (contentUrl) {
tileContentUrl = contentUrl;
tileContent = { uri: contentUrl.replace(`${basePath}/`, '') };
}
const tilePostprocessed = {
...tile,
id: tileContentUrl,
contentUrl: tileContentUrl,
lodMetricType: LOD_METRIC_TYPE.GEOMETRIC_ERROR,
lodMetricValue: tile.geometricError,
transformMatrix: tile.transform,
type: getTileType(tile, tileContentUrl),
refine: getRefine(tile.refine),
content: tileContent || tile.content,
children
};
return tilePostprocessed;
}
/**
* Implicit Tiling data can be in 3DTILES_implicit_tiling for 3DTiles v.Next or directly in implicitTiling object for 3DTiles v1.1.
* Spec 3DTiles v.Next - https://github.com/CesiumGS/3d-tiles/tree/main/extensions/3DTILES_implicit_tiling
* Spec 3DTiles v.1.1 - https://github.com/CesiumGS/3d-tiles/tree/draft-1.1/specification/ImplicitTiling
* @param tile
* @returns
*/
function getImplicitTilingExtensionData(tile) {
return tile?.extensions?.['3DTILES_implicit_tiling'] || tile?.implicitTiling;
}