UNPKG

@itwin/core-common

Version:

iTwin.js components common to frontend and backend

402 lines • 20.7 kB
"use strict"; /*--------------------------------------------------------------------------------------------- * Copyright (c) Bentley Systems, Incorporated. All rights reserved. * See LICENSE.md in the project root for license terms and full copyright notice. *--------------------------------------------------------------------------------------------*/ /** @packageDocumentation * @module iModels */ Object.defineProperty(exports, "__esModule", { value: true }); exports.IModel = exports.EcefLocation = void 0; const core_bentley_1 = require("@itwin/core-bentley"); const core_geometry_1 = require("@itwin/core-geometry"); const Cartographic_1 = require("./geometry/Cartographic"); const CoordinateReferenceSystem_1 = require("./geometry/CoordinateReferenceSystem"); const IModelError_1 = require("./IModelError"); /** The position and orientation of an iModel on the earth in [ECEF](https://en.wikipedia.org/wiki/ECEF) (Earth Centered Earth Fixed) coordinates * @note This is an immutable type - all of its properties are frozen. * @see [GeoLocation of iModels]($docs/learning/GeoLocation.md) * @public */ class EcefLocation { /** The origin of the ECEF transform. */ origin; /** The orientation of the ECEF transform */ orientation; /** Optional position on the earth used to establish the ECEF origin and orientation. */ cartographicOrigin; /** Optional X column vector used with [[yVector]] to calculate potentially non-rigid transform if a projection is present. */ xVector; /** Optional Y column vector used with [[xVector]] to calculate potentially non-rigid transform if a projection is present. */ yVector; /** The transform from iModel Spatial coordinates to ECEF from this EcefLocation */ _transform = core_geometry_1.Transform.createIdentity(); /** Get the transform from iModel Spatial coordinates to ECEF from this EcefLocation */ getTransform() { return this._transform; } /** Construct a new EcefLocation. Once constructed, it is frozen and cannot be modified. */ constructor(props) { this.origin = core_geometry_1.Point3d.fromJSON(props.origin).freeze(); this.orientation = core_geometry_1.YawPitchRollAngles.fromJSON(props.orientation).freeze(); if (props.cartographicOrigin) this.cartographicOrigin = Cartographic_1.Cartographic.fromRadians({ longitude: props.cartographicOrigin.longitude, latitude: props.cartographicOrigin.latitude, height: props.cartographicOrigin.height }).freeze(); if (props.xVector && props.yVector) { this.xVector = core_geometry_1.Vector3d.fromJSON(props.xVector).freeze(); this.yVector = core_geometry_1.Vector3d.fromJSON(props.yVector).freeze(); } if (props.transform) { this._transform.setFromJSON(props.transform); this._transform.freeze(); } else { let matrix; if (this.xVector && this.yVector) { const zVector = this.xVector.crossProduct(this.yVector); if (zVector.normalizeInPlace()) matrix = core_geometry_1.Matrix3d.createColumns(this.xVector, this.yVector, zVector); } if (!matrix) matrix = this.orientation.toMatrix3d(); this._transform = core_geometry_1.Transform.createOriginAndMatrix(this.origin, matrix); this._transform.freeze(); } } /** Returns true if this EcefLocation is not located at the center of the Earth. * @alpha are locations very close to the center considered valid? What are the specific criteria? */ get isValid() { return !this.origin.isZero; } /** Construct ECEF Location from cartographic origin with optional known point and angle. */ static createFromCartographicOrigin(origin, point, angle) { const ecefOrigin = origin.toEcef(); const deltaRadians = 10 / core_geometry_1.Constant.earthRadiusWGS84.polar; const northCarto = Cartographic_1.Cartographic.fromRadians({ longitude: origin.longitude, latitude: origin.latitude + deltaRadians, height: origin.height }); const eastCarto = Cartographic_1.Cartographic.fromRadians({ longitude: origin.longitude + deltaRadians, latitude: origin.latitude, height: origin.height }); const ecefNorth = northCarto.toEcef(); const ecefEast = eastCarto.toEcef(); const xVector = (0, core_bentley_1.expectDefined)(core_geometry_1.Vector3d.createStartEnd(ecefOrigin, ecefEast).normalize()); const yVector = (0, core_bentley_1.expectDefined)(core_geometry_1.Vector3d.createStartEnd(ecefOrigin, ecefNorth).normalize()); const matrix = (0, core_bentley_1.expectDefined)(core_geometry_1.Matrix3d.createRigidFromColumns(xVector, yVector, core_geometry_1.AxisOrder.XYZ)); if (angle !== undefined) { const north = core_geometry_1.Matrix3d.createRotationAroundAxisIndex(core_geometry_1.AxisIndex.Z, angle); matrix.multiplyMatrixMatrix(north, matrix); } if (point !== undefined) { const delta = matrix.multiplyVector(core_geometry_1.Vector3d.create(-point.x, -point.y, -point.z)); ecefOrigin.addInPlace(delta); } return new EcefLocation({ origin: ecefOrigin, orientation: (0, core_bentley_1.expectDefined)(core_geometry_1.YawPitchRollAngles.createFromMatrix3d(matrix)), cartographicOrigin: origin }); } /** Construct ECEF Location from transform with optional position on the earth used to establish the ECEF origin and orientation. */ static createFromTransform(transform) { const ecefOrigin = transform.getOrigin(); const angleFromInput = core_geometry_1.YawPitchRollAngles.createDegrees(0, 0, 0); const locationOrientationFromInputT = core_geometry_1.YawPitchRollAngles.createFromMatrix3d(transform.getMatrix(), angleFromInput); const transformProps = transform.toJSON(); return new EcefLocation({ origin: ecefOrigin, orientation: locationOrientationFromInputT ?? angleFromInput, transform: transformProps }); } /** Get the location center of the earth in the iModel coordinate system. */ get earthCenter() { const matrix = this.orientation.toMatrix3d(); return core_geometry_1.Point3d.createFrom(matrix.multiplyTransposeXYZ(-this.origin.x, -this.origin.y, -this.origin.z)); } /** Return true if this location is equivalent to another location within a small tolerance. */ isAlmostEqual(other) { if (!this.origin.isAlmostEqual(other.origin) || !this.orientation.isAlmostEqual(other.orientation)) return false; if ((this.xVector === undefined) !== (other.xVector === undefined) || (this.yVector === undefined) !== (other.yVector === undefined)) return false; if (this.xVector !== undefined && other.xVector !== undefined && !this.xVector.isAlmostEqual(other.xVector)) return false; if (this.yVector !== undefined && other.yVector !== undefined && !this.yVector.isAlmostEqual(other.yVector)) return false; if (!this.getTransform().isAlmostEqual(other.getTransform())) return false; const thisCarto = this.cartographicOrigin; const otherCarto = other.cartographicOrigin; if (undefined === thisCarto || undefined === otherCarto) return undefined === thisCarto && undefined === otherCarto; return thisCarto.equalsEpsilon(otherCarto, core_geometry_1.Geometry.smallMetricDistance); } toJSON() { const props = { origin: this.origin.toJSON(), orientation: this.orientation.toJSON(), transform: this.getTransform().toJSON(), }; if (this.cartographicOrigin) props.cartographicOrigin = this.cartographicOrigin.toJSON(); if (this.xVector) props.xVector = this.xVector.toJSON(); if (this.yVector) props.yVector = this.yVector.toJSON(); return props; } } exports.EcefLocation = EcefLocation; /** Represents an iModel in JavaScript. * @see [GeoLocation of iModels]($docs/learning/GeoLocation.md) * @public */ class IModel { _projectExtents; _name; _rootSubject; _globalOrigin; _ecefLocation; _geographicCoordinateSystem; _iModelId; _changeset; /** The Id of the repository model. */ static repositoryModelId = "0x1"; /** The Id of the root subject element. */ static rootSubjectId = "0x1"; /** The Id of the dictionary model. */ static dictionaryId = "0x10"; /** Event raised after [[name]] changes. */ onNameChanged = new core_bentley_1.BeEvent(); /** Event raised after [[rootSubject]] changes. */ onRootSubjectChanged = new core_bentley_1.BeEvent(); /** Event raised after [[projectExtents]] changes. */ onProjectExtentsChanged = new core_bentley_1.BeEvent(); /** Event raised after [[globalOrigin]] changes. */ onGlobalOriginChanged = new core_bentley_1.BeEvent(); /** Event raised after [[ecefLocation]] changes. */ onEcefLocationChanged = new core_bentley_1.BeEvent(); /** Event raised after [[geographicCoordinateSystem]] changes. */ onGeographicCoordinateSystemChanged = new core_bentley_1.BeEvent(); /** Event raised after [[changeset]] changes. */ onChangesetChanged = new core_bentley_1.BeEvent(); /** Name of the iModel */ get name() { (0, core_bentley_1.assert)(this._name !== undefined); return this._name; } set name(name) { if (name !== this._name) { const old = this._name; this._name = name; if (undefined !== old) this.onNameChanged.raiseEvent(old); } } /** The name and description of the root subject of this iModel */ get rootSubject() { (0, core_bentley_1.assert)(this._rootSubject !== undefined); return this._rootSubject; } set rootSubject(subject) { if (undefined === this._rootSubject || this._rootSubject.name !== subject.name || this._rootSubject.description !== subject.description) { const old = this._rootSubject; this._rootSubject = subject; if (old) this.onRootSubjectChanged.raiseEvent(old); } } /** * The volume, in spatial coordinates, inside which the entire project is contained. * @note The object returned from this method is frozen. You *must* make a copy before you do anything that might attempt to modify it. */ get projectExtents() { (0, core_bentley_1.assert)(undefined !== this._projectExtents); return this._projectExtents; } set projectExtents(extents) { // Don't allow any axis of the project extents to be less than 1 meter. const projectExtents = extents.clone(); projectExtents.ensureMinLengths(1.0); if (!this._projectExtents || !this._projectExtents.isAlmostEqual(projectExtents)) { const old = this._projectExtents; projectExtents.freeze(); this._projectExtents = projectExtents; if (old) this.onProjectExtentsChanged.raiseEvent(old); } } /** An offset to be applied to all spatial coordinates. */ get globalOrigin() { (0, core_bentley_1.assert)(this._globalOrigin !== undefined); return this._globalOrigin; } set globalOrigin(org) { if (!this._globalOrigin || !this._globalOrigin.isAlmostEqual(org)) { const old = this._globalOrigin; org.freeze(); this._globalOrigin = org; if (old) this.onGlobalOriginChanged.raiseEvent(old); } } /** The [EcefLocation]($docs/learning/glossary#ecefLocation) of the iModel in Earth Centered Earth Fixed coordinates. * If the iModel property geographicCoordinateSystem is not defined then the ecefLocation provides a geolocation by defining a * 3D coordinate system relative to the Earth model WGS84. Refer to additional documentation for details. If the geographicCoordinateSystem * property is defined then the ecefLocation must be used with care. When the geographicCoordinateSystem is defined it indicates the * iModel cartesian space is the result of a cartographic projection. This implies a flattening of the Earth surface process that * results in scale, angular or area distortion. The ecefLocation is then an approximation calculated at the center of the project extent. * If the project is more than 2 kilometer in size, the ecefLocation may represent a poor approximation of the effective * cartographic projection used and a linear transformation should then be calculated at the exact origin of the data * it must position. * @see [GeoLocation of iModels]($docs/learning/GeoLocation.md) */ get ecefLocation() { return this._ecefLocation; } set ecefLocation(ecefLocation) { const old = this._ecefLocation; if (!old && !ecefLocation) return; else if (old && ecefLocation && old.isAlmostEqual(ecefLocation)) return; this._ecefLocation = ecefLocation; this.onEcefLocationChanged.raiseEvent(old); } /** Set the [EcefLocation]($docs/learning/glossary#ecefLocation) for this iModel. */ setEcefLocation(ecef) { this.ecefLocation = new EcefLocation(ecef); } /** The geographic coordinate reference system of the iModel. */ get geographicCoordinateSystem() { return this._geographicCoordinateSystem; } set geographicCoordinateSystem(geoCRS) { const old = this._geographicCoordinateSystem; if (!old && !geoCRS) return; else if (old && geoCRS && old.equals(geoCRS)) return; this._geographicCoordinateSystem = geoCRS; this.onGeographicCoordinateSystemChanged.raiseEvent(old); } /** Sets the geographic coordinate reference system from GeographicCRSProps. */ setGeographicCoordinateSystem(geoCRS) { this.geographicCoordinateSystem = new CoordinateReferenceSystem_1.GeographicCRS(geoCRS); } /** @internal */ getConnectionProps() { return { name: this.name, rootSubject: this.rootSubject, projectExtents: this.projectExtents.toJSON(), globalOrigin: this.globalOrigin.toJSON(), ecefLocation: this.ecefLocation, geographicCoordinateSystem: this.geographicCoordinateSystem?.toJSON(), ...this._getRpcProps(), }; } /** Convert this iModel to a JSON representation. */ toJSON() { return this.getConnectionProps(); } /** A key used to identify this iModel in RPC calls from frontend to backend. * @internal */ _fileKey; /** Get the key that was used to open this iModel. This is the value used for Rpc and Ipc communications. */ get key() { return this._fileKey; } /** @internal */ _iTwinId; /** The Guid that identifies the iTwin that owns this iModel. */ get iTwinId() { return this._iTwinId; } /** The Guid that identifies this iModel. */ get iModelId() { return this._iModelId; } /** @public */ get changeset() { return { ...this._changeset }; } set changeset(changeset) { const prev = this._changeset; if (prev.id !== changeset.id || prev.index !== changeset.index) { this._changeset = { id: changeset.id, index: changeset.index }; this.onChangesetChanged.raiseEvent(prev); } } _openMode = core_bentley_1.OpenMode.Readonly; /** The [[OpenMode]] used for this IModel. */ get openMode() { return this._openMode; } /** Return a token for RPC operations. * @throws IModelError if the iModel is not open. */ getRpcProps() { if (!this.isOpen) throw new IModelError_1.IModelError(core_bentley_1.IModelStatus.BadRequest, "IModel is not open for rpc"); return this._getRpcProps(); } /** Returns the iModel's RPC properties. * @note It is an error to attempt to use these properties as a token for RPC operations if the iModel is not open. * @internal */ _getRpcProps() { return { key: this._fileKey, iTwinId: this.iTwinId, iModelId: this.iModelId, changeset: this.changeset, }; } /** @internal */ constructor(tokenProps) { this._changeset = tokenProps?.changeset ?? { id: "", index: 0 }; this._fileKey = ""; if (tokenProps) { this._fileKey = tokenProps.key; this._iTwinId = tokenProps.iTwinId; this._iModelId = tokenProps.iModelId; } } /** @internal */ initialize(name, props) { this.name = name; this.rootSubject = props.rootSubject; this.projectExtents = core_geometry_1.Range3d.fromJSON(props.projectExtents); this.globalOrigin = core_geometry_1.Point3d.fromJSON(props.globalOrigin); const ecefLocation = props.ecefLocation ? new EcefLocation(props.ecefLocation) : undefined; this.ecefLocation = ecefLocation?.isValid ? ecefLocation : undefined; this.geographicCoordinateSystem = props.geographicCoordinateSystem ? new CoordinateReferenceSystem_1.GeographicCRS(props.geographicCoordinateSystem) : undefined; } /** Get the default subCategoryId for the supplied categoryId */ static getDefaultSubCategoryId(categoryId) { return core_bentley_1.Id64.isValid(categoryId) ? core_bentley_1.Id64.fromLocalAndBriefcaseIds(core_bentley_1.Id64.getLocalId(categoryId) + 1, core_bentley_1.Id64.getBriefcaseId(categoryId)) : core_bentley_1.Id64.invalid; } /** True if this iModel has an [EcefLocation]($docs/learning/glossary#ecefLocation). */ get isGeoLocated() { return undefined !== this._ecefLocation; } /** Get the Transform from this iModel's Spatial coordinates to ECEF coordinates using its [[IModel.ecefLocation]]. * @throws IModelError if [[isGeoLocated]] is false. */ getEcefTransform() { if (undefined === this._ecefLocation) throw new IModelError_1.IModelError(core_bentley_1.GeoServiceStatus.NoGeoLocation, "iModel is not GeoLocated"); return this._ecefLocation.getTransform(); } /** Convert a point in this iModel's Spatial coordinates to an ECEF point using its [[IModel.ecefLocation]]. * @param spatial A point in the iModel's spatial coordinates * @param result If defined, use this for output * @returns A Point3d in ECEF coordinates * @throws IModelError if [[isGeoLocated]] is false. */ spatialToEcef(spatial, result) { return this.getEcefTransform().multiplyPoint3d(spatial, result); } /** Convert a point in ECEF coordinates to a point in this iModel's Spatial coordinates using its [[ecefLocation]]. * @param ecef A point in ECEF coordinates * @param result If defined, use this for output * @returns A Point3d in this iModel's spatial coordinates * @throws IModelError if [[isGeoLocated]] is false. * @note The resultant point will only be meaningful if the ECEF coordinate is close on the earth to the iModel. */ ecefToSpatial(ecef, result) { return (0, core_bentley_1.expectDefined)(this.getEcefTransform().multiplyInversePoint3d(ecef, result)); } /** Convert a point in this iModel's Spatial coordinates to a [[Cartographic]] using its [[IModel.ecefLocation]]. * @param spatial A point in the iModel's spatial coordinates * @param result If defined, use this for output * @returns A Cartographic location * @throws IModelError if [[isGeoLocated]] is false. */ spatialToCartographicFromEcef(spatial, result) { return (0, core_bentley_1.expectDefined)(Cartographic_1.Cartographic.fromEcef(this.spatialToEcef(spatial), result)); } /** Convert a [[Cartographic]] to a point in this iModel's Spatial coordinates using its [[IModel.ecefLocation]]. * @param cartographic A cartographic location * @param result If defined, use this for output * @returns A point in this iModel's spatial coordinates * @throws IModelError if [[isGeoLocated]] is false. * @note The resultant point will only be meaningful if the ECEF coordinate is close on the earth to the iModel. */ cartographicToSpatialFromEcef(cartographic, result) { return this.ecefToSpatial(cartographic.toEcef(result), result); } } exports.IModel = IModel; //# sourceMappingURL=IModel.js.map