UNPKG

@itwin/core-frontend

Version:
252 lines • 14.4 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 Tiles */ Object.defineProperty(exports, "__esModule", { value: true }); exports.WebMercatorTilingScheme = exports.WebMercatorProjection = exports.GeographicTilingScheme = exports.MapTilingScheme = void 0; 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("../internal"); /** A scheme for converting between two representations of the surface of the Earth: an ellipsoid and a rectangular [tiled map](https://en.wikipedia.org/wiki/Tiled_web_map). * Positions on the surface of the ellipsoid are expressed in [Cartographic]($common) coordinates. * Rectangular [[MapTile]]s are projected onto this ellipsoid by the tiling scheme. Tile coordinates are represented by [[QuadId]]s. * * The tiling scheme represents the (x,y) coordinates of its tiles as fractions in [0,1] along the X and Y axes. * An X fraction of 0 corresponds to the easternmost longitude and an X fraction of 1 to the westernmost longitude. * The scheme can choose to correlate a Y fraction of 0 with either the north or south pole, as specified by [[rowZeroAtNorthPole]]. * Implementing a tiling scheme only requires implementing the abstract method [[yFractionToLatitude]] and its inverse, [[latitudeToYFraction]]. * @public */ class MapTilingScheme { /** If true, the fractional Y coordinate 0 corresponds to the north pole and 1 to the south pole; otherwise, * 0 corresponds to the south pole and 1 to the north. */ rowZeroAtNorthPole; /** The number of tiles in the X direction at level 0 of the quad tree. */ numberOfLevelZeroTilesX; /** The number of tiles in the Y direction at level 0 of the quad tree. */ numberOfLevelZeroTilesY; _scratchFraction = core_geometry_1.Point2d.createZero(); _scratchPoint2d = core_geometry_1.Point2d.createZero(); /** Convert a longitude in [-pi, pi] radisn to a fraction in [0, 1] along the X axis. */ longitudeToXFraction(longitude) { return longitude / core_geometry_1.Angle.pi2Radians + .5; } /** Convert a fraction in [0, 1] along the X axis into a longitude in [-pi, pi] radians. */ xFractionToLongitude(xFraction) { return core_geometry_1.Angle.pi2Radians * (xFraction - .5); } constructor(numberOfLevelZeroTilesX, numberOfLevelZeroTilesY, rowZeroAtNorthPole) { this.rowZeroAtNorthPole = rowZeroAtNorthPole; this.numberOfLevelZeroTilesX = numberOfLevelZeroTilesX; this.numberOfLevelZeroTilesY = numberOfLevelZeroTilesY; } /** The total number of tiles in the X direction at the specified level of detail. * @param level The level of detail, with 0 corresponding to the root tile. */ getNumberOfXTilesAtLevel(level) { return level < 0 ? 1 : this.numberOfLevelZeroTilesX << level; } /** The total number of tiles in the Y direction at the specified level of detail. * @param level The level of detail, with 0 corresponding to the root tile. */ getNumberOfYTilesAtLevel(level) { return level < 0 ? 1 : this.numberOfLevelZeroTilesY << level; } /** @alpha */ get rootLevel() { return this.numberOfLevelZeroTilesX > 1 || this.numberOfLevelZeroTilesY > 1 ? -1 : 0; } /** @alpha */ getNumberOfXChildrenAtLevel(level) { return level === 0 ? this.numberOfLevelZeroTilesX : 2; } /** @alpha */ getNumberOfYChildrenAtLevel(level) { return level === 0 ? this.numberOfLevelZeroTilesY : 2; } /** Given the X component and level of a [[QuadId]], convert it to a fractional distance along the X axis. */ tileXToFraction(x, level) { return x / this.getNumberOfXTilesAtLevel(level); } /** Given the Y component and level of a [[QuadId]], convert it to a fractional distance along the Y axis. */ tileYToFraction(y, level) { return y / this.getNumberOfYTilesAtLevel(level); } /** Given a fractional distance along the X axis and a level of the quad tree, compute the X component of the corresponding [[QuadId]]. */ xFractionToTileX(xFraction, level) { const nTiles = this.getNumberOfXTilesAtLevel(level); return Math.min(Math.floor(xFraction * nTiles), nTiles - 1); } /** Given a fractional distance along the Y axis and a level of the quad tree, compute the Y component of the corresponding [[QuadId]]. */ yFractionToTileY(yFraction, level) { const nTiles = this.getNumberOfYTilesAtLevel(level); return Math.min(Math.floor(yFraction * nTiles), nTiles - 1); } /** Given the X component and level of a [[QuadId]], compute its longitude in [-pi, pi] radians. */ tileXToLongitude(x, level) { return this.xFractionToLongitude(this.tileXToFraction(x, level)); } /** Given the Y component and level of a [[QuadId]], compute its latitude in [-pi/2, pi/2] radians. */ tileYToLatitude(y, level) { return this.yFractionToLatitude(this.tileYToFraction(y, level)); } /** Given the components of a [[QuadId]], compute its fractional coordinates in the XY plane. */ tileXYToFraction(x, y, level, result) { if (undefined === result) result = core_geometry_1.Point2d.createZero(); result.x = this.tileXToFraction(x, level); result.y = this.tileYToFraction(y, level); return result; } /** Given the components of a [[QuadId]] and an elevation, compute the corresponding [Cartographic]($common) position. * @param x The X component of the QuadId. * @param y The Y component of the QuadId. * @param level The level component of the QuadId. * @param height The elevation above the ellipsoid. * @returns the corresponding cartographic position. */ tileXYToCartographic(x, y, level, result, height = 0) { const pt = this.tileXYToFraction(x, y, level, this._scratchFraction); return this.fractionToCartographic(pt.x, pt.y, result, height); } /** Given the components of a [[QuadId]], compute the corresponding region of the Earth's surface. */ tileXYToRectangle(x, y, level, result) { if (level < 0) return internal_1.MapCartoRectangle.createMaximum(); return internal_1.MapCartoRectangle.fromRadians(this.tileXToLongitude(x, level), this.tileYToLatitude(this.rowZeroAtNorthPole ? (y + 1) : y, level), this.tileXToLongitude(x + 1, level), this.tileYToLatitude(this.rowZeroAtNorthPole ? y : (y + 1), level), result); } /** Returns true if the tile at the specified X coordinate and level is adjacent to the north pole. */ tileBordersNorthPole(row, level) { return this.rowZeroAtNorthPole ? this.tileYToFraction(row, level) === 0.0 : this.tileYToFraction(row + 1, level) === 1.0; } /** Returns true if the tile at the specified X coordinate and level is adjacent to the south pole. */ tileBordersSouthPole(row, level) { return this.rowZeroAtNorthPole ? this.tileYToFraction(row + 1, level) === 1.0 : this.tileYToFraction(row, level) === 0.0; } /** Given a cartographic position, compute the corresponding position on the surface of the Earth as fractional distances along the * X and Y axes. */ cartographicToTileXY(carto, level, result) { const fraction = this.cartographicToFraction(carto.latitude, carto.longitude, this._scratchPoint2d); return core_geometry_1.Point2d.create(this.xFractionToTileX(fraction.x, level), this.yFractionToTileY(fraction.y, level), result); } /** Given fractional coordinates in the XY plane and an elevation, compute the corresponding cartographic position. */ fractionToCartographic(xFraction, yFraction, result, height = 0) { result.longitude = this.xFractionToLongitude(xFraction); result.latitude = this.yFractionToLatitude(yFraction); result.height = height; return result; } /** Given a cartographic location on the surface of the Earth, convert it to fractional coordinates in the XY plane. */ cartographicToFraction(latitudeRadians, longitudeRadians, result) { result.x = this.longitudeToXFraction(longitudeRadians); result.y = this.latitudeToYFraction(latitudeRadians); return result; } /** @alpha */ ecefToPixelFraction(point, applyTerrain) { const cartoGraphic = core_common_1.Cartographic.fromEcef(point); if (!cartoGraphic) return undefined; return core_geometry_1.Point3d.create(this.longitudeToXFraction(cartoGraphic.longitude), this.latitudeToYFraction(cartoGraphic.latitude), applyTerrain ? cartoGraphic.height : 0); } /** @alpha */ computeMercatorFractionToDb(ecefToDb, bimElevationOffset, iModel, applyTerrain) { const dbToEcef = (0, core_bentley_1.expectDefined)(ecefToDb.inverse()); const projectCenter = core_geometry_1.Point3d.create(iModel.projectExtents.center.x, iModel.projectExtents.center.y, bimElevationOffset); const projectEast = projectCenter.plusXYZ(1, 0, 0); const projectNorth = projectCenter.plusXYZ(0, 1, 0); const mercatorOrigin = this.ecefToPixelFraction(dbToEcef.multiplyPoint3d(projectCenter), applyTerrain); const mercatorX = this.ecefToPixelFraction(dbToEcef.multiplyPoint3d(projectEast), applyTerrain); const mercatorY = this.ecefToPixelFraction(dbToEcef.multiplyPoint3d(projectNorth), applyTerrain); if (!mercatorOrigin || !mercatorX || !mercatorY) return core_geometry_1.Transform.createIdentity(); const deltaX = core_geometry_1.Vector3d.createStartEnd(mercatorOrigin, mercatorX); const deltaY = core_geometry_1.Vector3d.createStartEnd(mercatorOrigin, mercatorY); const matrix = core_geometry_1.Matrix3d.createColumns(deltaX, deltaY, core_geometry_1.Vector3d.create(0, 0, 1)); const dbToMercator = core_geometry_1.Transform.createMatrixPickupPutdown(matrix, projectCenter, mercatorOrigin); const mercatorToDb = dbToMercator.inverse(); return mercatorToDb === undefined ? core_geometry_1.Transform.createIdentity() : mercatorToDb; } /** @alpha */ yFractionFlip(fraction) { return this.rowZeroAtNorthPole ? (1.0 - fraction) : fraction; } } exports.MapTilingScheme = MapTilingScheme; /** A [[MapTilingScheme]] using a simple geographic projection by which longitude and latitude are mapped directly to X and Y. * This projection is commonly known as "geographic", "equirectangular", "equidistant cylindrical", or "plate carrée". * @beta */ class GeographicTilingScheme extends MapTilingScheme { constructor(numberOfLevelZeroTilesX = 2, numberOfLevelZeroTilesY = 1, rowZeroAtNorthPole = false) { super(numberOfLevelZeroTilesX, numberOfLevelZeroTilesY, rowZeroAtNorthPole); } /** Implements [[MapTilingScheme.yFractionToLatitude]]. */ yFractionToLatitude(yFraction) { return Math.PI * (this.yFractionFlip(yFraction) - .5); } /** Implements [[MapTilingScheme.latitudeToYFraction]]. */ latitudeToYFraction(latitude) { return this.yFractionFlip(.5 + latitude / Math.PI); } } exports.GeographicTilingScheme = GeographicTilingScheme; /** @alpha */ class WebMercatorProjection { /** * Converts a Mercator angle, in the range -PI to PI, to a geodetic latitude * in the range -PI/2 to PI/2. * * @param {Number} mercatorAngle The angle to convert. * @returns {Number} The geodetic latitude in radians. */ static mercatorAngleToGeodeticLatitude(mercatorAngle) { return core_geometry_1.Angle.piOver2Radians - (2.0 * Math.atan(Math.exp(-mercatorAngle))); } static maximumLatitude = WebMercatorProjection.mercatorAngleToGeodeticLatitude(core_geometry_1.Angle.piRadians); static geodeticLatitudeToMercatorAngle(latitude) { // Clamp the latitude coordinate to the valid Mercator bounds. if (latitude > WebMercatorProjection.maximumLatitude) latitude = WebMercatorProjection.maximumLatitude; else if (latitude < -WebMercatorProjection.maximumLatitude) latitude = -WebMercatorProjection.maximumLatitude; const sinLatitude = Math.sin(latitude); return 0.5 * Math.log((1.0 + sinLatitude) / (1.0 - sinLatitude)); } } exports.WebMercatorProjection = WebMercatorProjection; /** A [[MapTilingScheme]] using the [EPSG:3857](https://en.wikipedia.org/wiki/Web_Mercator_projection) projection. * This scheme is used by most [tiled web maps](https://en.wikipedia.org/wiki/Tiled_web_map), including Bing Maps and Google Maps. * @beta */ class WebMercatorTilingScheme extends MapTilingScheme { constructor(numberOfLevelZeroTilesX = 1, numberOfLevelZeroTilesY = 1, rowZeroAtNorthPole = true) { super(numberOfLevelZeroTilesX, numberOfLevelZeroTilesY, rowZeroAtNorthPole); } /** Implements [[MapTilingScheme.yFractionToLatitude]]. */ yFractionToLatitude(yFraction) { const mercatorAngle = core_geometry_1.Angle.pi2Radians * (this.rowZeroAtNorthPole ? (.5 - yFraction) : (yFraction - .5)); return WebMercatorProjection.mercatorAngleToGeodeticLatitude(mercatorAngle); } /** Implements [[MapTilingScheme.latitudeToYFraction. */ latitudeToYFraction(latitude) { // Clamp the latitude coordinate to the valid Mercator bounds. if (latitude > WebMercatorProjection.maximumLatitude) { latitude = WebMercatorProjection.maximumLatitude; } else if (latitude < -WebMercatorProjection.maximumLatitude) { latitude = -WebMercatorProjection.maximumLatitude; } const sinLatitude = Math.sin(latitude); return (0.5 - Math.log((1.0 + sinLatitude) / (1.0 - sinLatitude)) / (4.0 * core_geometry_1.Angle.piRadians)); // https://msdn.microsoft.com/en-us/library/bb259689.aspx } } exports.WebMercatorTilingScheme = WebMercatorTilingScheme; //# sourceMappingURL=MapTilingScheme.js.map