UNPKG

@itwin/core-frontend

Version:
172 lines • 10.8 kB
/*--------------------------------------------------------------------------------------------- * Copyright (c) Bentley Systems, Incorporated. All rights reserved. * See LICENSE.md in the project root for license terms and full copyright notice. *--------------------------------------------------------------------------------------------*/ import { expectDefined, JsonUtils, Logger, RealityDataStatus } from "@itwin/core-bentley"; import { Cartographic, EcefLocation } from "@itwin/core-common"; import { Matrix3d, Point3d, Range3d, Transform, Vector3d } from "@itwin/core-geometry"; import { FrontendLoggerCategory } from "../../common/FrontendLoggerCategory"; import { RealityDataError } from "../../RealityDataSource"; const loggerCategory = FrontendLoggerCategory.RealityData; /** * This class provide methods used to interpret Cesium 3dTile format */ export class ThreeDTileFormatInterpreter { /** Gets reality data spatial location and extents * @param json root document file in json format * @returns spatial location and volume of interest, in meters, centered around `spatial location` * @throws [[RealityDataError]] if source is invalid or cannot be read */ static getSpatialLocationAndExtents(json) { const worldRange = new Range3d(); let isGeolocated = true; let location; Logger.logTrace(loggerCategory, "RealityData getSpatialLocationAndExtents"); if (undefined === json?.root) { Logger.logWarning(loggerCategory, `Error getSpatialLocationAndExtents - no root in json`); // return first 1024 char from the json const getMetaData = () => { return { json: JSON.stringify(json).substring(0, 1024) }; }; const error = new RealityDataError(RealityDataStatus.InvalidData, "Invalid or unknown data - no root in json", getMetaData); throw error; } try { if (undefined !== json?.root?.boundingVolume?.region) { const region = JsonUtils.asArray(json.root.boundingVolume.region); Logger.logTrace(loggerCategory, "RealityData json.root.boundingVolume.region", () => ({ ...region })); if (undefined === region) { Logger.logError(loggerCategory, `Error getSpatialLocationAndExtents - region undefined`); throw new TypeError("Unable to determine GeoLocation - no root Transform or Region on root."); } const ecefLow = (Cartographic.fromRadians({ longitude: region[0], latitude: region[1], height: region[4] })).toEcef(); const ecefHigh = (Cartographic.fromRadians({ longitude: region[2], latitude: region[3], height: region[5] })).toEcef(); const ecefRange = Range3d.create(ecefLow, ecefHigh); const cartoCenter = Cartographic.fromRadians({ longitude: (region[0] + region[2]) / 2.0, latitude: (region[1] + region[3]) / 2.0, height: (region[4] + region[5]) / 2.0 }); location = cartoCenter; const ecefLocation = EcefLocation.createFromCartographicOrigin(cartoCenter); // iModelDb.setEcefLocation(ecefLocation); const ecefToWorld = expectDefined(ecefLocation.getTransform().inverse()); worldRange.extendRange(Range3d.fromJSON(ecefToWorld.multiplyRange(ecefRange))); } else { let worldToEcefTransform = ThreeDTileFormatInterpreter.transformFromJson(json.root.transform); Logger.logTrace(loggerCategory, "RealityData json.root.transform", () => ({ ...worldToEcefTransform })); const range = expectDefined(ThreeDTileFormatInterpreter.rangeFromBoundingVolume(json.root.boundingVolume)); if (undefined === worldToEcefTransform) worldToEcefTransform = Transform.createIdentity(); const ecefRange = worldToEcefTransform.multiplyRange(range); // range in model -> range in ecef const ecefCenter = worldToEcefTransform.multiplyPoint3d(range.center); // range center in model -> range center in ecef const cartoCenter = Cartographic.fromEcef(ecefCenter); // ecef center to cartographic center const isNotNearEarthSurface = cartoCenter && (cartoCenter.height < -5000); // 5 km under ground! const earthCenterToRangeCenterRayLenght = range.center.magnitude(); if (worldToEcefTransform.matrix.isIdentity && (earthCenterToRangeCenterRayLenght < 1.0E5 || isNotNearEarthSurface)) { isGeolocated = false; worldRange.extendRange(Range3d.fromJSON(ecefRange)); const centerOfEarth = new EcefLocation({ origin: { x: 0.0, y: 0.0, z: 0.0 }, orientation: { yaw: 0.0, pitch: 0.0, roll: 0.0 } }); location = centerOfEarth; Logger.logTrace(loggerCategory, "RealityData NOT Geolocated", () => ({ ...location })); } else { let ecefLocation = EcefLocation.createFromTransform(worldToEcefTransform); // Fix Bug 445630: [RDV][Regression] Orientation of georeferenced Reality Mesh is wrong. // Use json.root.transform only if defined and not identity -> otherwise will use a transform computed from cartographic center. if (worldToEcefTransform.matrix.isIdentity) { // For georeferenced Reality Meshes, its origin is translated to model origin (0,0,0). // Apply range center to translate it back to its original position. const worldCenter = !worldToEcefTransform.matrix.isIdentity ? range.center : undefined; if (cartoCenter) ecefLocation = EcefLocation.createFromCartographicOrigin(cartoCenter, worldCenter); } location = ecefLocation; Logger.logTrace(loggerCategory, "RealityData is worldToEcefTransform.matrix.isIdentity", () => ({ isIdentity: worldToEcefTransform.matrix.isIdentity })); // iModelDb.setEcefLocation(ecefLocation); const ecefToWorld = expectDefined(ecefLocation.getTransform().inverse()); worldRange.extendRange(Range3d.fromJSON(ecefToWorld.multiplyRange(ecefRange))); Logger.logTrace(loggerCategory, "RealityData ecefToWorld", () => ({ ...ecefToWorld })); } } } catch { Logger.logWarning(loggerCategory, `Error getSpatialLocationAndExtents - cannot interpret json`); // return first 1024 char from the json const getMetaData = () => { return { json: JSON.stringify(json).substring(0, 1024) }; }; const error = new RealityDataError(RealityDataStatus.InvalidData, "Invalid or unknown data", getMetaData); throw error; } const spatialLocation = { location, worldRange, isGeolocated }; return spatialLocation; } /** Gets information to identify the product and engine that create this reality data * Will return undefined if cannot be resolved * @param rootDocjson root document file in json format * @returns information to identify the product and engine that create this reality data */ static getPublisherProductInfo(rootDocjson) { const info = { product: "", engine: "", version: "" }; if (rootDocjson && rootDocjson.root) { if (rootDocjson.root.SMPublisherInfo) { info.product = rootDocjson.root.SMPublisherInfo.Product ? rootDocjson.root.SMPublisherInfo.Product : ""; info.engine = rootDocjson.root.SMPublisherInfo.Publisher ? rootDocjson.root.SMPublisherInfo.Publisher : ""; info.version = rootDocjson.root.SMPublisherInfo["Publisher Version"] ? rootDocjson.root.SMPublisherInfo["Publisher Version"] : ""; } } return info; } /** Gets information about 3dTile file for this reality data * Will return undefined if cannot be resolved * @param rootDocjson root document file in json format * @returns information about 3dTile file for this reality data */ static getFileInfo(rootDocjson) { const info = { rootChildren: rootDocjson?.root?.children?.length ?? 0, }; return info; } /** Convert a boundingVolume into a range * @param boundingVolume the bounding volume to convert * @returns the range or undefined if cannot convert */ static rangeFromBoundingVolume(boundingVolume) { if (undefined === boundingVolume) return undefined; if (Array.isArray(boundingVolume.box)) { const box = boundingVolume.box; const center = Point3d.create(box[0], box[1], box[2]); const ux = Vector3d.create(box[3], box[4], box[5]); const uy = Vector3d.create(box[6], box[7], box[8]); const uz = Vector3d.create(box[9], box[10], box[11]); const corners = []; for (let j = 0; j < 2; j++) { for (let k = 0; k < 2; k++) { for (let l = 0; l < 2; l++) { corners.push(center.plus3Scaled(ux, (j ? -1.0 : 1.0), uy, (k ? -1.0 : 1.0), uz, (l ? -1.0 : 1.0))); } } } return Range3d.createArray(corners); } else if (Array.isArray(boundingVolume.sphere)) { const sphere = boundingVolume.sphere; const center = Point3d.create(sphere[0], sphere[1], sphere[2]); const radius = sphere[3]; return Range3d.createXYZXYZ(center.x - radius, center.y - radius, center.z - radius, center.x + radius, center.y + radius, center.z + radius); } return undefined; } /** Convert a boundingVolume into a range */ static maximumSizeFromGeometricTolerance(range, geometricError) { const minToleranceRatio = .5; // Nominally the error on screen size of a tile. Increasing generally increases performance (fewer draw calls) at expense of higher load times. return minToleranceRatio * range.diagonal().magnitude() / geometricError; } /** Convert a boundingVolume into a range */ static transformFromJson(jTrans) { return (jTrans === undefined) ? undefined : Transform.createOriginAndMatrix(Point3d.create(jTrans[12], jTrans[13], jTrans[14]), Matrix3d.createRowValues(jTrans[0], jTrans[4], jTrans[8], jTrans[1], jTrans[5], jTrans[9], jTrans[2], jTrans[6], jTrans[10])); } } //# sourceMappingURL=ThreeDTileFormatInterpreter.js.map