proj/cube.tstypescript
/**
* @module og/proj/cube
*/
import { LonLat } from "../LonLat";
import { DEGREES, PI_TWO, RADIANS } from "../math";
import { E, N, OPSIDE, S, W } from "../quadTree/quadTree";
import { Proj, Units } from "./Proj";
export const CUBE_TOP = 0;
export const CUBE_FRONT = 1;
export const CUBE_RIGHT = 2;
export const CUBE_BACK = 3;
export const CUBE_LEFT = 4;
export const CUBE_BOTTOM = 5;
/**
* Center, east and north directions of the cube faces. The faces from CUBE_FRONT to CUBE_LEFT lie on the equator at
* longitudes 0, 90, 180 and -90.
*/
export const CUBE_FACES = [
[0, 0, 1, 1, 0, 0, 0, 1, 0],
[1, 0, 0, 0, 1, 0, 0, 0, 1],
[0, 1, 0, -1, 0, 0, 0, 0, 1],
[-1, 0, 0, 0, -1, 0, 0, 0, 1],
[0, -1, 0, 1, 0, 0, 0, 0, 1],
[0, 0, -1, 1, 0, 0, 0, -1, 0]
];
/**
* The face and its side behind the N, E, S and W sides of each face.
*/
export const CUBE_FACE_LINKS: [number, number][][] = [
// CUBE_TOP
[
[CUBE_RIGHT, N],
[CUBE_FRONT, N],
[CUBE_LEFT, N],
[CUBE_BACK, N]
],
// CUBE_FRONT
[
[CUBE_TOP, E],
[CUBE_RIGHT, W],
[CUBE_BOTTOM, E],
[CUBE_LEFT, E]
],
// CUBE_RIGHT
[
[CUBE_TOP, N],
[CUBE_BACK, W],
[CUBE_BOTTOM, S],
[CUBE_FRONT, E]
],
// CUBE_BACK
[
[CUBE_TOP, W],
[CUBE_LEFT, W],
[CUBE_BOTTOM, W],
[CUBE_RIGHT, E]
],
// CUBE_LEFT
[
[CUBE_TOP, S],
[CUBE_FRONT, W],
[CUBE_BOTTOM, N],
[CUBE_BACK, E]
],
// CUBE_BOTTOM
[
[CUBE_LEFT, S],
[CUBE_FRONT, S],
[CUBE_RIGHT, S],
[CUBE_BACK, S]
]
];
/**
* Sides of the neighbors of a tile, see Segment.opside. First index is the face, second is the mask of the tile sides
* that lie on the edges of the face, a bit for each of {N,E,S,W}.
*/
export const CUBE_OPSIDE = CUBE_FACE_LINKS.map((links) =>
Array.from({ length: 16 }, (_, edges) =>
[N, E, S, W].map((side) => (edges & (1 << side) ? links[side][1] : OPSIDE[side]))
)
);
/**
* The root of a face has the node id of the offset plus the face.
* So the faces have no common ids.
*/
export const CUBE_NODE_ID_OFFSET = 5;
const MAX_POSITION = 1.0 - Math.pow(2, -50);
// A position on a face, from 0 to 1, as a coordinate on the plane of the face, from -1 to 1, and back
const toEquiangularPlane = (position: number) => Math.tan(PI_TWO * (position - 0.5));
const fromEquiangularPlane = (coordinate: number) => Math.atan(coordinate) / PI_TWO + 0.5;
const toGnomonicPlane = (position: number) => 2.0 * position - 1.0;
const fromGnomonicPlane = (coordinate: number) => (coordinate + 1.0) * 0.5;
/**
* Projection of a cube inflated to the ellipsoid. Native coordinates of a point: lon is the face index plus the
* position from west to east, lat is the position from south to north, both positions are from 0 to 1.
* @class
* @extends {Proj}
* @param {string} code - Projection code.
* @param {boolean} equiangular - True when equal steps on a face are equal angles, false when they are equal
* distances on the plane of the face, as in a common cube map.
*/
export class CubeProj extends Proj {
public readonly equiangular: boolean;
protected readonly _toPlane: (position: number) => number;
protected readonly _fromPlane: (coordinate: number) => number;
constructor(code: string, equiangular: boolean) {
super({ code, units: Units.DEGREES });
this.equiangular = equiangular;
this._toPlane = equiangular ? toEquiangularPlane : toGnomonicPlane;
this._fromPlane = equiangular ? fromEquiangularPlane : fromGnomonicPlane;
}
/**
* Returns the coordinate on the plane of a face, from -1 to 1, of the position on it, from 0 to 1.
* @public
* @param {number} position - Position on the face.
* @returns {number} -
*/
public toPlane(position: number): number {
return this._toPlane(position);
}
/**
* Returns the position on a face, from 0 to 1, of the coordinate on its plane, from -1 to 1. It goes on beyond
* the face for a coordinate beyond it.
* @public
* @param {number} coordinate - Coordinate on the face plane.
* @returns {number} -
*/
public fromPlane(coordinate: number): number {
return this._fromPlane(coordinate);
}
/**
* Returns the geodetic coordinates of the point on the cube. The direction of the cube point is the surface
* normal of the ellipsoid in it.
* @public
* @param {number} lon - Native longitude.
* @param {number} lat - Native latitude.
* @param {number} [face] - Face index, it is needed on the east edge of a face: the longitude is the next face
* there.
* @returns {LonLat} -
*/
public toLonLat(lon: number, lat: number, face: number = Math.floor(lon)): LonLat {
const f = CUBE_FACES[face],
a = this._toPlane(lon - face),
b = this._toPlane(lat),
x = f[0] + a * f[3] + b * f[6],
y = f[1] + a * f[4] + b * f[7],
z = f[2] + a * f[5] + b * f[8];
return new LonLat(Math.atan2(y, x) * DEGREES, Math.atan2(z, Math.sqrt(x * x + y * y)) * DEGREES);
}
/**
* Returns the native coordinates of the geodetic point.
* @public
* @param {LonLat} lonLat - Geodetic coordinates.
* @returns {LonLat} -
*/
public fromLonLat(lonLat: LonLat): LonLat {
const lon = lonLat.lon * RADIANS,
lat = lonLat.lat * RADIANS,
c = Math.cos(lat),
x = c * Math.cos(lon),
y = c * Math.sin(lon),
z = Math.sin(lat),
ax = Math.abs(x),
ay = Math.abs(y),
az = Math.abs(z);
let face;
if (az >= ax && az >= ay) {
face = z > 0 ? CUBE_TOP : CUBE_BOTTOM;
} else if (ax >= ay) {
face = x > 0 ? CUBE_FRONT : CUBE_BACK;
} else {
face = y > 0 ? CUBE_RIGHT : CUBE_LEFT;
}
const f = CUBE_FACES[face],
d = x * f[0] + y * f[1] + z * f[2],
a = (x * f[3] + y * f[4] + z * f[5]) / d,
b = (x * f[6] + y * f[7] + z * f[8]) / d;
return new LonLat(face + Math.min(this._fromPlane(a), MAX_POSITION), this._fromPlane(b));
}
}
/**
* The cube of a common cube map: the planes of the faces are split evenly.
* @type {CubeProj}
*/
export const cube = new CubeProj("cube", false);
/**
* The cube with tiles of about the same size all over the planet.
* @type {CubeProj}
*/
export const cubeEquiangular = new CubeProj("cubeEquiangular", true);
/**
* Returns true for the cube projections.
* @param {Proj} proj - Projection.
* @returns {boolean} -
*/
export function isCubeProj(proj: Proj): boolean {
return proj.equal(cube) || proj.equal(cubeEquiangular);
}