diff --git a/src/Core/Geographic/Coordinates.js b/src/Core/Geographic/Coordinates.ts similarity index 55% rename from src/Core/Geographic/Coordinates.js rename to src/Core/Geographic/Coordinates.ts index 1788f889f1..86068a781d 100644 --- a/src/Core/Geographic/Coordinates.js +++ b/src/Core/Geographic/Coordinates.ts @@ -3,18 +3,25 @@ import proj4 from 'proj4'; import CRS from 'Core/Geographic/Crs'; import Ellipsoid from 'Core/Math/Ellipsoid'; -proj4.defs('EPSG:4978', '+proj=geocent +datum=WGS84 +units=m +no_defs'); +import type { ProjectionLike } from './Crs'; const ellipsoid = new Ellipsoid(); -const projectionCache = {}; +const projectionCache: Record> = {}; const v0 = new THREE.Vector3(); const v1 = new THREE.Vector3(); -let coord0; -let coord1; +let coord0: Coordinates; +let coord1: Coordinates; -function proj4cache(crsIn, crsOut) { +export interface CoordinatesLike { + readonly crs: string; + readonly x: number; + readonly y: number; + readonly z: number; +} + +function proj4cache(crsIn: string, crsOut: string): proj4.Converter { if (!projectionCache[crsIn]) { projectionCache[crsIn] = {}; } @@ -43,34 +50,39 @@ function proj4cache(crsIn, crsOut) { * @example * // Declare EPSG:3946 with proj4 * itowns.proj4.defs('EPSG:3946', '+proj=lcc +lat_1=45.25 +lat_2=46.75 +lat_0=46 +lon_0=3 +x_0=1700000 +y_0=5200000 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs'); - * - * @property {boolean} isCoordinates - Used to checkout whether this coordinates - * is a Coordinates. Default is true. You should not change this, as it is used - * internally for optimisation. - * @property {string} crs - A supported crs by default in - * [`proj4js`](https://github.com/proj4js/proj4js#named-projections), or an - * added crs to `proj4js` (using `proj4.defs`). Note that `EPSG:4978` is also - * supported by default in itowns. - * @property {number} x - The first value of the coordinate. - * @property {number} y - The second value of the coordinate. - * @property {number} z - The third value of the coordinate. - * @property {number} latitude - The first value of the coordinate. - * @property {number} longitude - The second value of the coordinate. - * @property {number} altitude - The third value of the coordinate. - * @property {THREE.Vector3} geodesicNormal - The geodesic normal of the - * coordinate. */ class Coordinates { /** - * @param {string} crs - A supported Coordinate Reference System. - * @param {number|Array|Coordinates|THREE.Vector3} [v0=0] - - * x or longitude value, or a more complex one: it can be an array of three - * numbers, being x/lon, y/lat, z/alt, or it can be `THREE.Vector3`. It can - * also simply be a Coordinates. - * @param {number} [v1=0] - y or latitude value. - * @param {number} [v2=0] - z or altitude value. + * Used to checkout whether this coordinates is a Coordinates. Default is + * true. You should not change this, as it is used internally for + * optimisation. + */ + readonly isCoordinates: boolean; + /** + * A supported crs by default in + * [`proj4js`](https://github.com/proj4js/proj4js#named-projections), or an + * added crs to `proj4js` (using `proj4.defs`). Note that `EPSG:4978` is + * also supported by default in itowns. + */ + crs: ProjectionLike; + + /** The first value of the coordinate. */ + x: number; + /** The second value of the coordinate. */ + y: number; + /** The third value of the coordinate. */ + z: number; + + private _normal: THREE.Vector3; + private _normalNeedsUpdate: boolean; + + /** + * @param crs - A supported Coordinate Reference System. + * @param x - x or longitude value. + * @param y - y or latitude value. + * @param z - z or altitude value. */ - constructor(crs, v0 = 0, v1 = 0, v2 = 0) { + constructor(crs: ProjectionLike, x: number = 0, y: number = 0, z: number = 0) { this.isCoordinates = true; CRS.isValid(crs); @@ -85,20 +97,24 @@ class Coordinates { // Normal this._normal = new THREE.Vector3(); - if (v0.length > 0) { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + if ((v0 as any).length > 0) { // deepscan-disable-line console.warn( 'Deprecated Coordinates#constructor(string, number[]),', - 'use new Coordinates(string).setFromArray(number[]) instead.', + 'use `new Coordinates(string).setFromArray(number[])` instead.', ); - this.setFromArray(v0); - } else if (v0.isVector3 || v0.isCoordinates) { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + this.setFromArray(x as any); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + } else if ((x as any).isVector3 || (x as any).isCoordinates) { console.warn( 'Deprecated Coordinates#constructor(string, Vector3),', - 'use new Coordinates(string).setFromVector3(Vector3) instead.', + 'use `new Coordinates(string).setFromVector3(Vector3)` instead.', ); - this.setFromVector3(v0); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + this.setFromVector3(x as any); } else { - this.setFromValues(v0, v1, v2); + this.setFromValues(x, y, z); } this._normalNeedsUpdate = true; @@ -106,9 +122,9 @@ class Coordinates { /** * Sets the Coordinate Reference System. - * @param {String} crs Coordinate Reference System (e.g. 'EPSG:4978') + * @param crs - Coordinate Reference System (e.g. 'EPSG:4978') */ - setCrs(crs) { + setCrs(crs: ProjectionLike): this { CRS.isValid(crs); this.crs = crs; return this; @@ -117,16 +133,16 @@ class Coordinates { /** * Set the values of this Coordinates. * - * @param {number} [v0=0] - x or longitude value. - * @param {number} [v1=0] - y or latitude value. - * @param {number} [v2=0] - z or altitude value. + * @param x - x or longitude value. + * @param y - y or latitude value. + * @param z - z or altitude value. * - * @return {Coordinates} This Coordinates. + * @returns This Coordinates. */ - setFromValues(v0 = 0, v1 = 0, v2 = 0) { - this.x = v0 == undefined ? 0 : v0; - this.y = v1 == undefined ? 0 : v1; - this.z = v2 == undefined ? 0 : v2; + setFromValues(x: number = 0, y: number = 0, z: number = 0): this { + this.x = x; + this.y = y; + this.z = z; this._normalNeedsUpdate = true; return this; @@ -135,48 +151,50 @@ class Coordinates { /** * Set the values of this Coordinates from an array. * - * @param {Array} array - An array of number to assign to the - * Coordinates. - * @param {number} [offset] - Optional offset into the array. + * @param array - An array of number to assign to the Coordinates. + * @param offset - Optional offset into the array. * - * @return {Coordinates} This Coordinates. + * @returns This Coordinates. */ - setFromArray(array, offset = 0) { - return this.setFromValues(array[offset], array[offset + 1], array[offset + 2]); + setFromArray(array: number[], offset: number = 0): this { + return this.setFromValues( + array[offset], + array[offset + 1], + array[offset + 2], + ); } /** * Set the values of this Coordinates from a `THREE.Vector3` or an `Object` * having `x/y/z` properties, like a `Coordinates`. * - * @param {THREE.Vector3|Coordinates} v0 - The object to read the values - * from. + * @param v - The object to read the values from. * - * @return {Coordinates} This Coordinates. + * @returns This Coordinates. */ - setFromVector3(v0) { - return this.setFromValues(v0.x, v0.y, v0.z); + setFromVector3(v: THREE.Vector3Like): this { + return this.setFromValues(v.x, v.y, v.z); } /** * Returns a new Coordinates with the same values as this one. It will * instantiate a new Coordinates with the same CRS as this one. * - * @return {Coordinates} The target with its new coordinates. + * @returns The target with its new coordinates. */ - clone() { - return new Coordinates(this.crs, this); + clone(): Coordinates { + return new Coordinates(this.crs, this.x, this.y, this.z); } /** * Copies the values of the passed Coordinates to this one. The CRS is * however not copied. * - * @param {Coordinates} src - The source to copy from. + * @param src - The source to copy from. * - * @return {Coordinates} This Coordinates. + * @returns This Coordinates. */ - copy(src) { + copy(src: CoordinatesLike): this { this.crs = src.crs; return this.setFromVector3(src); } @@ -197,6 +215,9 @@ class Coordinates { this.z = value; } + /** + * The geodesic normal of the coordinate. + */ get geodesicNormal() { if (this._normalNeedsUpdate) { this._normalNeedsUpdate = false; @@ -216,38 +237,36 @@ class Coordinates { /** * Return this Coordinates values into a `THREE.Vector3`. * - * @param {THREE.Vector3} [target] - The target to put the values in. If not - * specified, a new vector will be created. - * - * @return {THREE.Vector3} + * @param target - The target to put the values in. If not specified, a new + * vector will be created. */ - toVector3(target = new THREE.Vector3()) { + toVector3(target: THREE.Vector3 = new THREE.Vector3()): THREE.Vector3 { return target.copy(this); } /** * Copy values coordinates to array * - * @param {number[]} array - array to store this vector to. If this is not + * @param array - array to store this vector to. If this is not * provided a new array will be created. - * @param {number} [offset=0] - optional offset into the array. + * @param offset - optional offset into the array. * - * @return {number[]} Returns an array [x, y, z], or copies x, y and z into - * the provided array. + * @returns An array [x, y, z], or copies x, y and z into the provided + * array. */ - toArray(array = [], offset = 0) { + toArray(array: number[] = [], offset: number = 0): ArrayLike { return THREE.Vector3.prototype.toArray.call(this, array, offset); } /** * Calculate planar distance between this coordinates and `coord`. - * Planar distance is the straight-line euclidean distance calculated in a 2D cartesian coordinate system. - * - * @param {Coordinates} coord The coordinate - * @return {number} planar distance + * Planar distance is the straight-line euclidean distance calculated in a + * 2D cartesian coordinate system. * + * @param coord - The coordinate + * @returns planar distance */ - planarDistanceTo(coord) { + planarDistanceTo(coord: Coordinates): number { this.toVector3(v0).setZ(0); coord.toVector3(v1).setZ(0); return v0.distanceTo(v1); @@ -255,16 +274,13 @@ class Coordinates { /** * Calculate geodetic distance between this coordinates and `coord`. - * **Geodetic distance** is calculated in an ellispoid space as the shortest distance - * across the curved surface of the world. - * - * => As the crow flies/ Orthodromy - * - * @param {Coordinates} coord The coordinate - * @return {number} geodetic distance + * **Geodetic distance** is calculated in an ellispoid space as the shortest + * distance across the curved surface of the world. * + * @param coord - The coordinate + * @returns geodetic distance */ - geodeticDistanceTo(coord) { + geodeticDistanceTo(coord: Coordinates): number { this.as('EPSG:4326', coord0); coord.as('EPSG:4326', coord1); return ellipsoid.geodesicDistance(coord0, coord1); @@ -273,34 +289,36 @@ class Coordinates { /** * Calculate earth euclidean distance between this coordinates and `coord`. * - * @param {Coordinates} coord The coordinate - * @return {number} earth euclidean distance - * + * @param coord - The coordinate + * @returns earth euclidean distance */ - spatialEuclideanDistanceTo(coord) { + spatialEuclideanDistanceTo(coord: Coordinates): number { this.as('EPSG:4978', coord0).toVector3(v0); coord.as('EPSG:4978', coord1).toVector3(v1); return v0.distanceTo(v1); } /** - * Multiplies this `coordinates` (with an implicit 1 in the 4th dimension) and `mat`. + * Multiplies this `coordinates` (with an implicit 1 in the 4th dimension) + * and `mat`. * - * @param {THREE.Matrix4} mat The matrix. - * @return {Coordinates} return this object. + * @param mat - The matrix. + * @returns return this object. */ - applyMatrix4(mat) { - return THREE.Vector3.prototype.applyMatrix4.call(this, mat); + applyMatrix4(mat: THREE.Matrix4): this { + THREE.Vector3.prototype.applyMatrix4.call(this, mat); + return this; } /** - * Returns coordinates in the wanted [CRS](http://inspire.ec.europa.eu/theme/rs). + * Returns coordinates in the wanted + * [CRS](http://inspire.ec.europa.eu/theme/rs). * - * @param {string} crs - The CRS to convert the Coordinates into. - * @param {Coordinates} [target] - The target to put the converted + * @param crs - The CRS to convert the Coordinates into. + * @param target - The target to put the converted * Coordinates into. If not specified a new one will be created. * - * @return {Coordinates} - The resulting Coordinates after the conversion. + * @returns The resulting Coordinates after the conversion. * * @example * const position = { longitude: 2.33, latitude: 48.24, altitude: 24999549 }; @@ -318,7 +336,7 @@ class Coordinates { * @example * new Coordinates('EPSG:4978', x: 20885167, y: 849862, z: 23385912).as('EPSG:4326'); // Geographic system */ - as(crs, target = new Coordinates(crs)) { + as(crs: ProjectionLike, target = new Coordinates(crs)): Coordinates { if (this.crs == crs) { target.copy(this); } else { @@ -326,7 +344,8 @@ class Coordinates { this.y = THREE.MathUtils.clamp(this.y, -89.999999, 89.999999); } - target.setFromArray(proj4cache(this.crs, crs).forward([this.x, this.y, this.z])); + target.setFromArray(proj4cache(this.crs, crs) + .forward([this.x, this.y, this.z])); } target.crs = crs;