@itwin/core-frontend
Version:
iTwin.js frontend components
252 lines • 14.4 kB
JavaScript
"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