UNPKG

@giro3d/giro3d

Version:

A JS/WebGL framework for 3D geospatial data visualization

482 lines (461 loc) 14.8 kB
import { register } from 'ol/proj/proj4'; import proj4 from 'proj4'; import { MathUtils, Vector2, Vector3 } from 'three'; import { getConverter } from './ProjectionCache'; proj4.defs('EPSG:4978', '+proj=geocent +datum=WGS84 +units=m +no_defs +type=crs'); proj4.defs('EPSG:4979', '+proj=longlat +datum=WGS84 +no_defs +type=crs'); register(proj4); export const UNIT = { DEGREE: 1, METER: 2, FOOT: 3 }; /** * Returns the enum value of the specified unit of measure * * @param projunit - - the proj4 UoM string * @returns the unit of measure (see `UNIT`) */ function unitFromProj4Unit(projunit) { switch (projunit) { case 'deg': case 'degrees': case 'degree': return UNIT.DEGREE; case 'm': case 'meter': case 'meters': return UNIT.METER; case 'ft': case 'foot': case 'feet': return UNIT.FOOT; default: return undefined; } } /** * Returns the horizontal unit of measure (UoM) of the specified CRS * * @param crs - the CRS to test * @returns the unit of measure (see `UNIT`) */ export function crsToUnit(crs) { switch (crs) { case 'EPSG:4326': case 'EPSG:4979': return UNIT.DEGREE; case 'EPSG:4978': return UNIT.METER; default: { const p = proj4.defs(crs); if (p == null) { return undefined; } return unitFromProj4Unit(p.units); } } } function crsToUnitWithError(crs) { const u = crsToUnit(crs); if (crs === undefined || u === undefined) { throw new Error(`Invalid crs parameter value '${crs}'`); } return u; } export function assertCrsIsValid(crs) { if (proj4.defs(crs) == null) { throw new Error(`Invalid crs parameter value '${crs}'. Did you define it with proj4?`); } } /** * Tests whether the CRS is in geographic coordinates. * * @param crs - the CRS to test * @returns `true` if the CRS is in geographic coordinates. */ export function crsIsGeographic(crs) { return crsToUnitWithError(crs) !== UNIT.METER; } /** * Tests whether the CRS is in geocentric coordinates. * * @param crs - the CRS to test * @returns `true` if the CRS is in geocentric coordinates. */ export function crsIsGeocentric(crs) { return crsToUnitWithError(crs) === UNIT.METER; } function assertIsGeographic(crs) { if (!crsIsGeographic(crs)) { throw new Error(`Can't query crs ${crs} long/lat`); } } function assertIsGeocentric(crs) { if (!crsIsGeocentric(crs)) { throw new Error(`Can't query crs ${crs} x/y/z`); } } export function is4326(crs) { return crs.indexOf('EPSG:4326') === 0; } const planarNormal = new Vector3(0, 0, 1); /** * Possible values to set a `Coordinates` object. * * It can be: * - A pair of numbers for 2D coordinates [X, Y] * - A triplet of numbers for 3D coordinates [X, Y, Z] * - A THREE `Vector3` * * @example * new Coordinates('EPSG:4978', 20885167, 849862, 23385912); //Geocentric coordinates * // or * new Coordinates('EPSG:4978', new Vector3(20885167, 849862, 23385912)) // Same with a vector. * // or * new Coordinates('EPSG:4326', 2.33, 48.24, 24999549); //Geographic coordinates */ /** * Represents coordinates associated with a coordinate reference system (CRS). */ class Coordinates { /** * Build a {@link Coordinates} object, given a [CRS](http://inspire.ec.europa.eu/theme/rs) and a number of coordinates value. * Coordinates can be geocentric, geographic, or an instance of [Vector3](https://threejs.org/docs/#api/math/Vector3). * - If `crs` is `'EPSG:4326'`, coordinates must be in [geographic system](https://en.wikipedia.org/wiki/Geographic_coordinate_system). * - If `crs` is `'EPSG:4978'`, coordinates must be in [geocentric system](https://en.wikipedia.org/wiki/Earth-centered,_Earth-fixed_coordinate_system). * * @param crs - Geographic or Geocentric coordinates system. * @param coordinates - The coordinates. */ constructor(crs, ...coordinates) { this._values = new Float64Array(3); this.crs = crs; this.set(crs, ...coordinates); } get values() { return this._values; } /** * Returns the normal vector associated with this coordinate. * * @returns The normal vector. */ get geodesicNormal() { return planarNormal; } set(crs, ...coordinates) { crsToUnitWithError(crs); this.crs = crs; if (coordinates.length === 1 && coordinates[0] instanceof Vector3) { this._values[0] = coordinates[0].x; this._values[1] = coordinates[0].y; this._values[2] = coordinates[0].z; } else { for (let i = 0; i < coordinates.length && i < 3; i++) { this._values[i] = coordinates[i]; } for (let i = coordinates.length; i < 3; i++) { this._values[i] = 0; } } return this; } clone(target) { let r; if (target) { Coordinates.call(target, this.crs, this._values[0], this._values[1], this._values[2]); r = target; } else { r = new Coordinates(this.crs, this._values[0], this._values[1], this._values[2]); } return r; } copy(src) { const v = src._values; this.set(src.crs, v[0], v[1], v[2]); return this; } /** * Returns the longitude in geographic coordinates. * Coordinates must be in geographic system (can be * converted by using {@link as} ). * * ```js * const position = { longitude: 2.33, latitude: 48.24, altitude: 24999549 }; * const coordinates = new Coordinates( * 'EPSG:4326', position.longitude, position.latitude, position.altitude); // Geographic * coordinates.longitude; // Longitude in geographic system * // returns 2.33 * * // or * * const position = { x: 20885167, y: 849862, z: 23385912 }; * // Geocentric system * const coords = new Coordinates('EPSG:4978', position.x, position.y, position.z); * const coordinates = coords.as('EPSG:4326'); // Geographic system * coordinates.longitude; // Longitude in geographic system * // returns 2.330201911389028 * ``` * @returns The longitude of the position. */ get longitude() { assertIsGeographic(this.crs); return this._values[0]; } /** * Returns the latitude in geographic coordinates. * Coordinates must be in geographic system (can be converted by using {@link as}). * * ```js * const position = { longitude: 2.33, latitude: 48.24, altitude: 24999549 }; * const coordinates = new Coordinates( * 'EPSG:4326', position.longitude, position.latitude, position.altitude); // Geographic * coordinates.latitude; // Latitude in geographic system * // returns : 48.24 * * // or * * const position = { x: 20885167, y: 849862, z: 23385912 }; * // Geocentric system * const coords = new Coordinates('EPSG:4978', position.x, position.y, position.z); * const coordinates = coords.as('EPSG:4326'); // Geographic system * coordinates.latitude; // Latitude in geographic system * // returns : 48.24830764643365 * ``` * @returns The latitude of the position. */ get latitude() { assertIsGeographic(this.crs); return this._values[1]; } /** * Returns the altitude in geographic coordinates. * Coordinates must be in geographic system (can be converted by using {@link as}). * * ```js * const position = { longitude: 2.33, latitude: 48.24, altitude: 24999549 }; * // Geographic system * const coordinates = * new Coordinates('EPSG:4326', position.longitude, position.latitude, position.altitude); * coordinates.altitude; // Altitude in geographic system * // returns : 24999549 * * // or * * const position = { x: 20885167, y: 849862, z: 23385912 }; * // Geocentric system * const coords = new Coordinates('EPSG:4978', position.x, position.y, position.z); * const coordinates = coords.as('EPSG:4326'); // Geographic system * coordinates.altitude; // Altitude in geographic system * // returns : 24999548.046711832 * ``` * @returns The altitude of the position. */ get altitude() { assertIsGeographic(this.crs); return this._values[2]; } /** * Set the altitude. * * @param altitude - the new altitude. * ```js * coordinates.setAltitude(10000) * ``` */ setAltitude(altitude) { assertIsGeographic(this.crs); this._values[2] = altitude; } /** * Returns the `x` component of this coordinate in geocentric coordinates. * Coordinates must be in geocentric system (can be * converted by using {@link as}). * * ```js * const position = { x: 20885167, y: 849862, z: 23385912 }; * const coordinates = new Coordinates('EPSG:4978', position.x, position.y, position.z); * coordinates.x; // Geocentric system * // returns : 20885167 * * // or * * const position = { longitude: 2.33, latitude: 48.24, altitude: 24999549 }; * // Geographic system * const coords = * new Coordinates('EPSG:4326', position.longitude, position.latitude, position.altitude); * const coordinates = coords.as('EPSG:4978'); // Geocentric system * coordinates.x; // Geocentric system * // returns : 20888561.0301258 * ``` * @returns The `x` component of the position. */ get x() { assertIsGeocentric(this.crs); return this._values[0]; } /** * Returns the `y` component of this coordinate in geocentric coordinates. * Coordinates must be in geocentric system (can be * converted by using {@link as}). * * ```js * const position = { x: 20885167, y: 849862, z: 23385912 }; * const coordinates = new Coordinates('EPSG:4978', position.x, position.y, position.z); * coordinates.y; // Geocentric system * // returns : 849862 * ``` * @returns The `y` component of the position. */ get y() { assertIsGeocentric(this.crs); return this._values[1]; } /** * Returns the `z` component of this coordinate in geocentric coordinates. * Coordinates must be in geocentric system (can be * converted by using {@link as}). * * ```js * const position = { x: 20885167, y: 849862, z: 23385912 }; * const coordinates = new Coordinates('EPSG:4978', position.x, position.y, position.z); * coordinates.z; // Geocentric system * // returns : 23385912 * ``` * @returns The `z` component of the position. */ get z() { assertIsGeocentric(this.crs); return this._values[2]; } /** * Returns the equivalent `Vector3` of this coordinate. * * ```js * const position = { x: 20885167, y: 849862, z: 23385912 }; * // Geocentric system * const coordinates = new Coordinates('EPSG:4978', position.x, position.y, position.z); * coordinates.toVector3(); * // returns : Vector3 * // x: 20885167 * // y: 849862 * // z: 23385912 * * // or * * const position = { longitude: 2.33, latitude: 48.24, altitude: 24999549 }; * // Geographic system * const coordinates = * new Coordinates('EPSG:4326', position.longitude, position.latitude, position.altitude); * coordinates.toVector3(); * // returns : Vector3 * // x: 2.33 * // y: 48.24 * // z: 24999549 * ``` * @param target - the geocentric coordinate * @returns target position */ toVector3(target) { const v = target || new Vector3(); v.fromArray(this._values); return v; } /** * Returns the equivalent `Vector2` of this coordinate. Note that the Z component (elevation) is * lost. * * ```js * * const position = { x: 20885167, y: 849862, z: 23385912 }; * // Metric system * const coordinates = new Coordinates('EPSG:3857', position.x, position.y, position.z); * coordinates.toVector2(); * // returns : Vector2 * // x: 20885167 * // y: 849862 * * // or * * const position = { longitude: 2.33, latitude: 48.24, altitude: 24999549 }; * // Geographic system * const coordinates = * new Coordinates('EPSG:4326', position.longitude, position.latitude, position.altitude); * coordinates.toVector2(); * // returns : Vector2 * // x: 2.33 * // y: 48.24 * ``` * @param target - the geocentric coordinate * @returns target position */ toVector2(target) { const v = target || new Vector2(); v.fromArray(this._values); return v; } /** * Converts coordinates in another [CRS](http://inspire.ec.europa.eu/theme/rs). * * If target is not specified, creates a new instance. * The original instance is never modified (except if you passed it as `target`). * * ```js * const position = { longitude: 2.33, latitude: 48.24, altitude: 24999549 }; * // Geographic system * const coords = * new Coordinates('EPSG:4326', position.longitude, position.latitude, position.altitude); * const coordinates = coords.as('EPSG:4978'); // Geocentric system * ``` * @param crs - the [CRS](http://inspire.ec.europa.eu/theme/rs) EPSG string * @param target - the object that is returned * @returns the converted coordinate */ as(crs, target) { if (crs === undefined || crsToUnit(crs) === undefined) { throw new Error(`Invalid crs paramater value '${crs}'`); } return this.convert(crs, target); } // Only support explicit conversions convert(newCrs, target) { target = target || new Coordinates(newCrs, 0, 0, 0); if (newCrs === this.crs) { return target.copy(this); } if (this.crs in proj4.defs && newCrs in proj4.defs) { const val0 = this._values[0]; let val1 = this._values[1]; const crsIn = this.crs; // there is a bug for converting anything from and to 4978 with proj4 // https://github.com/proj4js/proj4js/issues/195 // the workaround is to use an intermediate projection, like EPSG:4326 if (is4326(crsIn) && newCrs === 'EPSG:3857') { val1 = MathUtils.clamp(val1, -89.999999, 89.999999); const p = getConverter(crsIn, newCrs).forward([val0, val1]); return target.set(newCrs, p[0], p[1], this._values[2]); } // here is the normal case with proj4 const p = getConverter(crsIn, newCrs).forward([val0, val1]); return target.set(newCrs, p[0], p[1], this._values[2]); } throw new Error(`Cannot convert from crs ${this.crs} to ${newCrs}`); } /** * Returns the boolean result of the check if this coordinate is geographic (true) * or geocentric (false). * * ```js * const position = { x: 20885167, y: 849862, z: 23385912 }; * const coordinates = new Coordinates('EPSG:4978', position.x, position.y, position.z); * coordinates.isGeographic(); // Geocentric system * // returns : false * ``` * @returns `true` if the coordinate is geographic. */ isGeographic() { return crsIsGeographic(this.crs); } } export default Coordinates;