UNPKG

@itwin/core-frontend

Version:
482 lines • 28.9 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 Views */ Object.defineProperty(exports, "__esModule", { value: true }); exports.BackgroundMapGeometry = void 0; exports.getFrustumPlaneIntersectionDepthRange = getFrustumPlaneIntersectionDepthRange; exports.calculateEcefToDbTransformAtLocation = calculateEcefToDbTransformAtLocation; const core_bentley_1 = require("@itwin/core-bentley"); const core_geometry_1 = require("@itwin/core-geometry"); const core_common_1 = require("@itwin/core-common"); const internal_1 = require("./tile/internal"); const scratchRange = core_geometry_1.Range3d.createNull(); const scratchZeroPoint = core_geometry_1.Point3d.createZero(); const scratchPoint = core_geometry_1.Point3d.create(); const scratchVector = core_geometry_1.Vector3d.create(); const scratchCenterPoint = core_geometry_1.Point3d.createZero(); const scratchIntersectRay = core_geometry_1.Ray3d.create(core_geometry_1.Point3d.create(), core_geometry_1.Vector3d.create()); const scratchEyePoint = core_geometry_1.Point3d.createZero(); const scratchViewRotation = core_geometry_1.Matrix3d.createIdentity(); const scratchSilhouetteNormal = core_geometry_1.Vector3d.create(); const scratchCartoRectangle = new core_geometry_1.GrowableXYZArray(); const scratchWorkArray = new core_geometry_1.GrowableXYZArray(); function accumulateDepthRange(point, viewRotation, range) { viewRotation.multiplyXYZtoXYZ(point, scratchPoint); range.extend(scratchPoint); } function accumulateFrustumPlaneDepthRange(frustum, plane, viewRotation, range, eyePoint) { let includeHorizon = false; for (let i = 0; i < 4; i++) { const frustumRay = core_geometry_1.Ray3d.createStartEnd(eyePoint ? eyePoint : frustum.points[i + 4], frustum.points[i]); const thisFraction = frustumRay.intersectionWithPlane(plane, scratchPoint); if (undefined !== thisFraction && (!eyePoint || thisFraction > 0)) accumulateDepthRange(scratchPoint, viewRotation, range); else includeHorizon = true; } if (includeHorizon) { if (eyePoint !== undefined) { const eyeHeight = plane.altitude(eyePoint); if (eyeHeight < 0) accumulateDepthRange(eyePoint, viewRotation, range); else { const viewZ = viewRotation.getRow(2); const horizonDistance = Math.sqrt(eyeHeight * eyeHeight + 2 * eyeHeight * core_geometry_1.Constant.earthRadiusWGS84.equator); accumulateDepthRange(eyePoint.plusScaled(viewZ, -horizonDistance, scratchPoint), viewRotation, range); } } } } /** @internal */ function getFrustumPlaneIntersectionDepthRange(frustum, plane) { const eyePoint = frustum.getEyePoint(scratchEyePoint); const viewRotation = frustum.getRotation(scratchViewRotation); const intersectRange = core_geometry_1.Range3d.createNull(); accumulateFrustumPlaneDepthRange(frustum, plane, viewRotation, intersectRange, eyePoint); return intersectRange.isNull ? core_geometry_1.Range1d.createNull() : core_geometry_1.Range1d.createXX(intersectRange.low.z, intersectRange.high.z); } /** Geometry of background map - either an ellipsoid or a plane as defined by GlobeMode. * @internal */ class BackgroundMapGeometry { _bimElevationBias; _iModel; globeMode; geometry; globeOrigin; globeMatrix; cartesianRange; cartesianTransitionRange; cartesianPlane; cartesianDiagonal; cartesianChordHeight; maxGeometryChordHeight; _mercatorFractionToDb; _mercatorTilingScheme; _ecefToDb; static maxCartesianDistance = 1E4; // If globe is 3D we still consider the map geometry flat within this distance of the project extents. static _transitionDistanceMultiplier = .25; // In the transition range which extends beyond the cartesian range we interpolate between cartesian and ellipsoid. static _scratchRayFractions = new Array(); static _scratchRayAngles = new Array(); static _scratchPoint = core_geometry_1.Point3d.createZero(); constructor(_bimElevationBias, globeMode, _iModel) { this._bimElevationBias = _bimElevationBias; this._iModel = _iModel; this._ecefToDb = _iModel.getMapEcefToDb(_bimElevationBias); this.globeMode = globeMode; this.cartesianRange = BackgroundMapGeometry.getCartesianRange(_iModel); this.cartesianTransitionRange = this.cartesianRange.clone(); this.cartesianTransitionRange.expandInPlace(BackgroundMapGeometry.getCartesianTransitionDistance(_iModel)); this.cartesianDiagonal = this.cartesianRange.diagonal().magnitudeXY(); const earthRadius = core_geometry_1.Constant.earthRadiusWGS84.equator; this.globeOrigin = this._ecefToDb.origin.cloneAsPoint3d(); this.globeMatrix = this._ecefToDb.matrix.clone(); this.cartesianChordHeight = Math.sqrt(this.cartesianDiagonal * this.cartesianDiagonal + earthRadius * earthRadius) - earthRadius; // Maximum chord height deviation of the cartesian area. const halfChordAngle = core_geometry_1.Angle.piOver2Radians / 10; this.maxGeometryChordHeight = (1 - Math.cos(halfChordAngle)) * earthRadius; this.cartesianPlane = this.getPlane(); this.geometry = (globeMode === core_common_1.GlobeMode.Ellipsoid) ? this.getEarthEllipsoid() : this.cartesianPlane; this._mercatorTilingScheme = new internal_1.WebMercatorTilingScheme(); this._mercatorFractionToDb = this._mercatorTilingScheme.computeMercatorFractionToDb(this._ecefToDb, _bimElevationBias, _iModel, false); } static getCartesianRange(iModel, result) { const cartesianRange = core_geometry_1.Range3d.createFrom(iModel.projectExtents, result); cartesianRange.expandInPlace(BackgroundMapGeometry.maxCartesianDistance); return cartesianRange; } static getCartesianTransitionDistance(iModel) { return BackgroundMapGeometry.getCartesianRange(iModel, scratchRange).diagonal().magnitudeXY() * BackgroundMapGeometry._transitionDistanceMultiplier; } async dbToCartographicFromGcs(db) { const scratch = new core_geometry_1.Point3d(); const promises = db.map(async (p) => { return this.cartesianRange.containsPoint(core_geometry_1.Point3d.createFrom(p, scratch)) ? this._iModel.spatialToCartographic(p) : this.dbToCartographic(p); }); return Promise.all(promises); } async dbToWGS84CartographicFromGcs(db) { if (db.length === 0) return []; const result = Array(db.length); const reproject = []; const reprojectIdx = []; const scratch = new core_geometry_1.Point3d(); for (let i = 0; i < db.length; i++) { core_geometry_1.Point3d.createFrom(db[i], scratch); if (this.cartesianRange.containsPoint(scratch)) { reprojectIdx.push(i); reproject.push(db[i]); } else { result[i] = this.dbToCartographic(db[i]); } } if (reproject.length === 0) return result; const reprojectPromise = this._iModel.wgs84CartographicFromSpatial(reproject); return reprojectPromise.then((reprojected) => { if (reprojected.length === reprojectIdx.length) { // reprojected array size must match our index array, otherwise something is OFF for (let i = 0; i < reprojected.length; i++) { result[reprojectIdx[i]] = reprojected[i]; // Insert the reprojected values at their original index } } return result; }); } dbToCartographic(db, result) { if (undefined === result) result = core_common_1.Cartographic.createZero(); if (this.globeMode === core_common_1.GlobeMode.Plane) { const mercatorFraction = this._mercatorFractionToDb.multiplyInversePoint3d(db); return this._mercatorTilingScheme.fractionToCartographic(mercatorFraction.x, mercatorFraction.y, result, mercatorFraction.z); } else { const ecef = this._ecefToDb.multiplyInversePoint3d(db); return core_common_1.Cartographic.fromEcef(ecef, result); } } async cartographicToDbFromGcs(cartographic) { let db; if (this.globeMode === core_common_1.GlobeMode.Plane) { const fraction = core_geometry_1.Point2d.create(0, 0); db = cartographic.map((p) => { this._mercatorTilingScheme.cartographicToFraction(p.latitude, p.longitude, fraction); return this._mercatorFractionToDb.multiplyXYZ(fraction.x, fraction.y, p.height); }); } else { db = cartographic.map((p) => this._ecefToDb.multiplyPoint3d(p.toEcef())); } if (this._iModel.noGcsDefined) return db; const promises = db.map(async (p, i) => { return this.cartesianRange.containsPoint(p) ? this._iModel.cartographicToSpatialFromGcs(cartographic[i]) : p; }); return Promise.all(promises); } async cartographicToDbFromWgs84Gcs(cartographic) { let db; if (this.globeMode === core_common_1.GlobeMode.Plane) { const fraction = core_geometry_1.Point2d.create(0, 0); db = cartographic.map((p) => { this._mercatorTilingScheme.cartographicToFraction(p.latitude, p.longitude, fraction); return this._mercatorFractionToDb.multiplyXYZ(fraction.x, fraction.y, p.height); }); } else { db = cartographic.map((p) => this._ecefToDb.multiplyPoint3d(p.toEcef())); } if (this._iModel.noGcsDefined) return db; const toReprojectCoords = []; const toReprojectIdx = []; db.forEach(async (p, i) => { if (this.cartesianRange.containsPoint(p)) { toReprojectCoords.push({ x: cartographic[i].longitudeDegrees, y: cartographic[i].latitudeDegrees, z: cartographic[i].height }); toReprojectIdx.push(i); } }); const spatialPoints = await this._iModel.toSpatialFromGcs(toReprojectCoords, { horizontalCRS: { epsg: 4326 }, verticalCRS: { id: "ELLIPSOID" } }); return db.map((p, i) => { const reprojectedIdx = toReprojectIdx.findIndex((value) => value === i); return (reprojectedIdx === -1 ? p : spatialPoints[reprojectedIdx]); }); } cartographicToDb(cartographic, result) { if (this.globeMode === core_common_1.GlobeMode.Plane) { const fraction = core_geometry_1.Point2d.create(0, 0); this._mercatorTilingScheme.cartographicToFraction(cartographic.latitude, cartographic.longitude, fraction); return this._mercatorFractionToDb.multiplyXYZ(fraction.x, fraction.y, cartographic.height, result); } else { return this._ecefToDb.multiplyPoint3d(cartographic.toEcef()); } } getEarthEllipsoid(radiusOffset = 0) { const equatorRadius = core_geometry_1.Constant.earthRadiusWGS84.equator + radiusOffset, polarRadius = core_geometry_1.Constant.earthRadiusWGS84.polar + radiusOffset; return core_geometry_1.Ellipsoid.createCenterMatrixRadii(this.globeOrigin, this.globeMatrix, equatorRadius, equatorRadius, polarRadius); } getPlane(offset = 0) { return core_geometry_1.Plane3dByOriginAndUnitNormal.create(core_geometry_1.Point3d.create(0, 0, this._bimElevationBias + offset), core_geometry_1.Vector3d.create(0, 0, 1)); } getRayIntersection(ray, positiveOnly) { let intersect; if (this.globeMode === core_common_1.GlobeMode.Ellipsoid) { const ellipsoid = this.geometry; BackgroundMapGeometry._scratchRayAngles.length = 0; BackgroundMapGeometry._scratchRayFractions.length = 0; const count = ellipsoid.intersectRay(ray, BackgroundMapGeometry._scratchRayFractions, undefined, BackgroundMapGeometry._scratchRayAngles); let intersectDistance; for (let i = 0; i < count; i++) { const thisFraction = BackgroundMapGeometry._scratchRayFractions[i]; if ((!positiveOnly || thisFraction > 0) && (undefined === intersectDistance || thisFraction < intersectDistance)) { intersectDistance = thisFraction; intersect = scratchIntersectRay; ellipsoid.radiansToUnitNormalRay(BackgroundMapGeometry._scratchRayAngles[i].longitudeRadians, BackgroundMapGeometry._scratchRayAngles[i].latitudeRadians, intersect); if (intersect.direction.dotProduct(ray.direction) < 0) { if (this.cartesianRange.containsPoint(intersect.origin)) { // If we're in the cartesian range, correct to planar intersection. const planeFraction = ray.intersectionWithPlane(this.cartesianPlane, scratchIntersectRay.origin); if (undefined !== planeFraction && (!positiveOnly || planeFraction > 0)) { intersect.direction.setFromVector3d(this.cartesianPlane.getNormalRef()); } } } } } } else { const plane = this.geometry; const thisFraction = ray.intersectionWithPlane(plane, scratchIntersectRay.origin); if (undefined !== thisFraction && (!positiveOnly || thisFraction > 0)) { intersect = scratchIntersectRay; intersect.direction.setFromVector3d(plane.getNormalRef()); } } return intersect; } getPointHeight(point) { if (this.globeMode === core_common_1.GlobeMode.Ellipsoid) { const ellipsoid = this.geometry; const projected = ellipsoid.projectPointToSurface(point); if (undefined === projected) return undefined; const distance = ellipsoid.radiansToPoint(projected.longitudeRadians, projected.latitudeRadians).distance(point); const ellipsePoint = ellipsoid.transformRef.multiplyInversePoint3d(point, BackgroundMapGeometry._scratchPoint); return ellipsePoint.magnitude() < 1 ? -distance : distance; } else { const plane = this.geometry; return plane.altitude(point); } } /** @internal */ getFrustumIntersectionDepthRange(frustum, bimRange, heightRange, gridPlane, doGlobalScope) { const clipPlanes = frustum.getRangePlanes(false, false, 0); const eyePoint = frustum.getEyePoint(scratchEyePoint); const viewRotation = frustum.getRotation(scratchViewRotation); if (undefined === viewRotation) return core_geometry_1.Range1d.createNull(); // Degenerate frustum... const viewZ = viewRotation.getRow(2); const cartoRange = this.cartesianTransitionRange; const intersectRange = core_geometry_1.Range3d.createNull(); const doAccumulate = ((point) => accumulateDepthRange(point, viewRotation, intersectRange)); if (gridPlane) accumulateFrustumPlaneDepthRange(frustum, gridPlane, viewRotation, intersectRange, eyePoint); if (this.geometry instanceof core_geometry_1.Plane3dByOriginAndUnitNormal) { // Intersection with a planar background projection... const heights = heightRange ? [heightRange.low, heightRange.high] : [0]; for (const height of heights) { accumulateFrustumPlaneDepthRange(frustum, this.getPlane(height), viewRotation, intersectRange, eyePoint); } } else { const minOffset = heightRange ? heightRange.low : 0, maxOffset = (heightRange ? heightRange.high : 0) + this.cartesianChordHeight; const radiusOffsets = [minOffset, maxOffset]; // If we are doing global scope then include minimum ellipsoid that represents the chordal approximation of the low level tiles. // this substantially expands the frustum so don't do it for non-global views, but this clipping out the low level tiles. if (doGlobalScope) radiusOffsets.push(minOffset - this.maxGeometryChordHeight); const toView = core_geometry_1.Transform.createRefs(core_geometry_1.Point3d.createZero(), viewRotation); const eyePoint4d = eyePoint ? core_geometry_1.Point4d.createFromPointAndWeight(eyePoint, 1) : core_geometry_1.Point4d.createFromPointAndWeight(viewZ, 0); for (const radiusOffset of radiusOffsets) { const ellipsoid = this.getEarthEllipsoid(radiusOffset); const isInside = eyePoint && ellipsoid.worldToLocal(eyePoint).magnitude() < 1.0; const center = ellipsoid.localToWorld(scratchZeroPoint, scratchCenterPoint); const clipPlaneCount = clipPlanes.planes.length; // Extrema... let angles, extremaPoint; if (undefined !== (angles = ellipsoid.surfaceNormalToAngles(viewZ)) && undefined !== (extremaPoint = ellipsoid.radiansToPoint(angles.longitudeRadians, angles.latitudeRadians)) && (eyePoint === undefined || viewZ.dotProductStartEnd(extremaPoint, eyePoint) > 0) && clipPlanes.classifyPointContainment([extremaPoint], false) !== core_geometry_1.ClipPlaneContainment.StronglyOutside) doAccumulate(extremaPoint); if (isInside) { if (eyePoint) doAccumulate(eyePoint); } else { const silhouette = ellipsoid.silhouetteArc(eyePoint4d); if (silhouette !== undefined) { silhouette.perpendicularVector.clone(scratchSilhouetteNormal); // Push the silhouette plane as clip so that we do not include geometry at other side of ellipsoid. // First make sure that it is pointing in the right direction. if (eyePoint) { // Clip toward eye. if (scratchSilhouetteNormal.dotProduct(viewZ) < 0) scratchSilhouetteNormal.negate(scratchSilhouetteNormal); } else { /* If parallel projection - clip toward side of ellipsoid with BIM geometry */ if (core_geometry_1.Vector3d.createStartEnd(silhouette.center, bimRange.center).dotProduct(scratchSilhouetteNormal) < 0) scratchSilhouetteNormal.negate(scratchSilhouetteNormal); } clipPlanes.planes.push(core_geometry_1.ClipPlane.createNormalAndDistance(scratchSilhouetteNormal, scratchSilhouetteNormal.dotProduct(silhouette.center))); } else { clipPlanes.planes.push(core_geometry_1.ClipPlane.createNormalAndPoint(viewZ, center)); } } if (!isInside || radiusOffset === radiusOffsets[0]) { // Intersections of ellipsoid with frustum planes... const viewingInside = eyePoint !== undefined && viewZ.dotProduct(core_geometry_1.Vector3d.createStartEnd(center, eyePoint)) < 0; if (eyePoint === undefined || !isInside || viewingInside) { for (const clipPlane of clipPlanes.planes) { const plane = clipPlane.getPlane3d(); const arc = ellipsoid.createPlaneSection(plane); if (undefined !== arc) { arc.announceClipIntervals(clipPlanes, (a0, a1, cp) => { if (Math.abs(a1 - a0) < 1.0E-8) { doAccumulate(cp.fractionToPoint(a0)); // Tiny sweep - avoid problem with rangeMethod (not worth doing anyway). } else { const segment = cp.clonePartialCurve(a0, a1); if (segment !== undefined) segment.extendRange(intersectRange, toView); } }); } } } // Intersections of the cartesian region with frustum planes. scratchCartoRectangle.resize(0); scratchCartoRectangle.push({ x: cartoRange.low.x, y: cartoRange.low.y, z: radiusOffset }); scratchCartoRectangle.push({ x: cartoRange.high.x, y: cartoRange.low.y, z: radiusOffset }); scratchCartoRectangle.push({ x: cartoRange.high.x, y: cartoRange.high.y, z: radiusOffset }); scratchCartoRectangle.push({ x: cartoRange.low.x, y: cartoRange.high.y, z: radiusOffset }); scratchCartoRectangle.push({ x: cartoRange.low.x, y: cartoRange.low.y, z: radiusOffset }); clipPlanes.clipConvexPolygonInPlace(scratchCartoRectangle, scratchWorkArray); for (let i = 0; i < scratchCartoRectangle.length; i++) doAccumulate(scratchCartoRectangle.getPoint3dAtUncheckedPointIndex(i)); while (clipPlanes.planes.length > clipPlaneCount) // Remove pushed silhouette plane. clipPlanes.planes.pop(); } } } if (intersectRange.zLength() < 5) { // For the case where the fitted depth is small (less than 5 meters) we must be viewing planar projection or the // planar portion of the iModel in plan view. In this case use a constant (arbitrarily 100 meters) depth so that the frustum // Z is doesn't change and cause nearly planar geometry to jitter in Z buffer. const zCenter = (intersectRange.low.z + intersectRange.high.z) / 2; const zExpand = 50; return core_geometry_1.Range1d.createXX(zCenter - zExpand, zCenter + zExpand); } else { const diagonal = intersectRange.diagonal(scratchVector).magnitudeXY(); const expansion = diagonal * .01; return core_geometry_1.Range1d.createXX(intersectRange.low.z - expansion, intersectRange.high.z + expansion); } } addFrustumDecorations(builder, frustum) { if (this.geometry instanceof core_geometry_1.Ellipsoid) { const ellipsoid = this.geometry; const clipPlanes = frustum.getRangePlanes(false, false, 0); const viewRotation = frustum.getRotation(); const eyePoint = frustum.getEyePoint(scratchEyePoint); const viewZ = viewRotation.getRow(2); const eyePoint4d = eyePoint ? core_geometry_1.Point4d.createFromPointAndWeight(eyePoint, 1) : core_geometry_1.Point4d.createFromPointAndWeight(viewZ, 0); const isInside = eyePoint && ellipsoid.worldToLocal(eyePoint).magnitude() < 1.0; const center = ellipsoid.localToWorld(scratchZeroPoint, scratchCenterPoint); const cartoRange = this.cartesianTransitionRange; if (!isInside) { const silhouette = ellipsoid.silhouetteArc(eyePoint4d); if (silhouette !== undefined) { silhouette.perpendicularVector.clone(scratchSilhouetteNormal); if (scratchSilhouetteNormal.dotProduct(viewZ) < 0) scratchSilhouetteNormal.negate(scratchSilhouetteNormal); clipPlanes.planes.push(core_geometry_1.ClipPlane.createNormalAndDistance(scratchSilhouetteNormal, scratchSilhouetteNormal.dotProduct(silhouette.center))); } else { clipPlanes.planes.push(core_geometry_1.ClipPlane.createNormalAndPoint(viewZ, center)); } const ellipsoidColor = core_common_1.ColorDef.create(core_common_1.ColorByName.yellow); builder.setSymbology(ellipsoidColor, ellipsoidColor, 1, core_common_1.LinePixels.Code2); for (const clipPlane of clipPlanes.planes) { const plane = clipPlane.getPlane3d(); const arc = ellipsoid.createPlaneSection(plane); if (undefined !== arc) { arc.announceClipIntervals(clipPlanes, (a0, a1, cp) => { const segment = cp.clonePartialCurve(a0, a1); if (segment !== undefined) builder.addArc(segment, false, false); }); } // Intersections of the cartesian region with frustum planes. scratchCartoRectangle.resize(0); scratchCartoRectangle.push({ x: cartoRange.low.x, y: cartoRange.low.y, z: 0 }); scratchCartoRectangle.push({ x: cartoRange.high.x, y: cartoRange.low.y, z: 0 }); scratchCartoRectangle.push({ x: cartoRange.high.x, y: cartoRange.high.y, z: 0 }); scratchCartoRectangle.push({ x: cartoRange.low.x, y: cartoRange.high.y, z: 0 }); scratchCartoRectangle.push({ x: cartoRange.low.x, y: cartoRange.low.y, z: 0 }); clipPlanes.clipConvexPolygonInPlace(scratchCartoRectangle, scratchWorkArray); if (scratchCartoRectangle.length > 0) { builder.addLineString(scratchCartoRectangle.getPoint3dArray()); } } } } } } exports.BackgroundMapGeometry = BackgroundMapGeometry; /** Calculate the ECEF to database (IModel) coordinate transform at a provided location, using the GCS of the iModel. * The transform will exactly represent the GCS at the provided location. * @public */ async function calculateEcefToDbTransformAtLocation(originIn, iModel) { const geoConverter = iModel.noGcsDefined ? undefined : iModel.geoServices.getConverter("WGS84"); if (geoConverter === undefined) return undefined; const origin = core_geometry_1.Point3d.create(originIn.x, originIn.y, 0); // Always Test at zero. const eastPoint = origin.plusXYZ(1, 0, 0); const northPoint = origin.plusXYZ(0, 1, 0); const response = await geoConverter.getGeoCoordinatesFromIModelCoordinates([origin, northPoint, eastPoint]); if (response.geoCoords[0].s !== core_common_1.GeoCoordStatus.Success || response.geoCoords[1].s !== core_common_1.GeoCoordStatus.Success || response.geoCoords[2].s !== core_common_1.GeoCoordStatus.Success) return undefined; const geoOrigin = core_geometry_1.Point3d.fromJSON(response.geoCoords[0].p); const geoNorth = core_geometry_1.Point3d.fromJSON(response.geoCoords[1].p); const geoEast = core_geometry_1.Point3d.fromJSON(response.geoCoords[2].p); const ecefOrigin = core_common_1.Cartographic.fromDegrees({ longitude: geoOrigin.x, latitude: geoOrigin.y, height: geoOrigin.z }).toEcef(); const ecefNorth = core_common_1.Cartographic.fromDegrees({ longitude: geoNorth.x, latitude: geoNorth.y, height: geoNorth.z }).toEcef(); const ecefEast = core_common_1.Cartographic.fromDegrees({ longitude: geoEast.x, latitude: geoEast.y, height: geoEast.z }).toEcef(); const xVector = core_geometry_1.Vector3d.createStartEnd(ecefOrigin, ecefEast); const yVector = core_geometry_1.Vector3d.createStartEnd(ecefOrigin, ecefNorth); const zVector = xVector.unitCrossProduct(yVector); if (undefined === zVector) { (0, core_bentley_1.assert)(false); // Should never occur. return undefined; } const matrix = core_geometry_1.Matrix3d.createColumns(xVector, yVector, zVector); if (matrix === undefined) return undefined; const inverse = matrix.inverse(); if (inverse === undefined) { (0, core_bentley_1.assert)(false); // Should never occur. return undefined; } return core_geometry_1.Transform.createMatrixPickupPutdown(matrix, origin, ecefOrigin).inverse(); } //# sourceMappingURL=BackgroundMapGeometry.js.map