@itwin/core-frontend
Version:
iTwin.js frontend components
172 lines • 10.8 kB
JavaScript
/*---------------------------------------------------------------------------------------------
* 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