UNPKG

cesium-cartographic

Version:

Math helper extensions for cartographic calculation in Cesium.js

196 lines (164 loc) 8.44 kB
'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); var Cesium = require('cesium'); var assert = require('nanoassert'); function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; } var assert__default = /*#__PURE__*/_interopDefaultLegacy(assert); const MEAN_EARTH_RADIUS = 6371000; /** * Calculates the ground distance between two geographic coordinates using the Harvesine formulae. * * @param {Cesium.Cartographic} carto1 The first cartographic coordinate. * @param {Cesium.Cartographic} carto2 The second cartographic coordinate. * @param {number} radius The radius in meters (defaults to MEAN_EARTH_RADIUS) * @returns {number} The distance in meters */ function greatCircleGroundDistance (carto1, carto2, radius = MEAN_EARTH_RADIUS) { assert__default['default'](carto1, 'carto1 must be defined'); assert__default['default'](carto2, 'carto2 must be defined'); assert__default['default'](typeof radius === 'number', 'radius must be a number'); // original javascript implementation from Chris Veness : https://github.com/chrisveness/geodesy // a = sin²(Δφ/2) + cos(φ1)⋅cos(φ2)⋅sin²(Δλ/2) // δ = 2·atan2(√(a), √(1−a)) // see mathforum.org/library/drmath/view/51879.html for derivation const R = radius; const φ1 = carto1.latitude; const λ1 = carto1.longitude; const φ2 = carto2.latitude; const λ2 = carto2.longitude; const Δφ = φ2 - φ1; const Δλ = λ2 - λ1; const a = Math.pow(Math.sin(Δφ / 2), 2) + Math.cos1) * Math.cos2) * Math.pow(Math.sin(Δλ / 2), 2); const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); const d = R * c; return d } /** * Calculates the initial bearing to take to navigate from a point to another using the Harvesine formulae. * * @param {Cesium.Cartographic} carto1 The first cartographic coordinate * @param {Cesium.Cartographic} carto2 The second cartographic coordinate * @returns {number} The initial bearing in radians (0° = North, 90° = East, 270° = West) */ function greatCircleInitialBearing (carto1, carto2) { assert__default['default'](carto1, 'carto1 must be defined'); assert__default['default'](carto2, 'carto2 must be defined'); // original javascript implementation from Chris Veness : https://github.com/chrisveness/geodesy // tanθ = sinΔλ⋅cosφ2 / cosφ1⋅sinφ2 − sinφ1⋅cosφ2⋅cosΔλ // see mathforum.org/library/drmath/view/55417.html for derivation const φ1 = carto1.latitude; const φ2 = carto2.latitude; const Δλ = carto2.longitude - carto1.longitude; const cosφ2 = Math.cos2); const x = Math.cos1) * Math.sin2) - Math.sin1) * cosφ2 * Math.cos(Δλ); const y = Math.sin(Δλ) * cosφ2; const θ = Math.atan2(y, x); return Cesium.Math.zeroToTwoPi(θ) } /** * Calculates the end bearing of a trajectory that would go from one point to another using the Harvesine formulae. * * @param {Cesium.Cartographic} carto1 The first cartographic coordinate * @param {Cesium.Cartographic} carto2 The second cartographic coordinate * @returns {number} The end bearing in radians (0° = North, 90° = East, 270° = West) */ function greatCircleEndBearing (carto1, carto2) { /* The usual algorithm gives strange results when carto1 === carto2 or for very small distances. In these cases the initial bearing and the end bearing can be complete opposites. This behavior is not important for most applications can be critical if we try to compare the initial bearing and the end bearing. We solve this problem by comparing the coordinates and just using the initial bearing if the distance is too short. (< 10 µm on Earth) */ const e = Cesium.Math.EPSILON12; if (Math.abs(carto1.latitude - carto2.latitude) < e && Math.abs(carto1.longitude - carto2.longitude) < e) { return greatCircleInitialBearing(carto1, carto2) } else { return Cesium.Math.zeroToTwoPi(greatCircleInitialBearing(carto2, carto1) + Math.PI) } } /** * Calculates the translation from one point to another using the Harvesine formulae. The translation is a tuple containing the ground distance * as first element and the initial bearing as second element. This function returns the same results than `greatCircleDistance()` and * `greatCircleInitialBearing()` but requires less computation than calling both functions in succession. * * This function shares the following quasi-equality with `greatCircleTranslate()`: * * // given a and b, two Cartographic * * greatCircleTranslate(a, greatCircleTranslation(a, b)) ~= b * * @param {Cesium.Cartographic} carto1 The first cartographic coordinate. * @param {Cesium.Cartographic} carto2 The second cartographic coordinate. * @param {number} radius The radius in meters (defaults to MEAN_EARTH_RADIUS) * @returns {Array} A tuple containing the distance in meters between the two points as first element and the initial bearing in radians from first point to * second point as second element (0° = North, 90° = East, 270° = West) */ function greatCircleTranslation (carto1, carto2, radius = MEAN_EARTH_RADIUS) { assert__default['default'](carto1, 'carto1 must be defined'); assert__default['default'](carto2, 'carto2 must be defined'); assert__default['default'](typeof radius === 'number', 'radius must be a number'); const R = radius; const φ1 = carto1.latitude; const λ1 = carto1.longitude; const φ2 = carto2.latitude; const λ2 = carto2.longitude; const Δφ = φ2 - φ1; const Δλ = λ2 - λ1; const cosφ1 = Math.cos1); const cosφ2 = Math.cos2); const a = Math.pow(Math.sin(Δφ / 2), 2) + cosφ1 * cosφ2 * Math.pow(Math.sin(Δλ / 2), 2); const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); const d = R * c; const x = cosφ1 * Math.sin2) - Math.sin1) * cosφ2 * Math.cos(Δλ); const y = Math.sin(Δλ) * cosφ2; const θ = Math.atan2(y, x); return [d, Cesium.Math.zeroToTwoPi(θ)] } /** * Travels a distance from a given point with a given translation using the Harvesine formulae. The translation is a tuple * containing the ground distance as first element and the initial bearing as second element, as returned by the `greatCircleTranslation()` * function. * * This function shares the following quasi-equality with `greatCircleTranslation()`: * * // given a and b, two Cartographic * * greatCircleTranslate(a, greatCircleTranslation(a, b)) ~= b * * @param {Cesium.Cartographic} carto The initial cartographic coordinate * @param {Array} translation The translation to apply. It is a tuple where the first element is the ground distance in meters and the second * element is the initial bearing in radians (0° = North, 90° = East, 270° = West) * @param {number} radius The radius in meters (defaults to MEAN_EARTH_RADIUS) * @returns {Cesium.Cartographic} The new position */ function greatCircleTranslate (carto, translation, radius = MEAN_EARTH_RADIUS) { assert__default['default'](carto, 'carto must be defined'); assert__default['default'](translation, 'translation must be defined'); assert__default['default'](typeof radius === 'number', 'radius must be a number'); // original javascript implementation from Chris Veness : https://github.com/chrisveness/geodesy // sinφ2 = sinφ1⋅cosδ + cosφ1⋅sinδ⋅cosθ // tanΔλ = sinθ⋅sinδ⋅cosφ1 / cosδ−sinφ1⋅sinφ2 // see mathforum.org/library/drmath/view/52049.html for derivation const δ = translation[0] / radius; // angular distance in radians const θ = translation[1]; const φ1 = carto.latitude; const λ1 = carto.longitude; const cosφ1 = Math.cos1); const sinφ1 = Math.sin1); const cosδ = Math.cos(δ); const sinδ = Math.sin(δ); const sinφ2 = sinφ1 * cosδ + cosφ1 * sinδ * Math.cos(θ); const φ2 = Math.asin(sinφ2); const y = Math.sin(θ) * sinδ * cosφ1; const x = cosδ - sinφ1 * sinφ2; const λ2 = λ1 + Math.atan2(y, x); const lat = φ2; const lon = λ2; return new Cesium.Cartographic(lon, lat, 0) } exports.MEAN_EARTH_RADIUS = MEAN_EARTH_RADIUS; exports.greatCircleEndBearing = greatCircleEndBearing; exports.greatCircleGroundDistance = greatCircleGroundDistance; exports.greatCircleInitialBearing = greatCircleInitialBearing; exports.greatCircleTranslate = greatCircleTranslate; exports.greatCircleTranslation = greatCircleTranslation;