Comparing version 2.0.0 to 2.0.1
# Changelog | ||
## [2.0.1] - 2019-04-10 | ||
### Fixed | ||
- Add missing n-vector spherical alongTrackDistanceTo() method | ||
- Add missing .toUtm() method to LatLon object returned by Utm.toLatLon() | ||
- Fix n-vector spherical isWithinExtent() for point in different hemisphere | ||
- Fix vector3d angleTo() for case where plane normal n is in the plane | ||
- Rationalise/harmonise exception messages | ||
### Added | ||
- README ‘docs’ badge with link to documentation | ||
## [2.0.0] - 2019-02-14 | ||
@@ -4,0 +18,0 @@ |
@@ -270,3 +270,3 @@ /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ | ||
static compassPoint(bearing, precision=3) { | ||
if (![ 1,2,3 ].includes(Number(precision))) throw new RangeError('‘precision’ must be 1, 2 or 3'); // eslint-disable-line comma-spacing | ||
if (![ 1, 2, 3 ].includes(Number(precision))) throw new RangeError(`invalid precision ‘${precision}’`); | ||
// note precision could be extended to 4 for quarter-winds (eg NbNW), but I think they are little used | ||
@@ -273,0 +273,0 @@ |
@@ -118,3 +118,3 @@ /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ | ||
constructor(lat, lon, height=0, datum=datums.WGS84) { | ||
if (!datum || datum.transform==undefined) throw new TypeError(`Unrecognised datum ‘${datum}’`); | ||
if (!datum || datum.ellipsoid==undefined) throw new TypeError(`unrecognised datum ‘${datum}’`); | ||
@@ -201,3 +201,3 @@ super(lat, lon, height); | ||
if (!datum || datum.transform==undefined) throw new TypeError(`Unrecognised datum ‘${datum}’`); | ||
if (!datum || datum.ellipsoid==undefined) throw new TypeError(`unrecognised datum ‘${datum}’`); | ||
@@ -224,3 +224,3 @@ const point = super.parse(...args); | ||
convertDatum(toDatum) { | ||
if (toDatum == undefined || toDatum.transform == undefined) throw new TypeError('Unrecognised datum'); | ||
if (!toDatum || toDatum.ellipsoid==undefined) throw new TypeError(`unrecognised datum ‘${toDatum}’`); | ||
@@ -294,3 +294,3 @@ let oldLatLon = this; | ||
toLatLon(datum=datums.WGS84) { | ||
if (!datum) throw new TypeError('Unrecognised datum'); | ||
if (!datum || datum.ellipsoid==undefined) throw new TypeError(`unrecognised datum ‘${datum}’`); | ||
const latLon = super.toLatLon(datum.ellipsoid); | ||
@@ -312,3 +312,3 @@ return new LatLonEllipsoidal_Datum(latLon.lat, latLon.lon, latLon.height, datum); | ||
// this point | ||
const x1 = this.x, y1 = this.y, z1 = this.z; | ||
const { x: x1, y: y1, z: z1 } = this; | ||
@@ -315,0 +315,0 @@ // transform parameters |
@@ -115,4 +115,4 @@ /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ | ||
constructor(lat, lon, height=0, referenceFrame=referenceFrames.ITRF2014, epoch=undefined) { | ||
if (!referenceFrame || referenceFrame.epoch==undefined) throw new TypeError('Unrecognised reference frame'); | ||
if (epoch != undefined && isNaN(Number(epoch))) throw new TypeError(`Invalid epoch ’${epoch}’`); | ||
if (!referenceFrame || referenceFrame.epoch==undefined) throw new TypeError('unrecognised reference frame'); | ||
if (epoch != undefined && isNaN(Number(epoch))) throw new TypeError(`invalid epoch ’${epoch}’`); | ||
@@ -210,3 +210,3 @@ super(lat, lon, height); | ||
static parse(...args) { | ||
if (args.length == 0) throw new TypeError('Invalid (empty) coordinate'); | ||
if (args.length == 0) throw new TypeError('invalid (empty) point'); | ||
@@ -225,3 +225,3 @@ let referenceFrame = null, epoch = null; | ||
if (!referenceFrame || referenceFrame.epoch==undefined) throw new TypeError('Unrecognised reference frame'); | ||
if (!referenceFrame || referenceFrame.epoch==undefined) throw new TypeError('unrecognised reference frame'); | ||
@@ -251,3 +251,3 @@ // args is now lat, lon, height or latlon, height as taken by LatLonEllipsoidal .parse() | ||
convertReferenceFrame(toReferenceFrame) { | ||
if (!toReferenceFrame || toReferenceFrame.epoch == undefined) throw new TypeError('Unrecognised reference frame'); | ||
if (!toReferenceFrame || toReferenceFrame.epoch == undefined) throw new TypeError('unrecognised reference frame'); | ||
@@ -335,4 +335,4 @@ const oldCartesian = this.toCartesian(); // convert geodetic to cartesian | ||
constructor(x, y, z, referenceFrame=undefined, epoch=undefined) { | ||
if (referenceFrame!=undefined && referenceFrame.epoch==undefined) throw new TypeError('Unrecognised reference frame'); | ||
if (epoch!=undefined && isNaN(Number(epoch))) throw new TypeError(`Invalid epoch ’${epoch}’`); | ||
if (referenceFrame!=undefined && referenceFrame.epoch==undefined) throw new TypeError('unrecognised reference frame'); | ||
if (epoch!=undefined && isNaN(Number(epoch))) throw new TypeError(`invalid epoch ’${epoch}’`); | ||
@@ -353,3 +353,3 @@ super(x, y, z); | ||
set referenceFrame(referenceFrame) { | ||
if (!referenceFrame || referenceFrame.epoch==undefined) throw new TypeError('Unrecognised reference frame'); | ||
if (!referenceFrame || referenceFrame.epoch==undefined) throw new TypeError('unrecognised reference frame'); | ||
this._referenceFrame = referenceFrame; | ||
@@ -365,3 +365,3 @@ } | ||
set epoch(epoch) { | ||
if (isNaN(Number(epoch))) throw new TypeError(`Invalid epoch ’${epoch}’`); | ||
if (isNaN(Number(epoch))) throw new TypeError(`invalid epoch ’${epoch}’`); | ||
if (this._epoch != this._referenceFrame.epoch) this._epoch = Number(epoch); | ||
@@ -386,3 +386,3 @@ } | ||
toLatLon() { | ||
if (!this.referenceFrame) throw new Error('Cartesian reference frame not defined'); | ||
if (!this.referenceFrame) throw new Error('cartesian reference frame not defined'); | ||
@@ -412,4 +412,4 @@ const latLon = super.toLatLon(this.referenceFrame.ellipsoid); | ||
convertReferenceFrame(toReferenceFrame) { | ||
if (!toReferenceFrame || toReferenceFrame.epoch == undefined) throw new TypeError('Unrecognised reference frame'); | ||
if (!this.referenceFrame) throw new TypeError('Cartesian coordinate has no reference frame'); | ||
if (!toReferenceFrame || toReferenceFrame.epoch == undefined) throw new TypeError('unrecognised reference frame'); | ||
if (!this.referenceFrame) throw new TypeError('cartesian coordinate has no reference frame'); | ||
@@ -530,7 +530,7 @@ if (this.referenceFrame.name == toReferenceFrame.name) return this; // no-op! | ||
toString(dp=0) { | ||
const x = this.x.toFixed(dp), y = this.y.toFixed(dp), z = this.z.toFixed(dp); | ||
const { x, y, z } = this; | ||
const epochFmt = { useGrouping: false, minimumFractionDigits: 1, maximumFractionDigits: 20 }; | ||
const epoch = this.referenceFrame && this.epoch != this.referenceFrame.epoch ? this.epoch.toLocaleString('en', epochFmt) : ''; | ||
const trf = this.referenceFrame ? `(${this.referenceFrame.name}${epoch?'@'+epoch:''})` : ''; | ||
return `[${x},${y},${z}]${trf}`; | ||
return `[${x.toFixed(dp)},${y.toFixed(dp)},${z.toFixed(dp)}]${trf}`; | ||
} | ||
@@ -537,0 +537,0 @@ } |
@@ -164,3 +164,3 @@ /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ | ||
direct(distance, initialBearing) { | ||
if (this.height != 0) throw new RangeError('Point must be on the surface of the ellipsoid'); | ||
if (this.height != 0) throw new RangeError('point must be on the surface of the ellipsoid'); | ||
@@ -171,5 +171,5 @@ const φ1 = this.lat.toRadians(), λ1 = this.lon.toRadians(); | ||
const ellipsoid = this.datum ? this.datum.ellipsoid : | ||
this.referenceFrame ? this.referenceFrame.ellipsoid : LatLonEllipsoidal.ellipsoids.WGS84; | ||
const a = ellipsoid.a, b = ellipsoid.b, f = ellipsoid.f; | ||
// allow alternative ellipsoid to be specified | ||
const ellipsoid = this.datum ? this.datum.ellipsoid : LatLonEllipsoidal.ellipsoids.WGS84; | ||
const { a, b, f } = ellipsoid; | ||
@@ -210,3 +210,3 @@ const sinα1 = Math.sin(α1); | ||
const destinationPoint = new LatLonEllipsoidal_Vincenty(φ2.toDegrees(), Dms.wrap180(λ2.toDegrees()), 0, this.datum); | ||
const destinationPoint = new LatLonEllipsoidal_Vincenty(φ2.toDegrees(), λ2.toDegrees(), 0, this.datum); | ||
@@ -229,3 +229,3 @@ return { | ||
* @returns {Object} Object including distance, initialBearing, finalBearing. | ||
* @throws {TypeError} Point not LatLon object. | ||
* @throws {TypeError} Invalid point. | ||
* @throws {RangeError} Points must be on surface of ellipsoid. | ||
@@ -235,4 +235,4 @@ * @throws {EvalError} Formula failed to converge. | ||
inverse(point) { | ||
if (!(point instanceof LatLonEllipsoidal)) throw new TypeError('‘point’ is not (Ellipsoidal) LatLon object'); | ||
if (this.height!=0 || point.height!=0) throw new RangeError('Point must be on the surface of the ellipsoid'); | ||
if (!(point instanceof LatLonEllipsoidal)) throw new TypeError(`invalid point ‘${point}’`); | ||
if (this.height!=0 || point.height!=0) throw new RangeError('point must be on the surface of the ellipsoid'); | ||
@@ -243,5 +243,5 @@ const p1 = this, p2 = point; | ||
const ellipsoid = this.datum ? this.datum.ellipsoid : | ||
this.referenceFrame ? this.referenceFrame.ellipsoid : LatLonEllipsoidal.ellipsoids.WGS84; | ||
const a = ellipsoid.a, b = ellipsoid.b, f = ellipsoid.f; | ||
// allow alternative ellipsoid to be specified | ||
const ellipsoid = this.datum ? this.datum.ellipsoid : LatLonEllipsoidal.ellipsoids.WGS84; | ||
const { a, b, f } = ellipsoid; | ||
@@ -261,3 +261,3 @@ const L = λ2 - λ1; | ||
sinSqσ = (cosU2*sinλ) * (cosU2*sinλ) + (cosU1*sinU2-sinU1*cosU2*cosλ) * (cosU1*sinU2-sinU1*cosU2*cosλ); | ||
if (sinSqσ == 0) break; // co-incident points | ||
if (Math.abs(sinSqσ) < Number.EPSILON) break; // co-incident points | ||
sinσ = Math.sqrt(sinSqσ); | ||
@@ -290,4 +290,4 @@ cosσ = sinU1*sinU2 + cosU1*cosU2*cosλ; | ||
distance: s, | ||
initialBearing: s==0 ? NaN : Dms.wrap360(α1.toDegrees()), | ||
finalBearing: s==0 ? NaN : Dms.wrap360(α2.toDegrees()), | ||
initialBearing: Math.abs(s) < Number.EPSILON ? NaN : Dms.wrap360(α1.toDegrees()), | ||
finalBearing: Math.abs(s) < Number.EPSILON ? NaN : Dms.wrap360(α2.toDegrees()), | ||
iterations: iterations, | ||
@@ -294,0 +294,0 @@ }; |
@@ -76,5 +76,5 @@ /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ | ||
constructor(lat, lon, height=0) { | ||
if (isNaN(lat)) throw new TypeError(`Invalid lat ‘${lat}’`); | ||
if (isNaN(lon)) throw new TypeError(`Invalid lon ‘${lon}’`); | ||
if (isNaN(height)) throw new TypeError(`Invalid height ‘${height}’`); | ||
if (isNaN(lat)) throw new TypeError(`invalid lat ‘${lat}’`); | ||
if (isNaN(lon)) throw new TypeError(`invalid lon ‘${lon}’`); | ||
if (isNaN(height)) throw new TypeError(`invalid height ‘${height}’`); | ||
@@ -95,7 +95,7 @@ this._lat = Dms.wrap90(Number(lat)); | ||
this._lat = isNaN(lat) ? Dms.wrap90(Dms.parse(lat)) : Dms.wrap90(Number(lat)); | ||
if (isNaN(this._lat)) throw new TypeError(`Invalid lat ‘${lat}’`); | ||
if (isNaN(this._lat)) throw new TypeError(`invalid lat ‘${lat}’`); | ||
} | ||
set latitude(lat) { | ||
this._lat = isNaN(lat) ? Dms.wrap90(Dms.parse(lat)) : Dms.wrap90(Number(lat)); | ||
if (isNaN(this._lat)) throw new TypeError(`Invalid latitude ‘${lat}’`); | ||
if (isNaN(this._lat)) throw new TypeError(`invalid latitude ‘${lat}’`); | ||
} | ||
@@ -112,11 +112,11 @@ | ||
this._lon = isNaN(lon) ? Dms.wrap180(Dms.parse(lon)) : Dms.wrap180(Number(lon)); | ||
if (isNaN(this._lon)) throw new TypeError(`Invalid lon ‘${lon}’`); | ||
if (isNaN(this._lon)) throw new TypeError(`invalid lon ‘${lon}’`); | ||
} | ||
set lng(lon) { | ||
this._lon = isNaN(lon) ? Dms.wrap180(Dms.parse(lon)) : Dms.wrap180(Number(lon)); | ||
if (isNaN(this._lon)) throw new TypeError(`Invalid lng ‘${lon}’`); | ||
if (isNaN(this._lon)) throw new TypeError(`invalid lng ‘${lon}’`); | ||
} | ||
set longitude(lon) { | ||
this._lon = isNaN(lon) ? Dms.wrap180(Dms.parse(lon)) : Dms.wrap180(Number(lon)); | ||
if (isNaN(this._lon)) throw new TypeError(`Invalid longitude ‘${lon}’`); | ||
if (isNaN(this._lon)) throw new TypeError(`invalid longitude ‘${lon}’`); | ||
} | ||
@@ -128,3 +128,3 @@ | ||
get height() { return this._height; } | ||
set height(height) { this._height = Number(height); if (isNaN(this._height)) throw new TypeError(`Invalid height ‘${height}’`); } | ||
set height(height) { this._height = Number(height); if (isNaN(this._height)) throw new TypeError(`invalid height ‘${height}’`); } | ||
@@ -192,3 +192,3 @@ | ||
static parse(...args) { | ||
if (args.length == 0) throw new TypeError('Invalid (empty) coordinate'); | ||
if (args.length == 0) throw new TypeError('invalid (empty) point'); | ||
@@ -214,3 +214,3 @@ let lat=undefined, lon=undefined, height=undefined; | ||
if (args[1] != undefined) height = args[1]; | ||
if (isNaN(lat) || isNaN(lon)) throw new TypeError(`Invalid coordinate ‘${JSON.stringify(args[0])}’`); | ||
if (isNaN(lat) || isNaN(lon)) throw new TypeError(`invalid point ‘${JSON.stringify(args[0])}’`); | ||
} | ||
@@ -224,3 +224,3 @@ | ||
height = args[1] || 0; | ||
if (isNaN(lat) || isNaN(lon)) throw new TypeError(`Invalid coordinate ‘${args[0]}’`); | ||
if (isNaN(lat) || isNaN(lon)) throw new TypeError(`invalid point ‘${args[0]}’`); | ||
} | ||
@@ -234,3 +234,3 @@ | ||
height = args[2] || 0; | ||
if (isNaN(lat) || isNaN(lon)) throw new TypeError(`Invalid coordinate ‘${args.toString()}’`); | ||
if (isNaN(lat) || isNaN(lon)) throw new TypeError(`invalid point ‘${args.toString()}’`); | ||
} | ||
@@ -258,4 +258,3 @@ | ||
const h = this.height; | ||
const a = ellipsoid.a; | ||
const f = ellipsoid.f; | ||
const { a, f } = ellipsoid; | ||
@@ -281,3 +280,3 @@ const sinφ = Math.sin(φ), cosφ = Math.cos(φ); | ||
* @returns {bool} True if points have identical latitude, longitude, height, and datum/referenceFrame. | ||
* @throws {TypeError} Point is not LatLon object. | ||
* @throws {TypeError} Invalid point. | ||
* | ||
@@ -290,7 +289,7 @@ * @example | ||
equals(point) { | ||
if (!(point instanceof LatLonEllipsoidal)) throw new TypeError('‘point’ is not LatLon object'); | ||
if (!(point instanceof LatLonEllipsoidal)) throw new TypeError(`invalid point ‘${point}’`); | ||
if (this.lat != point.lat) return false; | ||
if (this.lon != point.lon) return false; | ||
if (this.height != point.height) return false; | ||
if (Math.abs(this.lat - point.lat) > Number.EPSILON) return false; | ||
if (Math.abs(this.lon - point.lon) > Number.EPSILON) return false; | ||
if (Math.abs(this.height - point.height) > Number.EPSILON) return false; | ||
if (this.datum != point.datum) return false; | ||
@@ -323,3 +322,3 @@ if (this.referenceFrame != point.referenceFrame) return false; | ||
// note: explicitly set dp to undefined for passing through to toLat/toLon | ||
if (![ 'd', 'dm', 'dms', 'n' ].includes(format)) throw new RangeError(`Invalid format ‘${format}’`); | ||
if (![ 'd', 'dm', 'dms', 'n' ].includes(format)) throw new RangeError(`invalid format ‘${format}’`); | ||
@@ -386,6 +385,6 @@ const height = (this.height>=0 ? ' +' : ' ') + this.height.toFixed(dpHeight) + 'm'; | ||
// note ellipsoid is available as a parameter for when toLatLon gets subclassed. | ||
if (!ellipsoid || !ellipsoid.a) throw new TypeError('Invalid ellipsoid'); | ||
if (!ellipsoid || !ellipsoid.a) throw new TypeError('invalid ellipsoid'); | ||
const x = this.x, y = this.y, z = this.z; | ||
const a = ellipsoid.a, b = ellipsoid.b, f = ellipsoid.f; | ||
const { x, y, z } = this; | ||
const { a, b, f } = ellipsoid; | ||
@@ -392,0 +391,0 @@ const e2 = 2*f - f*f; // 1st eccentricity squared ≡ (a²−b²)/a² |
@@ -47,3 +47,4 @@ /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ | ||
* @param {LatLon} point - Point delta is to be determined to. | ||
* @returns {Ned} Delta from ‘this’ point to supplied point in local tangent plane of this point. | ||
* @returns {Ned} Delta from ‘this’ point to supplied point in local tangent plane of this point. | ||
* @throws {TypeError} Invalid point. | ||
* | ||
@@ -59,3 +60,3 @@ * @example | ||
deltaTo(point) { | ||
if (!(point instanceof LatLonEllipsoidal)) throw new TypeError('‘point’ is not (Ellipsoidal) LatLon object'); | ||
if (!(point instanceof LatLonEllipsoidal)) throw new TypeError(`invalid point ‘${point}’`); | ||
@@ -232,3 +233,3 @@ // get delta in cartesian frame | ||
const x = this.x, y = this.y, z = this.z; | ||
const { x, y, z } = this; | ||
@@ -254,13 +255,5 @@ const φ = Math.atan2(z, Math.sqrt(x*x + y*y)); | ||
toCartesian() { | ||
const ellipsoid = this.datum ? this.datum.ellipsoid : | ||
this.referenceFrame ? this.referenceFrame.ellipsoid : LatLonEllipsoidal.ellipsoids.WGS84; | ||
const { b, f } = this.datum.ellipsoid; | ||
const { x, y, z, h } = this; | ||
const b = ellipsoid.b; | ||
const f = ellipsoid.f; | ||
const x = this.x; | ||
const y = this.y; | ||
const z = this.z; | ||
const h = this.h; | ||
const m = (1-f) * (1-f); // (1−f)² = b²/a² | ||
@@ -290,8 +283,6 @@ const n = b / Math.sqrt(x*x/m + y*y/m + z*z); | ||
toString(dp=3, dpHeight=null) { | ||
const x = this.x.toFixed(dp); | ||
const y = this.y.toFixed(dp); | ||
const z = this.z.toFixed(dp); | ||
const { x, y, z } = this; | ||
const h = `${this.h>=0 ? '+' : ''}${this.h.toFixed(dpHeight)}m`; | ||
return `[${x},${y},${z}${dpHeight==null ? '' : h}]`; | ||
return `[${x.toFixed(dp)},${y.toFixed(dp)},${z.toFixed(dp)}${dpHeight==null ? '' : h}]`; | ||
} | ||
@@ -326,9 +317,5 @@ | ||
toNvector(datum=LatLonEllipsoidal.datums.WGS84) { | ||
const a = datum.ellipsoid.a; | ||
const f = datum.ellipsoid.f; | ||
const { a, f } = datum.ellipsoid; | ||
const { x, y, z } = this; | ||
const x = this.x; | ||
const y = this.y; | ||
const z = this.z; | ||
const e2 = 2*f - f*f; // e² = 1st eccentricity squared ≡ (a²-b²)/a² | ||
@@ -395,5 +382,5 @@ const e4 = e2*e2; // e⁴ | ||
get length() { | ||
const n = this.north, e = this.east, d = this.down; | ||
const { north, east, down } = this; | ||
return Math.sqrt(n*n + e*e + d*d); | ||
return Math.sqrt(north*north + east*east + down*down); | ||
} | ||
@@ -400,0 +387,0 @@ |
@@ -45,3 +45,3 @@ /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ | ||
* @param {number} lon - Longitude (in degrees). | ||
* @throws {TypeError} Non-numeric lat/lon. | ||
* @throws {TypeError} Invalid lat/lon. | ||
* | ||
@@ -53,4 +53,4 @@ * @example | ||
constructor(lat, lon) { | ||
if (isNaN(lat)) throw new TypeError(`Invalid lat ‘${lat}’`); | ||
if (isNaN(lon)) throw new TypeError(`Invalid lon ‘${lon}’`); | ||
if (isNaN(lat)) throw new TypeError(`invalid lat ‘${lat}’`); | ||
if (isNaN(lon)) throw new TypeError(`invalid lon ‘${lon}’`); | ||
@@ -70,7 +70,7 @@ this._lat = Dms.wrap90(Number(lat)); | ||
this._lat = isNaN(lat) ? Dms.wrap90(Dms.parse(lat)) : Dms.wrap90(Number(lat)); | ||
if (isNaN(this._lat)) throw new TypeError(`Invalid lat ‘${lat}’`); | ||
if (isNaN(this._lat)) throw new TypeError(`invalid lat ‘${lat}’`); | ||
} | ||
set latitude(lat) { | ||
this._lat = isNaN(lat) ? Dms.wrap90(Dms.parse(lat)) : Dms.wrap90(Number(lat)); | ||
if (isNaN(this._lat)) throw new TypeError(`Invalid latitude ‘${lat}’`); | ||
if (isNaN(this._lat)) throw new TypeError(`invalid latitude ‘${lat}’`); | ||
} | ||
@@ -87,11 +87,11 @@ | ||
this._lon = isNaN(lon) ? Dms.wrap180(Dms.parse(lon)) : Dms.wrap180(Number(lon)); | ||
if (isNaN(this._lon)) throw new TypeError(`Invalid lon ‘${lon}’`); | ||
if (isNaN(this._lon)) throw new TypeError(`invalid lon ‘${lon}’`); | ||
} | ||
set lng(lon) { | ||
this._lon = isNaN(lon) ? Dms.wrap180(Dms.parse(lon)) : Dms.wrap180(Number(lon)); | ||
if (isNaN(this._lon)) throw new TypeError(`Invalid lng ‘${lon}’`); | ||
if (isNaN(this._lon)) throw new TypeError(`invalid lng ‘${lon}’`); | ||
} | ||
set longitude(lon) { | ||
this._lon = isNaN(lon) ? Dms.wrap180(Dms.parse(lon)) : Dms.wrap180(Number(lon)); | ||
if (isNaN(this._lon)) throw new TypeError(`Invalid longitude ‘${lon}’`); | ||
if (isNaN(this._lon)) throw new TypeError(`invalid longitude ‘${lon}’`); | ||
} | ||
@@ -169,3 +169,3 @@ | ||
* @returns {number} Distance between this point and destination point, in same units as radius. | ||
* @throws {TypeError} Point is not LatLon object, Radius is not a number. | ||
* @throws {TypeError} Invalid point/radius. | ||
* | ||
@@ -178,4 +178,4 @@ * @example | ||
distanceTo(point, radius=6371e3) { | ||
if (!(point instanceof LatLonNvectorSpherical)) throw new TypeError('‘point’ is not (NvectorSpherical) LatLon object'); | ||
if (isNaN(radius)) throw new TypeError('Radius is not a number'); | ||
if (!(point instanceof LatLonNvectorSpherical)) throw new TypeError(`invalid point ‘${point}’`); | ||
if (isNaN(radius)) throw new TypeError(`invalid radius ‘${radius}’`); | ||
@@ -200,3 +200,3 @@ const R = Number(radius); | ||
* @returns {number} Initial bearing in degrees from north (0°..360°). | ||
* @throws {TypeError} Point is not LatLon object. | ||
* @throws {TypeError} Invalid point. | ||
* | ||
@@ -209,3 +209,4 @@ * @example | ||
initialBearingTo(point) { | ||
if (!(point instanceof LatLonNvectorSpherical)) throw new TypeError('‘point’ is not (NvectorSpherical) LatLon object'); | ||
if (!(point instanceof LatLonNvectorSpherical)) throw new TypeError(`invalid point ‘${point}’`); | ||
if (this.equals(point)) return NaN; // coincident points | ||
@@ -232,3 +233,3 @@ const p1 = this.toNvector(); | ||
* @returns {number} Final bearing in degrees from north (0°..360°). | ||
* @throws {TypeError} Point is not LatLon object. | ||
* @throws {TypeError} Invalid point. | ||
* | ||
@@ -241,3 +242,3 @@ * @example | ||
finalBearingTo(point) { | ||
if (!(point instanceof LatLonNvectorSpherical)) throw new TypeError('‘point’ is not (NvectorSpherical) LatLon object'); | ||
if (!(point instanceof LatLonNvectorSpherical)) throw new TypeError(`invalid point ‘${point}’`); | ||
@@ -254,3 +255,3 @@ // get initial bearing from destination point to this point & reverse it by adding 180° | ||
* @returns {LatLon} Midpoint between this point and destination point. | ||
* @throws {TypeError} Point is not LatLon object. | ||
* @throws {TypeError} Invalid point. | ||
* | ||
@@ -263,3 +264,3 @@ * @example | ||
midpointTo(point) { | ||
if (!(point instanceof LatLonNvectorSpherical)) throw new TypeError('‘point’ is not (NvectorSpherical) LatLon object'); | ||
if (!(point instanceof LatLonNvectorSpherical)) throw new TypeError(`invalid point ‘${point}’`); | ||
@@ -281,3 +282,3 @@ const n1 = this.toNvector(); | ||
* @returns {LatLon} Intermediate point between this point and destination point. | ||
* @throws {TypeError} Point is not LatLon object. | ||
* @throws {TypeError} Invalid point/fraction. | ||
* | ||
@@ -290,3 +291,4 @@ * @example | ||
intermediatePointTo(point, fraction) { | ||
if (!(point instanceof LatLonNvectorSpherical)) throw new TypeError('‘point’ is not (NvectorSpherical) LatLon object'); | ||
if (!(point instanceof LatLonNvectorSpherical)) throw new TypeError(`invalid point ‘${point}’`); | ||
if (isNaN(fraction)) throw new TypeError(`invalid fraction ‘${fraction}’`); | ||
@@ -322,3 +324,3 @@ // angular distance between points; tanδ = |n₁×n₂| / n₁⋅n₂ | ||
* @returns {LatLon} Intermediate point between this point and destination point. | ||
* @throws {TypeError} Point is not LatLon object. | ||
* @throws {TypeError} Invalid point. | ||
* | ||
@@ -331,3 +333,3 @@ * @example | ||
intermediatePointOnChordTo(point, fraction) { | ||
if (!(point instanceof LatLonNvectorSpherical)) throw new TypeError('‘point’ is not (NvectorSpherical) LatLon object'); | ||
if (!(point instanceof LatLonNvectorSpherical)) throw new TypeError(`invalid point ‘${point}’`); | ||
@@ -390,3 +392,3 @@ const n1 = this.toNvector(); | ||
* @returns {LatLon} Destination point (null if no unique intersection defined) | ||
* @throws {TypeError} Parameter is not LatLon object. | ||
* @throws {TypeError} Invalid parameter. | ||
* | ||
@@ -399,7 +401,9 @@ * @example | ||
static intersection(path1start, path1brngEnd, path2start, path2brngEnd) { | ||
if (!(path1start instanceof LatLonNvectorSpherical)) throw new TypeError('‘path1start’ is not LatLon object'); | ||
if (!(path2start instanceof LatLonNvectorSpherical)) throw new TypeError('‘path2start’ is not LatLon object'); | ||
if (!(path1brngEnd instanceof LatLonNvectorSpherical) && isNaN(path1brngEnd)) throw new TypeError('‘path1brngEnd’ is not LatLon object or bearing'); | ||
if (!(path2brngEnd instanceof LatLonNvectorSpherical) && isNaN(path2brngEnd)) throw new TypeError('‘path2brngEnd’ is not LatLon object or bearing'); | ||
if (!(path1start instanceof LatLonNvectorSpherical)) throw new TypeError(`invalid path1start ‘${path1start}’`); | ||
if (!(path2start instanceof LatLonNvectorSpherical)) throw new TypeError(`invalid path2start ‘${path2start}’`); | ||
if (!(path1brngEnd instanceof LatLonNvectorSpherical) && isNaN(path1brngEnd)) throw new TypeError(`invalid path1brngEnd ‘${path1brngEnd}’`); | ||
if (!(path2brngEnd instanceof LatLonNvectorSpherical) && isNaN(path2brngEnd)) throw new TypeError(`invalid path2brngEnd ‘${path2brngEnd}’`); | ||
if (path1start.equals(path2start)) return new LatLonNvectorSpherical(path1start.lat, path2start.lon); // coincident points | ||
// if c1 & c2 are great circles through start and end points (or defined by start point + bearing), | ||
@@ -420,3 +424,3 @@ // then candidate intersections are simply c1 × c2 & c2 × c1; most of the work is deciding correct | ||
} else { // path 1 defined by initial bearing | ||
c1 = path1start.greatCircle(Number(path1brngEnd)); | ||
c1 = path1start.greatCircle(path1brngEnd); | ||
path1def = 'bearing'; | ||
@@ -428,3 +432,3 @@ } | ||
} else { // path 2 defined by initial bearing | ||
c2 = path2start.greatCircle(Number(path2brngEnd)); | ||
c2 = path2start.greatCircle(path2brngEnd); | ||
path2def = 'bearing'; | ||
@@ -485,3 +489,3 @@ } | ||
* @returns {number} Distance to great circle (-ve if to left, +ve if to right of path). | ||
* @throws {TypeError} Parameter is not LatLon object. | ||
* @throws {TypeError} Invalid parameter. | ||
* | ||
@@ -498,4 +502,7 @@ * @example | ||
crossTrackDistanceTo(pathStart, pathBrngEnd, radius=6371e3) { | ||
if (!(pathStart instanceof LatLonNvectorSpherical)) throw new TypeError('‘pathStart’ is not (NvectorSpherical) LatLon object'); | ||
if (!(pathStart instanceof LatLonNvectorSpherical)) throw new TypeError(`invalid pathStart ‘${pathStart}’`); | ||
if (!(pathBrngEnd instanceof LatLonNvectorSpherical || !isNaN(pathBrngEnd))) throw new TypeError(`invalid pathBrngEnd ‘${pathBrngEnd}’`); | ||
if (this.equals(pathStart)) return NaN; // coincident points | ||
const p = this.toNvector(); | ||
@@ -506,3 +513,3 @@ const R = Number(radius); | ||
? pathStart.toNvector().cross(pathBrngEnd.toNvector()) // great circle defined by two points | ||
: pathStart.greatCircle(Number(pathBrngEnd)); // great circle defined by point + bearing | ||
: pathStart.greatCircle(pathBrngEnd); // great circle defined by point + bearing | ||
@@ -515,5 +522,38 @@ const α = gc.angleTo(p) - π/2; // angle between point & great-circle | ||
// TODO: alongTrackDistanceTo | ||
/** | ||
* Returns how far ‘this’ point is along a path from from start-point, heading on bearing or towards | ||
* end-point. That is, if a perpendicular is drawn from ‘this’ point to the (great circle) path, the | ||
* along-track distance is the distance from the start point to where the perpendicular crosses the | ||
* path. | ||
* | ||
* @param {LatLon} pathStart - Start point of great circle path. | ||
* @param {LatLon|number} pathBrngEnd - End point of great circle path or initial bearing from great circle start point. | ||
* @param {number} [radius=6371e3] - (Mean) radius of earth (defaults to radius in metres). | ||
* @returns {number} Distance along great circle to point nearest ‘this’ point. | ||
* | ||
* @example | ||
* const pCurrent = new LatLon(53.2611, -0.7972); | ||
* const p1 = new LatLon(53.3206, -1.7297); | ||
* const p2 = new LatLon(53.1887, 0.1334); | ||
* const d = pCurrent.alongTrackDistanceTo(p1, p2); // 62.331 km | ||
*/ | ||
alongTrackDistanceTo(pathStart, pathBrngEnd, radius=6371e3) { | ||
if (!(pathStart instanceof LatLonNvectorSpherical)) throw new TypeError(`invalid pathStart ‘${pathStart}’`); | ||
if (!(pathBrngEnd instanceof LatLonNvectorSpherical || !isNaN(pathBrngEnd))) throw new TypeError(`invalid pathBrngEnd ‘${pathBrngEnd}’`); | ||
const p = this.toNvector(); | ||
const R = Number(radius); | ||
const gc = pathBrngEnd instanceof LatLonNvectorSpherical // (note JavaScript is not good at method overloading) | ||
? pathStart.toNvector().cross(pathBrngEnd.toNvector()) // great circle defined by two points | ||
: pathStart.greatCircle(pathBrngEnd); // great circle defined by point + bearing | ||
const pat = gc.cross(p).cross(gc); // along-track point c × p × c | ||
const α = pathStart.toNvector().angleTo(pat, gc); // angle between start point and along-track point | ||
return α * R; | ||
} | ||
/** | ||
@@ -543,3 +583,3 @@ * Returns closest point on great circle segment between point1 & point2 to ‘this’ point. | ||
if (this.isWithinExtent(point1, point2)) { | ||
if (this.isWithinExtent(point1, point2) && !point1.equals(point2)) { | ||
// closer to segment than to its endpoints, find closest point on segment | ||
@@ -555,3 +595,4 @@ const n0 = this.toNvector(), n1 = point1.toNvector(), n2 = point2.toNvector(); | ||
const d2 = this.distanceTo(point2); | ||
p = d1<d2 ? point1 : point2; | ||
const pCloser = d1<d2 ? point1 : point2; | ||
p = new LatLonNvectorSpherical(pCloser.lat, pCloser.lon); | ||
} | ||
@@ -567,3 +608,4 @@ | ||
* If this point is not on the great circle defined by point1 & point 2, returns whether it is | ||
* within the area bound by perpendiculars to the great circle at each point. | ||
* within the area bound by perpendiculars to the great circle at each point (in the same | ||
* hemisphere). | ||
* | ||
@@ -580,2 +622,4 @@ * @param {LatLon} point1 - First point defining segment. | ||
isWithinExtent(point1, point2) { | ||
if (point1.equals(point2)) return this.equals(point1); // null segment | ||
const n0 = this.toNvector(), n1 = point1.toNvector(), n2 = point2.toNvector(); // n-vectors | ||
@@ -591,3 +635,5 @@ | ||
return extent1>=0 && extent2>=0; | ||
const isSameHemisphere = n0.dot(n1)>=0 && n0.dot(n2)>=0; | ||
return extent1>=0 && extent2>=0 && isSameHemisphere; | ||
} | ||
@@ -670,2 +716,4 @@ | ||
if (!isFinite(x) || !isFinite(y)) return null; // coincident points? | ||
const n = n1.plus(eX.times(x)).plus(eY.times(y)); // note don't use z component; assume points at same height | ||
@@ -724,2 +772,4 @@ | ||
* | ||
* Uses Girard’s theorem: A = [Σθᵢ − (n−2)·π]·R² | ||
* | ||
* @param {LatLon[]} polygon - Array of points defining vertices of the polygon. | ||
@@ -734,4 +784,2 @@ * @param {number} [radius=6371e3] - (Mean) radius of earth (defaults to radius in metres). | ||
static areaOf(polygon, radius=6371e3) { | ||
// uses Girard’s theorem: A = [Σθᵢ − (n−2)·π]·R² | ||
const R = Number(radius); | ||
@@ -745,3 +793,3 @@ | ||
// get great-circle vector for each vertex | ||
// get great-circle vector for each segment | ||
const c = []; | ||
@@ -764,2 +812,6 @@ for (let v=0; v<n; v++) { | ||
// note: angle between two sides of a spherical triangle is acos(c₁·c₂) where cₙ is the | ||
// plane normal vector to the great circle representing the triangle side - use this instead | ||
// of angleTo()? | ||
const E = (Σθ - (n-2)*π); // spherical excess (in steradians) | ||
@@ -801,3 +853,3 @@ const A = E * R*R; // area in units of R² | ||
* @returns {bool} True if points have identical latitude and longitude values. | ||
* @throws {TypeError} Point is not LatLon object. | ||
* @throws {TypeError} Invalid point. | ||
* | ||
@@ -810,6 +862,6 @@ * @example | ||
equals(point) { | ||
if (!(point instanceof LatLonNvectorSpherical)) throw new TypeError('‘point’ is not (NvectorSpherical) LatLon object'); | ||
if (!(point instanceof LatLonNvectorSpherical)) throw new TypeError(`invalid point ‘${point}’`); | ||
if (this.lat != point.lat) return false; | ||
if (this.lon != point.lon) return false; | ||
if (Math.abs(this.lat - point.lat) > Number.EPSILON) return false; | ||
if (Math.abs(this.lon - point.lon) > Number.EPSILON) return false; | ||
@@ -841,3 +893,3 @@ return true; | ||
* const d = greenwich.toString(); // 51.4778°N, 000.0015°W | ||
* const dms = greenwich.toString('dms', 2); // 51°28′40″N, 000°00′05″W | ||
* const dms = greenwich.toString('dms', 2); // 51°28′40.37″N, 000°00′05.29″W | ||
* const [lat, lon] = greenwich.toString('n').split(','); // 51.4778, -0.0015 | ||
@@ -847,3 +899,3 @@ */ | ||
// note: explicitly set dp to undefined for passing through to toLat/toLon | ||
if (![ 'd', 'dm', 'dms', 'n' ].includes(format)) throw new RangeError(`Invalid format ‘${format}’`); | ||
if (![ 'd', 'dm', 'dms', 'n' ].includes(format)) throw new RangeError(`invalid format ‘${format}’`); | ||
@@ -850,0 +902,0 @@ if (format == 'n') { // signed numeric degrees |
@@ -42,3 +42,3 @@ /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ | ||
* @param {number} lon - Longitude (in degrees). | ||
* @throws {TypeError} Non-numeric lat/lon. | ||
* @throws {TypeError} Invalid lat/lon. | ||
* | ||
@@ -50,4 +50,4 @@ * @example | ||
constructor(lat, lon) { | ||
if (isNaN(lat)) throw new TypeError(`Invalid lat ‘${lat}’`); | ||
if (isNaN(lon)) throw new TypeError(`Invalid lon ‘${lon}’`); | ||
if (isNaN(lat)) throw new TypeError(`invalid lat ‘${lat}’`); | ||
if (isNaN(lon)) throw new TypeError(`invalid lon ‘${lon}’`); | ||
@@ -67,7 +67,7 @@ this._lat = Dms.wrap90(lat); | ||
this._lat = isNaN(lat) ? Dms.wrap90(Dms.parse(lat)) : Dms.wrap90(lat); | ||
if (isNaN(this._lat)) throw new TypeError(`Invalid lat ‘${lat}’`); | ||
if (isNaN(this._lat)) throw new TypeError(`invalid lat ‘${lat}’`); | ||
} | ||
set latitude(lat) { | ||
this._lat = isNaN(lat) ? Dms.wrap90(Dms.parse(lat)) : Dms.wrap90(lat); | ||
if (isNaN(this._lat)) throw new TypeError(`Invalid latitude ‘${lat}’`); | ||
if (isNaN(this._lat)) throw new TypeError(`invalid latitude ‘${lat}’`); | ||
} | ||
@@ -84,11 +84,11 @@ | ||
this._lon = isNaN(lon) ? Dms.wrap180(Dms.parse(lon)) : Dms.wrap180(lon); | ||
if (isNaN(this._lon)) throw new TypeError(`Invalid lon ‘${lon}’`); | ||
if (isNaN(this._lon)) throw new TypeError(`invalid lon ‘${lon}’`); | ||
} | ||
set lng(lon) { | ||
this._lon = isNaN(lon) ? Dms.wrap180(Dms.parse(lon)) : Dms.wrap180(lon); | ||
if (isNaN(this._lon)) throw new TypeError(`Invalid lng ‘${lon}’`); | ||
if (isNaN(this._lon)) throw new TypeError(`invalid lng ‘${lon}’`); | ||
} | ||
set longitude(lon) { | ||
this._lon = isNaN(lon) ? Dms.wrap180(Dms.parse(lon)) : Dms.wrap180(lon); | ||
if (isNaN(this._lon)) throw new TypeError(`Invalid longitude ‘${lon}’`); | ||
if (isNaN(this._lon)) throw new TypeError(`invalid longitude ‘${lon}’`); | ||
} | ||
@@ -121,3 +121,3 @@ | ||
* @returns {LatLon} Latitude/longitude point. | ||
* @throws {TypeError} Invalid coordinate. | ||
* @throws {TypeError} Invalid point. | ||
* | ||
@@ -135,4 +135,4 @@ * @example | ||
static parse(...args) { | ||
if (args.length == 0) throw new TypeError('Invalid (empty) coordinate'); | ||
if (args[0]===null || args[1]===null) throw new TypeError('Invalid (null) coordinate'); | ||
if (args.length == 0) throw new TypeError('invalid (empty) point'); | ||
if (args[0]===null || args[1]===null) throw new TypeError('invalid (null) point'); | ||
@@ -145,3 +145,3 @@ let lat=undefined, lon=undefined; | ||
lon = Dms.wrap180(Dms.parse(lon)); | ||
if (isNaN(lat) || isNaN(lon)) throw new TypeError(`Invalid coordinate ‘${args.toString()}’`); | ||
if (isNaN(lat) || isNaN(lon)) throw new TypeError(`invalid point ‘${args.toString()}’`); | ||
} | ||
@@ -153,3 +153,3 @@ | ||
lon = Dms.wrap180(Dms.parse(lon)); | ||
if (isNaN(lat) || isNaN(lon)) throw new TypeError(`Invalid coordinate ‘${args[0]}’`); | ||
if (isNaN(lat) || isNaN(lon)) throw new TypeError(`invalid point ‘${args[0]}’`); | ||
} | ||
@@ -170,6 +170,6 @@ | ||
} | ||
if (isNaN(lat) || isNaN(lon)) throw new TypeError(`Invalid coordinate ‘${JSON.stringify(args[0])}’`); | ||
if (isNaN(lat) || isNaN(lon)) throw new TypeError(`invalid point ‘${JSON.stringify(args[0])}’`); | ||
} | ||
if (isNaN(lat) || isNaN(lon)) throw new TypeError(`Invalid coordinate ‘${args.toString()}’`); | ||
if (isNaN(lat) || isNaN(lon)) throw new TypeError(`invalid point ‘${args.toString()}’`); | ||
@@ -188,3 +188,3 @@ return new LatLonSpherical(lat, lon); | ||
* @returns {number} Distance between this point and destination point, in same units as radius. | ||
* @throws {TypeError} Radius is not a number. | ||
* @throws {TypeError} Invalid radius. | ||
* | ||
@@ -199,3 +199,3 @@ * @example | ||
if (!(point instanceof LatLonSpherical)) point = LatLonSpherical.parse(point); // allow literal forms | ||
if (isNaN(radius)) throw new TypeError('Radius is not a number'); | ||
if (isNaN(radius)) throw new TypeError(`invalid radius ‘${radius}’`); | ||
@@ -233,2 +233,3 @@ // a = sin²(Δφ/2) + cos(φ1)⋅cos(φ2)⋅sin²(Δλ/2) | ||
if (!(point instanceof LatLonSpherical)) point = LatLonSpherical.parse(point); // allow literal forms | ||
if (this.equals(point)) return NaN; // coincident points | ||
@@ -308,3 +309,3 @@ // tanθ = sinΔλ⋅cosφ2 / cosφ1⋅sinφ2 − sinφ1⋅cosφ2⋅cosΔλ | ||
return new LatLonSpherical(lat, Dms.wrap180(lon)); | ||
return new LatLonSpherical(lat, lon); | ||
} | ||
@@ -327,2 +328,3 @@ | ||
if (!(point instanceof LatLonSpherical)) point = LatLonSpherical.parse(point); // allow literal forms | ||
if (this.equals(point)) return new LatLonSpherical(this.lat, this.lon); // coincident points | ||
@@ -352,3 +354,3 @@ const φ1 = this.lat.toRadians(), λ1 = this.lon.toRadians(); | ||
return new LatLonSpherical(lat, Dms.wrap180(lon)); | ||
return new LatLonSpherical(lat, lon); | ||
} | ||
@@ -389,3 +391,3 @@ | ||
return new LatLonSpherical(lat, Dms.wrap180(lon)); | ||
return new LatLonSpherical(lat, lon); | ||
} | ||
@@ -411,2 +413,4 @@ | ||
if (!(p2 instanceof LatLonSpherical)) p2 = LatLonSpherical.parse(p2); // allow literal forms | ||
if (isNaN(brng1)) throw new TypeError(`invalid brng1 ‘${brng1}’`); | ||
if (isNaN(brng2)) throw new TypeError(`invalid brng2 ‘${brng2}’`); | ||
@@ -423,3 +427,3 @@ // see www.edwilliams.org/avform.htm#Intersection | ||
+ Math.cos(φ1) * Math.cos(φ2) * Math.sin(Δλ/2) * Math.sin(Δλ/2))); | ||
if (δ12 == 0) return null; | ||
if (Math.abs(δ12) < Number.EPSILON) return new LatLonSpherical(p1.lat, p1.lon); // coincident points | ||
@@ -439,3 +443,3 @@ // initial/final bearings between points | ||
if (Math.sin(α1) == 0 && Math.sin(α2) == 0) return null; // infinite intersections | ||
if (Math.sin(α1) * Math.sin(α2) < 0) return null; // ambiguous intersection | ||
if (Math.sin(α1) * Math.sin(α2) < 0) return null; // ambiguous intersection (antipodal?) | ||
@@ -454,3 +458,3 @@ const cosα3 = -Math.cos(α1)*Math.cos(α2) + Math.sin(α1)*Math.sin(α2)*Math.cos(δ12); | ||
return new LatLonSpherical(lat, Dms.wrap180(lon)); | ||
return new LatLonSpherical(lat, lon); | ||
} | ||
@@ -555,2 +559,4 @@ | ||
static crossingParallels(point1, point2, latitude) { | ||
if (point1.equals(point2)) return null; // coincident points | ||
const φ = Number(latitude).toRadians(); | ||
@@ -632,3 +638,2 @@ | ||
* @returns {number} Bearing in degrees from north. | ||
* @throws {TypeError} Invalid coordinate. | ||
* | ||
@@ -642,2 +647,3 @@ * @example | ||
if (!(point instanceof LatLonSpherical)) point = LatLonSpherical.parse(point); // allow literal forms | ||
if (this.equals(point)) return NaN; // coincident points | ||
@@ -693,3 +699,3 @@ const φ1 = this.lat.toRadians(), φ2 = point.lat.toRadians(); | ||
return new LatLonSpherical(lat, Dms.wrap180(lon)); | ||
return new LatLonSpherical(lat, lon); | ||
} | ||
@@ -730,7 +736,7 @@ | ||
return new LatLonSpherical(lat, Dms.wrap180(lon)); | ||
return new LatLonSpherical(lat, lon); | ||
} | ||
/* Area - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ | ||
/* Area - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ | ||
@@ -752,4 +758,5 @@ | ||
// uses method due to Karney: osgeo-org.1560.x6.nabble.com/Area-of-a-spherical-polygon-td3841625.html; | ||
// for each edge of the polygon, tan(E/2) = tan(Δλ/2)·(tan(φ1/2) + tan(φ2/2)) / (1 + tan(φ1/2)·tan(φ2/2)) | ||
// for each edge of the polygon, tan(E/2) = tan(Δλ/2)·(tan(φ₁/2)+tan(φ₂/2)) / (1+tan(φ₁/2)·tan(φ₂/2)) | ||
// where E is the spherical excess of the trapezium obtained by extending the edge to the equator | ||
// (Karney's method is probably more efficient than the more widely known L’Huilier’s Theorem) | ||
@@ -820,4 +827,4 @@ const R = radius; | ||
if (this.lat != point.lat) return false; | ||
if (this.lon != point.lon) return false; | ||
if (Math.abs(this.lat - point.lat) > Number.EPSILON) return false; | ||
if (Math.abs(this.lon - point.lon) > Number.EPSILON) return false; | ||
@@ -855,3 +862,3 @@ return true; | ||
// note: explicitly set dp to undefined for passing through to toLat/toLon | ||
if (![ 'd', 'dm', 'dms', 'n' ].includes(format)) throw new RangeError(`Invalid format ‘${format}’`); | ||
if (![ 'd', 'dm', 'dms', 'n' ].includes(format)) throw new RangeError(`invalid format ‘${format}’`); | ||
@@ -858,0 +865,0 @@ if (format == 'n') { // signed numeric degrees |
49
mgrs.js
@@ -73,7 +73,7 @@ /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ | ||
const errors = []; // check & report all other possible errors rather than reporting one-by-one | ||
if (band.length!=1 || latBands.indexOf(band) == -1) errors.push(`Invalid MGRS band ‘${band}’`); | ||
if (e100k.length!=1 || e100kLetters[(zone-1)%3].indexOf(e100k) == -1) errors.push(`Invalid MGRS 100km grid square column ‘${e100k}’ for zone ${zone}`); | ||
if (n100k.length!=1 || n100kLetters[0].indexOf(n100k) == -1) errors.push(`Invalid MGRS 100km grid square row ‘${n100k}’`); | ||
if (isNaN(Number(easting))) errors.push(`Invalid MGRS easting ‘${easting}’`); | ||
if (isNaN(Number(northing))) errors.push(`Invalid MGRS northing ‘${northing}’`); | ||
if (band.length!=1 || latBands.indexOf(band) == -1) errors.push(`invalid MGRS band ‘${band}’`); | ||
if (e100k.length!=1 || e100kLetters[(zone-1)%3].indexOf(e100k) == -1) errors.push(`invalid MGRS 100km grid square column ‘${e100k}’ for zone ${zone}`); | ||
if (n100k.length!=1 || n100kLetters[0].indexOf(n100k) == -1) errors.push(`invalid MGRS 100km grid square row ‘${n100k}’`); | ||
if (isNaN(Number(easting))) errors.push(`invalid MGRS easting ‘${easting}’`); | ||
if (isNaN(Number(northing))) errors.push(`invalid MGRS northing ‘${northing}’`); | ||
if (errors.length > 0) throw new RangeError(errors.join(', ')); | ||
@@ -94,6 +94,10 @@ | ||
* | ||
* @returns {Utm} UTM coordinate equivalent to this MGRS grid reference | ||
* Grid references refer to squares rather than points (with the size of the square indicated | ||
* by the precision of the reference); this conversion will return the UTM coordinate of the SW | ||
* corner of the grid reference square. | ||
* | ||
* @returns {Utm} UTM coordinate of SW corner of this MGRS grid reference. | ||
* | ||
* @example | ||
* const mgrsRef = new Mgrs(31, 'U', 'D', 'Q', 48251, 11932); | ||
* const mgrsRef = Mgrs.parse('31U DQ 48251 11932'); | ||
* const utmCoord = mgrsRef.toUtm(); // 31 N 448251 5411932 | ||
@@ -146,7 +150,7 @@ */ | ||
static parse(mgrsGridRef) { | ||
if (!mgrsGridRef) throw new Error(`Invalid MGRS grid reference ‘${mgrsGridRef}’`); | ||
if (!mgrsGridRef) throw new Error(`invalid MGRS grid reference ‘${mgrsGridRef}’`); | ||
// check for military-style grid reference with no separators | ||
if (!mgrsGridRef.trim().match(/\s/)) { | ||
if (!Number(mgrsGridRef.slice(0, 2))) throw new Error(`Invalid MGRS grid reference ‘${mgrsGridRef}’`); | ||
if (!Number(mgrsGridRef.slice(0, 2))) throw new Error(`invalid MGRS grid reference ‘${mgrsGridRef}’`); | ||
let en = mgrsGridRef.trim().slice(5); // get easting/northing following zone/band/100ksq | ||
@@ -160,3 +164,3 @@ en = en.slice(0, en.length/2)+' '+en.slice(-en.length/2); // separate easting/northing | ||
if (ref==null || ref.length!=4) throw new Error(`Invalid MGRS grid reference ‘${mgrsGridRef}’`); | ||
if (ref==null || ref.length!=4) throw new Error(`invalid MGRS grid reference ‘${mgrsGridRef}’`); | ||
@@ -192,3 +196,7 @@ // split gzd into zone/band | ||
* | ||
* Note that MGRS grid references get truncated, not rounded (unlike UTM coordinates). | ||
* Note that MGRS grid references get truncated, not rounded (unlike UTM coordinates); grid | ||
* references indicate a bounding square, rather than a point, with the size of the square | ||
* indicated by the precision - a precision of 10 indicates a 1-metre square, a precision of 4 | ||
* indicates a 1,000-metre square (hence 31U DQ 48 11 indicates a 1km square with SW corner at | ||
* 31 N 448000 5411000, which would include the 1m square 31U DQ 48251 11932). | ||
* | ||
@@ -203,19 +211,16 @@ * @param {number} [digits=10] - Precision of returned grid reference (eg 4 = km, 10 = m). | ||
toString(digits=10) { | ||
if (![ 2,4,6,8,10 ].includes(Number(digits))) throw new RangeError(`Invalid precision ‘${digits}’`); // eslint-disable-line comma-spacing | ||
if (![ 2, 4, 6, 8, 10 ].includes(Number(digits))) throw new RangeError(`invalid precision ‘${digits}’`); | ||
const zone = this.zone.toString().padStart(2, '0'); // ensure leading zero | ||
const band = this.band; | ||
const { zone, band, e100k, n100k, easting, northing } = this; | ||
const e100k = this.e100k; | ||
const n100k = this.n100k; | ||
// truncate to required precision | ||
const eRounded = Math.floor(this.easting/Math.pow(10, 5-digits/2)); | ||
const nRounded = Math.floor(this.northing/Math.pow(10, 5-digits/2)); | ||
const eRounded = Math.floor(easting/Math.pow(10, 5-digits/2)); | ||
const nRounded = Math.floor(northing/Math.pow(10, 5-digits/2)); | ||
// ensure leading zeros | ||
const easting = eRounded.toString().padStart(digits/2, '0'); | ||
const northing = nRounded.toString().padStart(digits/2, '0'); | ||
const zPadded = zone.toString().padStart(2, '0'); | ||
const ePadded = eRounded.toString().padStart(digits/2, '0'); | ||
const nPadded = nRounded.toString().padStart(digits/2, '0'); | ||
return `${zone}${band} ${e100k}${n100k} ${easting} ${northing}`; | ||
return `${zPadded}${band} ${e100k}${n100k} ${ePadded} ${nPadded}`; | ||
} | ||
@@ -222,0 +227,0 @@ } |
@@ -56,4 +56,4 @@ /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ | ||
if (isNaN(easting) || this.easting<0 || this.easting>700e3) throw new RangeError(`Invalid easting ‘${easting}’`); | ||
if (isNaN(northing) || this.northing<0 || this.northing>1300e3) throw new RangeError(`Invalid northing ‘${northing}’`); | ||
if (isNaN(easting) || this.easting<0 || this.easting>700e3) throw new RangeError(`invalid easting ‘${easting}’`); | ||
if (isNaN(northing) || this.northing<0 || this.northing>1300e3) throw new RangeError(`invalid northing ‘${northing}’`); | ||
} | ||
@@ -83,4 +83,3 @@ | ||
toLatLon(datum=LatLonEllipsoidal.datums.WGS84) { | ||
const E = this.easting; | ||
const N = this.northing; | ||
const { easting: E, northing: N } = this; | ||
@@ -164,3 +163,3 @@ const a = 6377563.396, b = 6356256.909; // Airy 1830 major & minor semi-axes | ||
match = gridref.match(/^[A-Z]{2}\s*[0-9]+\s*[0-9]+$/i); | ||
if (!match) throw new Error(`Invalid grid reference ‘${gridref}’`); | ||
if (!match) throw new Error(`invalid grid reference ‘${gridref}’`); | ||
@@ -175,3 +174,3 @@ // get numeric values of letter references, mapping A->0, B->1, C->2, etc: | ||
// sanity check | ||
if (l1<8 || l1 > 18) throw new Error(`Invalid grid reference ‘${gridref}’`); | ||
if (l1<8 || l1 > 18) throw new Error(`invalid grid reference ‘${gridref}’`); | ||
@@ -188,3 +187,3 @@ // convert grid letters into 100km-square indexes from false origin (grid square SV): | ||
// validation | ||
if (en[0].length != en[1].length) throw new Error(`Invalid grid reference ‘${gridref}’`); | ||
if (en[0].length != en[1].length) throw new Error(`invalid grid reference ‘${gridref}’`); | ||
@@ -214,6 +213,5 @@ // standardise to 10-digit refs (metres) | ||
toString(digits=10) { | ||
if (![ 0,2,4,6,8,10,12,14,16 ].includes(Number(digits))) throw new RangeError(`Invalid precision ‘${digits}’`); // eslint-disable-line comma-spacing | ||
if (![ 0,2,4,6,8,10,12,14,16 ].includes(Number(digits))) throw new RangeError(`invalid precision ‘${digits}’`); // eslint-disable-line comma-spacing | ||
let e = this.easting; | ||
let n = this.northing; | ||
let { easting: e, northing: n } = this; | ||
@@ -220,0 +218,0 @@ // use digits = 0 to return numeric format (in metres) - note northing may be >= 1e7 |
@@ -11,3 +11,3 @@ { | ||
"author": "Chris Veness", | ||
"version": "2.0.0", | ||
"version": "2.0.1", | ||
"license": "MIT", | ||
@@ -27,7 +27,8 @@ "engines": { | ||
"chai": "^4.2.0", | ||
"eslint": "^5.13.0", | ||
"esm": "^3.2.4", | ||
"coveralls": "^3.0.3", | ||
"eslint": "^5.16.0", | ||
"esm": "^3.2.22", | ||
"jsdoc": "^3.5.5", | ||
"mocha": "^5.2.0", | ||
"nyc": "^13.2.0" | ||
"mocha": "^6.1.2", | ||
"nyc": "^13.3.0" | ||
}, | ||
@@ -34,0 +35,0 @@ "eslintConfig": { |
156
README.md
Geodesy functions | ||
================= | ||
[![Build Status](https://travis-ci.org/chrisveness/geodesy.svg?branch=v2.0.0) | ||
](https://travis-ci.org/chrisveness/geodesy) | ||
[![Coverage Status](https://coveralls.io/repos/github/chrisveness/geodesy/bad | ||
ge.svg?branch=master)](https://coveralls.io/github/chrisveness/geodesy?branch | ||
=v2.0.0) | ||
[![Build Status](https://travis-ci.org/chrisveness/geodesy.svg?branch=master)](https://travis-ci.org/chrisveness/geodesy) | ||
[![Coverage Status](https://coveralls.io/repos/github/chrisveness/geodesy/badge.svg?branch=master)](https://coveralls.io/github/chrisveness/geodesy?branch=master) | ||
[![Documentation](https://img.shields.io/badge/docs-www.movable--type.co.uk%2Fscripts%2Fgeodesy--library.html-lightgrey.svg)](https://www.movable-type.co.uk/scripts/geodesy-library.html) | ||
@@ -63,8 +61,8 @@ These libraries started life (a long time ago) as simple ‘latitude/longitude’ code fragments | ||
```` | ||
import LatLon from '/js/geodesy/latlon-spherical.js | ||
```javascript | ||
import LatLon from '/js/geodesy/latlon-spherical.js'; | ||
const p1 = new LatLon(52.205, 0.119); | ||
const p2 = new LatLon(48.857, 2.351); | ||
const d = p1.distanceTo(p2); // 404.3×10³ m | ||
```` | ||
``` | ||
@@ -74,4 +72,4 @@ - or to find the destination point for a given distance and initial bearing on an ellipsoidal model | ||
```` | ||
import LatLon from '/js/geodesy/latlon-ellipsoidal-vincency.js | ||
```javascript | ||
import LatLon from '/js/geodesy/latlon-ellipsoidal-vincency.js'; | ||
const p1 = new LatLon(-37.95103, 144.42487); | ||
@@ -81,6 +79,7 @@ const dist = 54972.271; | ||
const p2 = p1.destinationPoint(dist, brng); // 37.6528°S, 143.9265°E | ||
```` | ||
``` | ||
Full documentation is available at [www.movable-type.co.uk/scripts/geodesy-library.html](https://www.movable-type.co.uk/scripts/geodesy-library.html), | ||
and there is a full [test suite](https://www.movable-type.co.uk/scripts/test/geodesy-test.html). | ||
and tests in the [browser](https://www.movable-type.co.uk/scripts/test/geodesy-test.html) as well as | ||
[Travis CI](https://travis-ci.org/chrisveness/geodesy). | ||
@@ -98,29 +97,33 @@ Usage | ||
<!doctype html><title>geodesy example</title><meta charset="utf-8"> | ||
<script type="module"> | ||
import LatLon from 'https://cdn.jsdelivr.net/gh/chrisveness/geodesy@2.0.0/latlon-spherical.min.js'; | ||
```html | ||
<!doctype html><title>geodesy example</title><meta charset="utf-8"> | ||
<script type="module"> | ||
import LatLon from 'https://cdn.jsdelivr.net/gh/chrisveness/geodesy@2.0.1/latlon-spherical.min.js'; | ||
const p1 = new LatLon(50.06632, -5.71475); | ||
const p2 = new LatLon(58.64402, -3.07009); | ||
const p1 = new LatLon(50.06632, -5.71475); | ||
const p2 = new LatLon(58.64402, -3.07009); | ||
const d = p1.distanceTo(p2); | ||
console.assert(d.toFixed(3) == '968874.704'); | ||
const d = p1.distanceTo(p2); | ||
console.assert(d.toFixed(3) == '968874.704'); | ||
const mid = p1.midpointTo(p2); | ||
console.assert(mid.toString('dms') == '54°21′44″N, 004°31′51″W'); | ||
</script> | ||
const mid = p1.midpointTo(p2); | ||
console.assert(mid.toString('dms') == '54° 21′ 44″ N, 004° 31′ 51″ W'); | ||
</script> | ||
``` | ||
### Usage in Node.js | ||
The library can be used in a Node.js app from npm: | ||
The library can be used in a Node.js app from [npm](https://www.npmjs.com/package/geodesy): | ||
$ npm install geodesy | ||
$ node -r esm | ||
> import LatLon from 'geodesy/latlon-spherical'; | ||
> const p1 = new LatLon(50.06632, -5.71475); | ||
> const p2 = new LatLon(58.64402, -3.07009); | ||
> const d = p1.distanceTo(p2); | ||
> console.assert(d.toFixed(3) == '968874.704'); | ||
> const mid = p1.midpointTo(p2); | ||
> console.assert(mid.toString() == '54°21′44″N, 004°31′51″W'); | ||
```shell | ||
$ npm install geodesy | ||
$ node -r esm | ||
> import LatLon from 'geodesy/latlon-spherical'; | ||
> const p1 = new LatLon(50.06632, -5.71475); | ||
> const p2 = new LatLon(58.64402, -3.07009); | ||
> const d = p1.distanceTo(p2); | ||
> console.assert(d.toFixed(3) == '968874.704'); | ||
> const mid = p1.midpointTo(p2); | ||
> console.assert(mid.toString('dms') == '54° 21′ 44″ N, 004° 31′ 51″ W'); | ||
``` | ||
@@ -133,74 +136,87 @@ ### Other examples | ||
import LatLon from 'geodesy/latlon-ellipsoidal-vincenty.js'; | ||
```javascript | ||
import LatLon from 'geodesy/latlon-ellipsoidal-vincenty.js'; | ||
const p1 = new LatLon(50.06632, -5.71475); | ||
const p2 = new LatLon(58.64402, -3.07009); | ||
const p1 = new LatLon(50.06632, -5.71475); | ||
const p2 = new LatLon(58.64402, -3.07009); | ||
const d = p1.distanceTo(p2); | ||
console.assert(d.toFixed(3) == '969954.166'); | ||
const d = p1.distanceTo(p2); | ||
console.assert(d.toFixed(3) == '969954.166'); | ||
``` | ||
e.g. for UTM conversions: | ||
import Utm from 'geodesy/utm.js'; | ||
```javascript | ||
import Utm from 'geodesy/utm.js'; | ||
const utm = Utm.parse('48 N 377298.745 1483034.794'); | ||
const latlon = utm.toLatLon(); | ||
console.assert(latlon.toString('dms', 2) == '13°24′45.00″N, 103°52′00.00″E'); | ||
const utm = Utm.parse('48 N 377298.745 1483034.794'); | ||
const latlon = utm.toLatLon(); | ||
console.assert(latlon.toUtm().toString() == '48 N 377298.745 1483034.794'; | ||
console.assert(latlon.toString('dms', 2) == '13° 24′ 45.00″ N, 103° 52′ 00.00″ E'); | ||
console.assert(latlon.toUtm().toString() == '48 N 377298.745 1483034.794'; | ||
``` | ||
e.g. for MGRS/NATO map references: | ||
import Mgrs, { LatLon } from 'geodesy/mgrs.js'; | ||
```javascript | ||
import Mgrs, { LatLon } from 'geodesy/mgrs.js'; | ||
const mgrs = Mgrs.parse('31U DQ 48251 11932'); | ||
const latlon = mgrs.toUtm().toLatLon(); | ||
console.assert(latlon.toString('dms', 2) == '48°51′29.50″N, 002°17′40.16″E'); | ||
const mgrs = Mgrs.parse('31U DQ 48251 11932'); | ||
const latlon = mgrs.toUtm().toLatLon(); | ||
console.assert(latlon.toString('dms', 2) == '48° 51′ 29.50″ N, 002° 17′ 40.16″ E'); | ||
const p = LatLon.parse('51°28′40.37″N, 000°00′05.29″W'); | ||
const ref = p.toUtm().toMgrs(); | ||
console.assert(ref.toString() == '30U YC 08215 07233'); | ||
const p = LatLon.parse('51°28′40.37″N, 000°00′05.29″W'); | ||
const ref = p.toUtm().toMgrs(); | ||
console.assert(ref.toString() == '30U YC 08215 07233'); | ||
``` | ||
e.g. for OS grid references: | ||
import OsGridRef, { LatLon } from 'geodesy/osgridref.js'; | ||
```javascript | ||
import OsGridRef, { LatLon } from 'geodesy/osgridref.js'; | ||
const gridref = new OsGridRef(651409.903, 313177.270); | ||
const gridref = new OsGridRef(651409.903, 313177.270); | ||
const pWgs84 = OsGridRef.osGridToLatLon(gridref); | ||
console.assert(pWgs84.toString('dms', 4) == '52°39′28.7230″N, 001°42′57.7870″E'); | ||
const pWgs84 = gridref.toLatLon(); | ||
console.assert(pWgs84.toString('dms', 4) == '52° 39′ 28.7230″ N, 001° 42′ 57.7870″ E'); | ||
const pOsgb = OsGridRef.osGridToLatLon(gridref, LatLon.datums.OSGB36); | ||
console.assert(pOsgb.toString('dms', 4) == '52°39′27.2531″N, 001°43′04.5177″E'); | ||
const pOsgb = gridref.toLatLon(LatLon.datums.OSGB36); | ||
console.assert(pOsgb.toString('dms', 4) == '52° 39′ 27.2531″ N, 001° 43′ 04.5177″ E'); | ||
``` | ||
e.g. for testing if a point is enclosed within a polygon: | ||
import LatLon from 'geodesy/latlon-nvector-spherical.js'; | ||
```javascript | ||
import LatLon from 'geodesy/latlon-nvector-spherical.js'; | ||
const polygon = [ new LatLon(48,2), new LatLon(49,2), new LatLon(49,3), new LatLon(48,3) ]; | ||
const polygon = [ new LatLon(48,2), new LatLon(49,2), new LatLon(49,3), new LatLon(48,3) ]; | ||
const enclosed = new LatLon(48.9,2.4).isEnclosedBy(polygon) | ||
const enclosed = new LatLon(48.9,2.4).isEnclosedBy(polygon); | ||
console.assert(enclosed == true); | ||
``` | ||
console.assert(enclosed == true); | ||
e.g. greater parsing & presentation control: | ||
import LatLon from 'geodesy/latlon-spherical.js'; | ||
```javascript | ||
import LatLon from 'geodesy/latlon-spherical.js'; | ||
Dms.separator = ' '; // full-space separator between degrees-minutes-seconds | ||
const p1 = LatLon.parse('50°03′59″N, 005°42′53″W')); | ||
const p2 = LatLon.parse('58°38′38″N, 003°04′12″W')); | ||
const p1 = LatLon.parse({ lat: '50:03:59N', lng: '005:42:53W' }); | ||
const p2 = LatLon.parse({ lat: '58:38:38N', lng: '003:04:12W' }); | ||
const mid = p1.midpointTo(p2); | ||
Dms.separator = ' '; // full-space separator between degrees-minutes-seconds | ||
console.assert(mid.toString('dms') == '54° 21′ 44″N, 004° 31′ 51″W'); | ||
const mid = p1.midpointTo(p2); | ||
console.assert(mid.toString('dms') == '54° 21′ 44″ N, 004° 31′ 50″ W'); | ||
``` | ||
e.g. datum conversions: | ||
import LatLon from 'geodesy/latlon-ellipsoidal-datum.js'; | ||
```javascript | ||
import LatLon from 'geodesy/latlon-ellipsoidal-datum.js'; | ||
const pWgs84 = new LatLon('53.3498° N, 6.2603° W'); | ||
const pIrl1975 = pWgs84.convertDatum(LatLon.datums.Irl1975); | ||
const pWgs84 = new LatLon(53.3444, -6.2577); | ||
console.assert(pIrl1975.toString() == '53.3497°N, 006.2589°W'); | ||
const pIrl1975 = pWgs84.convertDatum(LatLon.datums.Irl1975); | ||
console.assert(pIrl1975.toString() == '53.3442° N, 006.2567° W'); | ||
``` | ||
(The format of the import statements will vary according to deployment). |
@@ -28,4 +28,4 @@ /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ | ||
describe('valid datum', function() { | ||
test('constructor', () => should.Throw(function() { new LatLon(0, 0, 0, null); }, TypeError, 'Unrecognised datum ‘null’')); | ||
test('parse', () => should.Throw(function() { LatLon.parse('0, 0', 0, null); }, TypeError, 'Unrecognised datum ‘null’')); | ||
test('constructor', () => should.Throw(function() { new LatLon(0, 0, 0, null); }, TypeError, 'unrecognised datum ‘null’')); | ||
test('parse', () => should.Throw(function() { LatLon.parse('0, 0', 0, null); }, TypeError, 'unrecognised datum ‘null’')); | ||
}); | ||
@@ -43,3 +43,3 @@ | ||
test('convert round-trip', () => greenwichOSGB36.convertDatum(LatLon.datums.WGS84).toString('d', 5).should.equal('51.47788°N, 000.00147°W')); | ||
test('convert fails', () => should.Throw(function() { new LatLon(51, 0).convertDatum(null); }, TypeError, 'Unrecognised datum')); | ||
test('convert fails', () => should.Throw(function() { new LatLon(51, 0).convertDatum(null); }, TypeError, 'unrecognised datum ‘null’')); | ||
}); | ||
@@ -64,3 +64,3 @@ | ||
test('LL neq (datum)', () => p1.equals(new LatLon(51.47788, -0.00147, 1, LatLon.datums.Irl1975)).should.be.false); | ||
test('equals (fail)', () => should.Throw(function() { p1.equals(null); }, TypeError, '‘point’ is not LatLon object')); | ||
test('equals (fail)', () => should.Throw(function() { p1.equals(null); }, TypeError, 'invalid point ‘null’')); | ||
}); | ||
@@ -73,5 +73,5 @@ | ||
test('toLatLon', () => c.toLatLon().toString().should.equal('45.0000°N, 045.0000°E')); | ||
test('toLatLon fail', () => should.Throw(function() { c.toLatLon(null); }, TypeError, 'Unrecognised datum')); | ||
test('toLatLon fail', () => should.Throw(function() { c.toLatLon('xx'); }, TypeError, 'Unrecognised datum ‘xx’')); | ||
test('toLatLon fail', () => should.Throw(function() { c.toLatLon(null); }, TypeError, 'unrecognised datum ‘null’')); | ||
test('toLatLon fail', () => should.Throw(function() { c.toLatLon('xx'); }, TypeError, 'unrecognised datum ‘xx’')); | ||
}); | ||
}); |
@@ -20,4 +20,4 @@ /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ | ||
test('TRF', () => new LatLon(0, 0, 0, LatLon.referenceFrames.ITRF2014, 2000.0).toString().should.equal('00.0000°N, 000.0000°E')); | ||
test('bad TRF fail', () => should.Throw(function() { new LatLon(0, 0, 0, null); }, TypeError, 'Unrecognised reference frame')); | ||
test('bad epoch fail', () => should.Throw(function() { new LatLon(0, 0, 0, LatLon.referenceFrames.ITRF2014, 'xxx'); }, TypeError, 'Invalid epoch ’xxx’')); | ||
test('bad TRF fail', () => should.Throw(function() { new LatLon(0, 0, 0, null); }, TypeError, 'unrecognised reference frame')); | ||
test('bad epoch fail', () => should.Throw(function() { new LatLon(0, 0, 0, LatLon.referenceFrames.ITRF2014, 'xxx'); }, TypeError, 'invalid epoch ’xxx’')); | ||
}); | ||
@@ -55,26 +55,26 @@ | ||
describe('parse fail', function() { | ||
test('empty', () => should.Throw(function() { LatLon.parse(); }, TypeError, 'Invalid (empty) coordinate')); | ||
test('l,l bad TRF', () => should.Throw(function() { LatLon.parse(0, 0, 0, 0); }, TypeError, 'Unrecognised reference frame')); | ||
test('l,l bad TRF', () => should.Throw(function() { LatLon.parse(0, 0, 0, null); }, TypeError, 'Unrecognised reference frame')); | ||
test('l/l bad TRF', () => should.Throw(function() { LatLon.parse('0, 0', 0, 0); }, TypeError, 'Unrecognised reference frame')); | ||
test('l/l bad TRF', () => should.Throw(function() { LatLon.parse('0, 0', 0, null); }, TypeError, 'Unrecognised reference frame')); | ||
test('empty', () => should.Throw(function() { LatLon.parse(); }, TypeError, 'invalid (empty) point')); | ||
test('l,l bad TRF', () => should.Throw(function() { LatLon.parse(0, 0, 0, 0); }, TypeError, 'unrecognised reference frame')); | ||
test('l,l bad TRF', () => should.Throw(function() { LatLon.parse(0, 0, 0, null); }, TypeError, 'unrecognised reference frame')); | ||
test('l/l bad TRF', () => should.Throw(function() { LatLon.parse('0, 0', 0, 0); }, TypeError, 'unrecognised reference frame')); | ||
test('l/l bad TRF', () => should.Throw(function() { LatLon.parse('0, 0', 0, null); }, TypeError, 'unrecognised reference frame')); | ||
}); | ||
describe('convertReferenceFrame fail', function() { | ||
test('no TRF', () => should.Throw(function() { new LatLon(0, 0).convertReferenceFrame('ITRF2014'); }, TypeError, 'Unrecognised reference frame')); | ||
test('no TRF', () => should.Throw(function() { new Cartesian(1, 2, 3).convertReferenceFrame('ITRF2014'); }, TypeError, 'Unrecognised reference frame')); | ||
test('no TRF', () => should.Throw(function() { new LatLon(0, 0).convertReferenceFrame('ITRF2014'); }, TypeError, 'unrecognised reference frame')); | ||
test('no TRF', () => should.Throw(function() { new Cartesian(1, 2, 3).convertReferenceFrame('ITRF2014'); }, TypeError, 'unrecognised reference frame')); | ||
}); | ||
describe('Cartesian constructor fail', function() { | ||
test('empty', () => should.Throw(function() { new Cartesian(4027893.924, 307041.993, 4919474.294, 'ITRF2000'); }, Error, 'Unrecognised reference frame')); | ||
test('empty', () => should.Throw(function() { new Cartesian(4027893.924, 307041.993, 4919474.294, LatLon.referenceFrames.ITRF2000, 'last year'); }, Error, 'Invalid epoch ’last year’')); | ||
test('empty', () => should.Throw(function() { new Cartesian(4027893.924, 307041.993, 4919474.294, 'ITRF2000'); }, Error, 'unrecognised reference frame')); | ||
test('empty', () => should.Throw(function() { new Cartesian(4027893.924, 307041.993, 4919474.294, LatLon.referenceFrames.ITRF2000, 'last year'); }, Error, 'invalid epoch ’last year’')); | ||
}); | ||
describe('Cartesian setter fail', function() { | ||
test('bad TRF', () => should.Throw(function() { new Cartesian(1, 2, 3).referenceFrame = 'ITRF2014'; }, TypeError, 'Unrecognised reference frame')); | ||
test('bad epoch', () => should.Throw(function() { new Cartesian(1, 2, 3).epoch = 'last year'; }, TypeError, 'Invalid epoch ’last year’')); | ||
test('bad TRF', () => should.Throw(function() { new Cartesian(1, 2, 3).referenceFrame = 'ITRF2014'; }, TypeError, 'unrecognised reference frame')); | ||
test('bad epoch', () => should.Throw(function() { new Cartesian(1, 2, 3).epoch = 'last year'; }, TypeError, 'invalid epoch ’last year’')); | ||
}); | ||
describe('Cartesian.toLatLon fail', function() { | ||
test('empty', () => should.Throw(function() { new Cartesian(4027893.924, 307041.993, 4919474.294).toLatLon(null); }, Error, 'Cartesian reference frame not defined')); | ||
test('empty', () => should.Throw(function() { new Cartesian(4027893.924, 307041.993, 4919474.294).toLatLon(null); }, Error, 'cartesian reference frame not defined')); | ||
}); | ||
@@ -81,0 +81,0 @@ |
@@ -20,5 +20,5 @@ /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ | ||
test('@example', () => new LatLon(51.47788, -0.00147, 17).toString('d', 4, 2).should.equal('51.4779°N, 000.0015°W +17.00m')); | ||
test('non-numeric lat fail', () => should.Throw(function() { new LatLon('x', 0, 0); }, TypeError, 'Invalid lat ‘x’')); | ||
test('non-numeric lon fail', () => should.Throw(function() { new LatLon(0, 'x', 0); }, TypeError, 'Invalid lon ‘x’')); | ||
test('non-numeric height fail', () => should.Throw(function() { new LatLon(0, 0, 'x'); }, TypeError, 'Invalid height ‘x’')); | ||
test('non-numeric lat fail', () => should.Throw(function() { new LatLon('x', 0, 0); }, TypeError, 'invalid lat ‘x’')); | ||
test('non-numeric lon fail', () => should.Throw(function() { new LatLon(0, 'x', 0); }, TypeError, 'invalid lon ‘x’')); | ||
test('non-numeric height fail', () => should.Throw(function() { new LatLon(0, 0, 'x'); }, TypeError, 'invalid height ‘x’')); | ||
}); | ||
@@ -57,12 +57,12 @@ | ||
describe('parse fail', function() { | ||
test('empty', () => should.Throw(function() { LatLon.parse(); }, TypeError, 'Invalid (empty) coordinate')); | ||
test('single arg num', () => should.Throw(function() { LatLon.parse(1); }, TypeError, 'Invalid coordinate ‘1’')); | ||
test('single arg str', () => should.Throw(function() { LatLon.parse('London'); }, TypeError, 'Invalid coordinate ‘London’')); | ||
test('single arg str + h', () => should.Throw(function() { LatLon.parse('London', 99); }, TypeError, 'Invalid coordinate ‘London,99’')); | ||
test('invalid comma arg', () => should.Throw(function() { LatLon.parse('London,UK'); }, TypeError, 'Invalid coordinate ‘London,UK’')); | ||
test('invalid comma arg + h', () => should.Throw(function() { LatLon.parse('London,UK', 99); }, TypeError, 'Invalid coordinate ‘London,UK’')); | ||
test('empty object', () => should.Throw(function() { LatLon.parse({}); }, TypeError, 'Invalid coordinate ‘{}’')); | ||
test('invalid object 1', () => should.Throw(function() { LatLon.parse({ y: 51.47788, x: -0.00147 }); }, TypeError, 'Invalid coordinate ‘{"y":51.47788,"x":-0.00147}’')); | ||
test('invalid object 2', () => should.Throw(function() { LatLon.parse({ lat: 'y', lon: 'x' }); }, TypeError, 'Invalid coordinate ‘{"lat":"y","lon":"x"}’')); | ||
test('invalid lat,lon', () => should.Throw(function() { LatLon.parse(null, null); }, TypeError, 'Invalid coordinate ‘,’')); | ||
test('empty', () => should.Throw(function() { LatLon.parse(); }, TypeError, 'invalid (empty) point')); | ||
test('single arg num', () => should.Throw(function() { LatLon.parse(1); }, TypeError, 'invalid point ‘1’')); | ||
test('single arg str', () => should.Throw(function() { LatLon.parse('London'); }, TypeError, 'invalid point ‘London’')); | ||
test('single arg str + h', () => should.Throw(function() { LatLon.parse('London', 99); }, TypeError, 'invalid point ‘London,99’')); | ||
test('invalid comma arg', () => should.Throw(function() { LatLon.parse('London,UK'); }, TypeError, 'invalid point ‘London,UK’')); | ||
test('invalid comma arg + h', () => should.Throw(function() { LatLon.parse('London,UK', 99); }, TypeError, 'invalid point ‘London,UK’')); | ||
test('empty object', () => should.Throw(function() { LatLon.parse({}); }, TypeError, 'invalid point ‘{}’')); | ||
test('invalid object 1', () => should.Throw(function() { LatLon.parse({ y: 51.47788, x: -0.00147 }); }, TypeError, 'invalid point ‘{"y":51.47788,"x":-0.00147}’')); | ||
test('invalid object 2', () => should.Throw(function() { LatLon.parse({ lat: 'y', lon: 'x' }); }, TypeError, 'invalid point ‘{"lat":"y","lon":"x"}’')); | ||
test('invalid lat,lon', () => should.Throw(function() { LatLon.parse(null, null); }, TypeError, 'invalid point ‘,’')); | ||
}); | ||
@@ -110,8 +110,8 @@ | ||
test('get ellipsoids', () => LatLon.ellipsoids.should.deep.equal({ WGS84: { a: 6378137, b: 6356752.314245, f: 1/298.257223563 } })); | ||
test('set lat fail', () => should.Throw(function() { p.lat = 'x'; }, TypeError, 'Invalid lat ‘x’')); | ||
test('set latitude fail', () => should.Throw(function() { p.latitude = 'x'; }, TypeError, 'Invalid latitude ‘x’')); | ||
test('set lon fail', () => should.Throw(function() { p.lon = 'x'; }, TypeError, 'Invalid lon ‘x’')); | ||
test('set lng fail', () => should.Throw(function() { p.lng = 'x'; }, TypeError, 'Invalid lng ‘x’')); | ||
test('set longitude fail', () => should.Throw(function() { p.longitude = 'x'; }, TypeError, 'Invalid longitude ‘x’')); | ||
test('set height fail', () => should.Throw(function() { p.height = 'x'; }, TypeError, 'Invalid height ‘x’')); | ||
test('set lat fail', () => should.Throw(function() { p.lat = 'x'; }, TypeError, 'invalid lat ‘x’')); | ||
test('set latitude fail', () => should.Throw(function() { p.latitude = 'x'; }, TypeError, 'invalid latitude ‘x’')); | ||
test('set lon fail', () => should.Throw(function() { p.lon = 'x'; }, TypeError, 'invalid lon ‘x’')); | ||
test('set lng fail', () => should.Throw(function() { p.lng = 'x'; }, TypeError, 'invalid lng ‘x’')); | ||
test('set longitude fail', () => should.Throw(function() { p.longitude = 'x'; }, TypeError, 'invalid longitude ‘x’')); | ||
test('set height fail', () => should.Throw(function() { p.height = 'x'; }, TypeError, 'invalid height ‘x’')); | ||
}); | ||
@@ -127,3 +127,3 @@ | ||
test('LL neq h', () => (p1.equals(new LatLon(51.47788, -0.00147, 99))).should.equal(false)); | ||
test('equals fail', () => should.Throw(function() { p1.equals(null); }, TypeError, '‘point’ is not LatLon object')); | ||
test('equals fail', () => should.Throw(function() { p1.equals(null); }, TypeError, 'invalid point ‘null’')); | ||
}); | ||
@@ -136,4 +136,4 @@ | ||
test('toLatLon', () => c.toLatLon().toString().should.equal('45.0000°N, 045.0000°E')); | ||
test('toLatLon fail', () => should.Throw(function() { c.toLatLon(null); }, TypeError, 'Invalid ellipsoid')); | ||
test('toLatLon fail', () => should.Throw(function() { c.toLatLon(null); }, TypeError, 'invalid ellipsoid')); | ||
}); | ||
}); |
@@ -27,5 +27,5 @@ /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ | ||
test('direct final bearing', () => le.finalBearingOn(dist, brngInit).should.equal(brngFinal)); | ||
test('inverse distance (fail)', () => should.Throw(function() { le.distanceTo(null); }, TypeError, '‘point’ is not (Ellipsoidal) LatLon object')); | ||
test('inverse init brng (fail)', () => should.Throw(function() { le.initialBearingTo(null); }, TypeError, '‘point’ is not (Ellipsoidal) LatLon object')); | ||
test('inverse final brng (fail)', () => should.Throw(function() { le.finalBearingTo(null); }, TypeError, '‘point’ is not (Ellipsoidal) LatLon object')); | ||
test('inverse distance (fail)', () => should.Throw(function() { le.distanceTo(null); }, TypeError, 'invalid point ‘null’')); | ||
test('inverse init brng (fail)', () => should.Throw(function() { le.initialBearingTo(null); }, TypeError, 'invalid point ‘null’')); | ||
test('inverse final brng (fail)', () => should.Throw(function() { le.finalBearingTo(null); }, TypeError, 'invalid point ‘null’')); | ||
}); | ||
@@ -86,11 +86,12 @@ | ||
const le = new LatLon(50.06632, -5.71475, 1), jog = new LatLon(58.64402, -3.07009); | ||
test('distanceTo (fail)', () => should.Throw(function() { le.distanceTo(jog); }, RangeError, 'Point must be on the surface of the ellipsoid')); | ||
test('initialBearingTo (fail)', () => should.Throw(function() { le.initialBearingTo(jog); }, RangeError, 'Point must be on the surface of the ellipsoid')); | ||
test('finalBearingTo (fail)', () => should.Throw(function() { le.finalBearingTo(jog); }, RangeError, 'Point must be on the surface of the ellipsoid')); | ||
test('destinationPoint (fail)', () => should.Throw(function() { le.destinationPoint(0, 0); }, RangeError, 'Point must be on the surface of the ellipsoid')); | ||
test('finalBearingOn (fail)', () => should.Throw(function() { le.finalBearingOn(0, 0); }, RangeError, 'Point must be on the surface of the ellipsoid')); | ||
test('distanceTo (fail)', () => should.Throw(function() { le.distanceTo(jog); }, RangeError, 'point must be on the surface of the ellipsoid')); | ||
test('initialBearingTo (fail)', () => should.Throw(function() { le.initialBearingTo(jog); }, RangeError, 'point must be on the surface of the ellipsoid')); | ||
test('finalBearingTo (fail)', () => should.Throw(function() { le.finalBearingTo(jog); }, RangeError, 'point must be on the surface of the ellipsoid')); | ||
test('destinationPoint (fail)', () => should.Throw(function() { le.destinationPoint(0, 0); }, RangeError, 'point must be on the surface of the ellipsoid')); | ||
test('finalBearingOn (fail)', () => should.Throw(function() { le.finalBearingOn(0, 0); }, RangeError, 'point must be on the surface of the ellipsoid')); | ||
}); | ||
describe('convergence', function() { | ||
test('vincenty antipodal convergence failure', () => new LatLon(0, 0).distanceTo(new LatLon(0.5, 179.7)).should.be.NaN); | ||
test('vincenty antipodal λ > π', () => new LatLon(0.0, 0.0).distanceTo(new LatLon(0.5, 179.7)).should.be.NaN); | ||
test('vincenty antipodal convergence', () => new LatLon(5.0, 0.0).distanceTo(new LatLon(-5.1, 179.4)).should.be.NaN); | ||
}); | ||
@@ -109,5 +110,8 @@ | ||
const dist = 969982.014; // 27.848m more than on WGS-84 ellipsoid; Airy1830 has a smaller flattening, hence larger distance at higher latitudes | ||
const brngInit = 9.1428517; | ||
test('inverse distance', () => le.distanceTo(jog).should.equal(dist)); | ||
test('inverse bearing', () => le.initialBearingTo(jog).should.equal(brngInit)); | ||
test('direct destination', () => le.destinationPoint(dist, brngInit).toString('d', 6).should.equal('58.644399°N, 003.068521°W')); | ||
}); | ||
}); |
@@ -137,3 +137,3 @@ /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ | ||
test('fail', () => should.Throw(function() { new LatLon(0, 0).deltaTo(null); }, TypeError, '‘point’ is not (Ellipsoidal) LatLon object')); | ||
test('fail', () => should.Throw(function() { new LatLon(0, 0).deltaTo(null); }, TypeError, 'invalid point ‘null’')); | ||
}); | ||
@@ -140,0 +140,0 @@ |
@@ -13,36 +13,43 @@ /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ | ||
describe('latlon-nvector-spherical', function() { | ||
const test = it; // just an alias | ||
Dms.separator = ''; // tests are easier without any DMS separator | ||
const R = 6371e3; | ||
const π = Math.PI; | ||
const ε = Number.EPSILON; | ||
describe('@examples', function() { | ||
test('constructor', () => new LatLon(52.205, 0.119).toString().should.equal('52.2050°N, 000.1190°E')); | ||
test('toNvector', () => new LatLon(45, 45).toNvector().toString().should.equal('[0.500,0.500,0.707]')); | ||
test('greatCircle', () => new LatLon(53.3206, -1.7297).greatCircle(96.0).toString().should.equal('[-0.794,0.129,0.594]')); | ||
test('distanceTo d', () => new LatLon(52.205, 0.119).distanceTo(new LatLon(48.857, 2.351)).toFixed().should.equal('404279')); | ||
test('distanceTo m', () => new LatLon(52.205, 0.119).distanceTo(new LatLon(48.857, 2.351), 3959).toFixed(1).should.equal('251.2')); | ||
test('initialBearingTo', () => new LatLon(52.205, 0.119).initialBearingTo(new LatLon(48.857, 2.351)).toFixed(1).should.equal('156.2')); | ||
test('finalBearingTo', () => new LatLon(52.205, 0.119).finalBearingTo(new LatLon(48.857, 2.351)).toFixed(1).should.equal('157.9')); | ||
test('midpointTo', () => new LatLon(52.205, 0.119).midpointTo(new LatLon(48.857, 2.351)).toString().should.equal('50.5363°N, 001.2746°E')); | ||
test('intermediatePointTo', () => new LatLon(52.205, 0.119).intermediatePointTo(new LatLon(48.857, 2.351), 0.25).toString().should.equal('51.3721°N, 000.7073°E')); | ||
test('intermediatePointOnChordTo', () => new LatLon(52.205, 0.119).intermediatePointOnChordTo(new LatLon(48.857, 2.351), 0.25).toString().should.equal('51.3723°N, 000.7072°E')); | ||
test('destinationPoint', () => new LatLon(51.47788, -0.00147).destinationPoint(7794, 300.7).toString().should.equal('51.5136°N, 000.0983°W')); | ||
test('intersection', () => LatLon.intersection(new LatLon(51.8853, 0.2545), 108.547, new LatLon(49.0034, 2.5735), 32.435).toString().should.equal('50.9078°N, 004.5084°E')); | ||
test('crossTrackDistanceTo', () => new LatLon(53.2611, -0.7972).crossTrackDistanceTo(new LatLon(53.3206, -1.7297), new LatLon(53.1887, 0.1334)).toFixed(1).should.equal('-307.5')); | ||
test('nearestPointOnSegment 1', () => new LatLon(51.0, 1.9).nearestPointOnSegment(new LatLon(51.0, 1.0), new LatLon(51.0, 2.0)).toString().should.equal('51.0004°N, 001.9000°E')); | ||
test('nearestPointOnSegment 2', () => new LatLon(51.0, 2.1).nearestPointOnSegment(new LatLon(51.0, 1.0), new LatLon(51.0, 2.0)).toString().should.equal('51.0000°N, 002.0000°E')); | ||
test('isWithinExtent 1', () => new LatLon(52, 1).isWithinExtent(new LatLon(51, 1), new LatLon(52, 2)).should.be.true); | ||
test('isWithinExtent 2', () => new LatLon(51, 0).isWithinExtent(new LatLon(51, 1), new LatLon(52, 2)).should.be.false); | ||
test('triangulate', () => LatLon.triangulate(new LatLon(50.7175, 1.65139), 333.3508, new LatLon(50.9250, 1.7094), 310.1414).toString().should.equal('51.1297°N, 001.3214°E')); | ||
test('trilaterate', () => LatLon.trilaterate(new LatLon(0, 0), 157e3, new LatLon(0, 1), 111e3, new LatLon(1, 0), 111e3).toString().should.equal('00.9985°N, 000.9986°E')); | ||
test('constructor', () => new LatLon(52.205, 0.119).toString().should.equal('52.2050°N, 000.1190°E')); | ||
test('toNvector', () => new LatLon(45, 45).toNvector().toString().should.equal('[0.500,0.500,0.707]')); | ||
test('greatCircle', () => new LatLon(53.3206, -1.7297).greatCircle(96.0).toString().should.equal('[-0.794,0.129,0.594]')); | ||
test('distanceTo d', () => new LatLon(52.205, 0.119).distanceTo(new LatLon(48.857, 2.351)).toFixed().should.equal('404279')); | ||
test('distanceTo m', () => new LatLon(52.205, 0.119).distanceTo(new LatLon(48.857, 2.351), 3959).toFixed(1).should.equal('251.2')); | ||
test('initialBearingTo', () => new LatLon(52.205, 0.119).initialBearingTo(new LatLon(48.857, 2.351)).toFixed(1).should.equal('156.2')); | ||
test('finalBearingTo', () => new LatLon(52.205, 0.119).finalBearingTo(new LatLon(48.857, 2.351)).toFixed(1).should.equal('157.9')); | ||
test('midpointTo', () => new LatLon(52.205, 0.119).midpointTo(new LatLon(48.857, 2.351)).toString().should.equal('50.5363°N, 001.2746°E')); | ||
test('intermediatePointTo', () => new LatLon(52.205, 0.119).intermediatePointTo(new LatLon(48.857, 2.351), 0.25).toString().should.equal('51.3721°N, 000.7073°E')); | ||
test('intermediatePointOnChordTo', () => new LatLon(52.205, 0.119).intermediatePointOnChordTo(new LatLon(48.857, 2.351), 0.25).toString().should.equal('51.3723°N, 000.7072°E')); | ||
test('destinationPoint', () => new LatLon(51.47788, -0.00147).destinationPoint(7794, 300.7).toString().should.equal('51.5136°N, 000.0983°W')); | ||
test('intersection', () => LatLon.intersection(new LatLon(51.8853, 0.2545), 108.547, new LatLon(49.0034, 2.5735), 32.435).toString().should.equal('50.9078°N, 004.5084°E')); | ||
test('crossTrackDistanceTo', () => new LatLon(53.2611, -0.7972).crossTrackDistanceTo(new LatLon(53.3206, -1.7297), new LatLon(53.1887, 0.1334)).toFixed(1).should.equal('-307.5')); | ||
test('alongTrackDistanceTo', () => new LatLon(53.2611, -0.7972).alongTrackDistanceTo(new LatLon(53.3206, -1.7297), new LatLon(53.1887, 0.1334)).toFixed().should.equal('62331')); | ||
test('nearestPointOnSegment 1', () => new LatLon(51.0, 1.9).nearestPointOnSegment(new LatLon(51.0, 1.0), new LatLon(51.0, 2.0)).toString().should.equal('51.0004°N, 001.9000°E')); | ||
test('nearestPointOnSegment 2', () => new LatLon(51.0, 2.1).nearestPointOnSegment(new LatLon(51.0, 1.0), new LatLon(51.0, 2.0)).toString().should.equal('51.0000°N, 002.0000°E')); | ||
test('nearestPointOnSegment antip', () => new LatLon(10, -140).nearestPointOnSegment(new LatLon(0, 20), new LatLon(0, 40)).toString().should.equal('00.0000°N, 020.0000°E')); | ||
test('isWithinExtent 1', () => new LatLon(52, 1).isWithinExtent(new LatLon(51, 1), new LatLon(52, 2)).should.be.true); | ||
test('isWithinExtent 2', () => new LatLon(51, 0).isWithinExtent(new LatLon(51, 1), new LatLon(52, 2)).should.be.false); | ||
test('isWithinExtent coincident', () => new LatLon(51, 0).isWithinExtent(new LatLon(51, 1), new LatLon(51, 1)).should.be.false); | ||
test('triangulate', () => LatLon.triangulate(new LatLon(50.7175, 1.65139), 333.3508, new LatLon(50.9250, 1.7094), 310.1414).toString().should.equal('51.1297°N, 001.3214°E')); | ||
test('trilaterate', () => LatLon.trilaterate(new LatLon(0, 0), 157e3, new LatLon(0, 1), 111e3, new LatLon(1, 0), 111e3).toString().should.equal('00.9985°N, 000.9986°E')); | ||
const bounds = [ new LatLon(45, 1), new LatLon(45, 2), new LatLon(46, 2), new LatLon(46, 1) ]; | ||
test('isEnclosedBy', () => new LatLon(45.1, 1.1).isEnclosedBy(bounds).should.be.true); | ||
const polygon = [ new LatLon(0, 0), new LatLon(1, 0), new LatLon(0, 1) ]; | ||
test('areaOf', () => LatLon.areaOf(polygon).toExponential(2).should.equal('6.18e+9')); | ||
test('meanOf', () => LatLon.meanOf([ new LatLon(1, 1), new LatLon(4, 2), new LatLon(1, 3) ]).toString().should.equal('02.0001°N, 002.0000°E')); | ||
test('equals', () => new LatLon(52.205, 0.119).equals(new LatLon(52.205, 0.119)).should.be.true); | ||
test('isEnclosedBy', () => new LatLon(45.1, 1.1).isEnclosedBy(bounds).should.be.true); | ||
test('areaOf cw', () => LatLon.areaOf([ new LatLon(0, 0), new LatLon(1, 0), new LatLon(0, 1) ]).toExponential(2).should.equal('6.18e+9')); | ||
test('areaOf ccw', () => LatLon.areaOf([ new LatLon(0, 0), new LatLon(0, 1), new LatLon(1, 0) ]).toExponential(2).should.equal('6.18e+9')); | ||
test('meanOf', () => LatLon.meanOf([ new LatLon(1, 1), new LatLon(4, 2), new LatLon(1, 3) ]).toString().should.equal('02.0001°N, 002.0000°E')); | ||
test('equals', () => new LatLon(52.205, 0.119).equals(new LatLon(52.205, 0.119)).should.be.true); | ||
const greenwich = new LatLon(51.47788, -0.00147); | ||
test('toString d', () => greenwich.toString().should.equal('51.4779°N, 000.0015°W')); | ||
test('toString dms', () => greenwich.toString('dms').should.equal('51°28′40″N, 000°00′05″W')); | ||
test('toString lat,lon', () => greenwich.toString('n').split(',').should.deep.equal([ '51.4779', '-0.0015' ])); | ||
test('toString d', () => greenwich.toString().should.equal('51.4779°N, 000.0015°W')); | ||
test('toString dms', () => greenwich.toString('dms', 2).should.equal('51°28′40.37″N, 000°00′05.29″W')); | ||
test('toString lat,lon', () => greenwich.toString('n').split(',').should.deep.equal([ '51.4779', '-0.0015' ])); | ||
}); | ||
@@ -56,9 +63,5 @@ | ||
describe('constructor fail', function() { | ||
test('non-numeric lat fail', () => should.Throw(function() { new LatLon('x', 0, 0); }, TypeError, 'Invalid lat ‘x’')); | ||
test('non-numeric lon fail', () => should.Throw(function() { new LatLon(0, 'x', 0); }, TypeError, 'Invalid lon ‘x’')); | ||
test('non-numeric lat fail', () => should.Throw(function() { new LatLon('x', 0, 0); }, TypeError, 'invalid lat ‘x’')); | ||
test('non-numeric lon fail', () => should.Throw(function() { new LatLon(0, 'x', 0); }, TypeError, 'invalid lon ‘x’')); | ||
}); | ||
describe('constructor fail', function() { | ||
test('non-numeric lat fail', () => should.Throw(function() { new LatLon('x', 0, 0); }, TypeError, 'Invalid lat ‘x’')); | ||
test('non-numeric lon fail', () => should.Throw(function() { new LatLon(0, 'x', 0); }, TypeError, 'Invalid lon ‘x’')); | ||
}); | ||
@@ -88,7 +91,7 @@ describe('getters/setters', function() { | ||
const camb = new LatLon(0, 0); | ||
test('lat', () => should.Throw(function() { camb.lat = 'xxx'; }, TypeError, 'Invalid lat ‘xxx’')); | ||
test('latitude', () => should.Throw(function() { camb.latitude = 'xxx'; }, TypeError, 'Invalid latitude ‘xxx’')); | ||
test('lon', () => should.Throw(function() { camb.lon = 'xxx'; }, TypeError, 'Invalid lon ‘xxx’')); | ||
test('lgn', () => should.Throw(function() { camb.lng = 'xxx'; }, TypeError, 'Invalid lng ‘xxx’')); | ||
test('longitude', () => should.Throw(function() { camb.longitude = 'xxx'; }, TypeError, 'Invalid longitude ‘xxx’')); | ||
test('lat', () => should.Throw(function() { camb.lat = 'xxx'; }, TypeError, 'invalid lat ‘xxx’')); | ||
test('latitude', () => should.Throw(function() { camb.latitude = 'xxx'; }, TypeError, 'invalid latitude ‘xxx’')); | ||
test('lon', () => should.Throw(function() { camb.lon = 'xxx'; }, TypeError, 'invalid lon ‘xxx’')); | ||
test('lgn', () => should.Throw(function() { camb.lng = 'xxx'; }, TypeError, 'invalid lng ‘xxx’')); | ||
test('longitude', () => should.Throw(function() { camb.longitude = 'xxx'; }, TypeError, 'invalid longitude ‘xxx’')); | ||
}); | ||
@@ -104,3 +107,3 @@ | ||
test('n,6', () => btTower.toString('n', 6).should.equal('51.521470,-0.138833')); | ||
test('bad fmt', () => should.Throw(function() { btTower.toString('x', 6); }, RangeError, 'Invalid format ‘x’')); | ||
test('bad fmt', () => should.Throw(function() { btTower.toString('x', 6); }, RangeError, 'invalid format ‘x’')); | ||
}); | ||
@@ -113,3 +116,3 @@ | ||
describe('geodesics', function() { | ||
describe('dist/brng/dest', function() { | ||
const cambg = new LatLon(52.205, 0.119), paris = new LatLon(48.857, 2.351); | ||
@@ -120,17 +123,25 @@ test('distance', () => cambg.distanceTo(paris).toPrecision(4).should.equal('4.043e+5')); | ||
test('final bearing', () => cambg.finalBearingTo(paris).toFixed(1).should.equal('157.9')); | ||
test('initial brng coinc', () => cambg.initialBearingTo(cambg).should.be.NaN); | ||
test('final brng coinc', () => cambg.finalBearingTo(cambg).should.be.NaN); | ||
test('bearing (reverse)', () => paris.initialBearingTo(cambg).toFixed(1).should.equal('337.9')); | ||
test('midpoint', () => cambg.midpointTo(paris).toString().should.equal('50.5363°N, 001.2746°E')); | ||
test('int.point', () => cambg.intermediatePointTo(paris, 0.25).toString().should.equal('51.3721°N, 000.7073°E')); | ||
test('destination', () => new LatLon(51.47788, -0.00147).destinationPoint(7794, 300.7).toString().should.equal('51.5136°N, 000.0983°W')); | ||
test('int.point coinc', () => cambg.intermediatePointTo(cambg, 0.25).toString().should.equal('52.2050°N, 000.1190°E')); | ||
test('int.pt', () => new LatLon(90, 0).intermediatePointTo(new LatLon(0, 90), 0.75).toString().should.equal('22.5000°N, 090.0000°E')); | ||
test('int.pt chord', () => new LatLon(90, 0).intermediatePointOnChordTo(new LatLon(0, 90), 0.75).toString().should.equal('18.4349°N, 090.0000°E')); | ||
const greenwich = new LatLon(51.47788, -0.00147), dist = 7794, brng = 300.7; | ||
test('destination', () => greenwich.destinationPoint(dist, brng).toString().should.equal('51.5136°N, 000.0983°W')); | ||
test('destination r', () => greenwich.destinationPoint(dist, brng, 6371e3).toString().should.equal('51.5136°N, 000.0983°W')); | ||
}); | ||
describe('geodesics fails', function() { | ||
describe('dist/brng/dest fails', function() { | ||
const cambg = new LatLon(52.205, 0.119), paris = new LatLon(48.857, 2.351); | ||
test('distance (fail p)', () => should.Throw(function() { cambg.distanceTo('paris'); }, TypeError, '‘point’ is not (NvectorSpherical) LatLon object')); | ||
test('distance (fail r)', () => should.Throw(function() { cambg.distanceTo(paris, 'xxx'); }, TypeError, 'Radius is not a number')); | ||
test('init brng (fail)', () => should.Throw(function() { cambg.initialBearingTo('paris'); }, TypeError, '‘point’ is not (NvectorSpherical) LatLon object')); | ||
test('final brng (fail)', () => should.Throw(function() { cambg.finalBearingTo('paris'); }, TypeError, '‘point’ is not (NvectorSpherical) LatLon object')); | ||
test('midpoint (fail)', () => should.Throw(function() { cambg.midpointTo('paris'); }, TypeError, '‘point’ is not (NvectorSpherical) LatLon object')); | ||
test('int.point (fail)', () => should.Throw(function() { cambg.intermediatePointTo('paris', 0.5); }, TypeError, '‘point’ is not (NvectorSpherical) LatLon object')); | ||
test('int.point.ch (fail)', () => should.Throw(function() { cambg.intermediatePointOnChordTo('paris', 0.5); }, TypeError, '‘point’ is not (NvectorSpherical) LatLon object')); | ||
test('distance (fail p)', () => should.Throw(function() { cambg.distanceTo({}); }, TypeError, 'invalid point ‘[object Object]’')); | ||
test('distance (fail p)', () => should.Throw(function() { cambg.distanceTo('paris'); }, TypeError, 'invalid point ‘paris’')); | ||
test('distance (fail r)', () => should.Throw(function() { cambg.distanceTo(paris, 'xxx'); }, TypeError, 'invalid radius ‘xxx’')); | ||
test('init brng (fail)', () => should.Throw(function() { cambg.initialBearingTo('paris'); }, TypeError, 'invalid point ‘paris’')); | ||
test('final brng (fail)', () => should.Throw(function() { cambg.finalBearingTo('paris'); }, TypeError, 'invalid point ‘paris’')); | ||
test('midpoint (fail)', () => should.Throw(function() { cambg.midpointTo('paris'); }, TypeError, 'invalid point ‘paris’')); | ||
test('int.point (fail)', () => should.Throw(function() { cambg.intermediatePointTo('paris', 0.5); }, TypeError, 'invalid point ‘paris’')); | ||
test('int.point.ch (fail)', () => should.Throw(function() { cambg.intermediatePointOnChordTo('paris', 0.5); }, TypeError, 'invalid point ‘paris’')); | ||
}); | ||
@@ -160,19 +171,51 @@ | ||
test('coincident', () => LatLon.intersection(new LatLon(1, 1), N, new LatLon(1, 1), E).toString().should.equal('01.0000°N, 001.0000°E')); | ||
const stn = new LatLon(51.8853, 0.2545), cdg = new LatLon(49.0034, 2.5735); | ||
test('stn-cdg-bxl', () => LatLon.intersection(stn, 108.547, cdg, 32.435).toString().should.equal('50.9078°N, 004.5084°E')); | ||
test('int’n (fail 1)', () => should.Throw(function() { LatLon.intersection(null, 'n', null, 's'); }, TypeError, '‘path1start’ is not LatLon object')); | ||
test('int’n (fail 2)', () => should.Throw(function() { LatLon.intersection(stn, 'n', null, 's'); }, TypeError, '‘path2start’ is not LatLon object')); | ||
test('int’n (fail 3)', () => should.Throw(function() { LatLon.intersection(stn, 'n', cdg, 's'); }, TypeError, '‘path1brngEnd’ is not LatLon object')); | ||
test('int’n (fail 4)', () => should.Throw(function() { LatLon.intersection(stn, 'n', cdg, 's'); }, TypeError, '‘path1brngEnd’ is not LatLon object')); | ||
test('int’n (fail 5)', () => should.Throw(function() { LatLon.intersection(stn, 0, cdg, 's'); }, TypeError, '‘path2brngEnd’ is not LatLon object')); | ||
test('bad point 1', () => should.Throw(function() { LatLon.intersection(false, N, new LatLon(1, 0), E); }, TypeError, 'invalid path1start ‘false’')); | ||
test('bad point 2', () => should.Throw(function() { LatLon.intersection(new LatLon(0, 1), N, false, E); }, TypeError, 'invalid path2start ‘false’')); | ||
test('int’n (fail 1)', () => should.Throw(function() { LatLon.intersection(null, 'n', null, 's'); }, TypeError, 'invalid path1start ‘null’')); | ||
test('int’n (fail 2)', () => should.Throw(function() { LatLon.intersection(stn, 'n', null, 's'); }, TypeError, 'invalid path2start ‘null’')); | ||
test('int’n (fail 3)', () => should.Throw(function() { LatLon.intersection(stn, 'n', cdg, 's'); }, TypeError, 'invalid path1brngEnd ‘n’')); | ||
test('int’n (fail 4)', () => should.Throw(function() { LatLon.intersection(stn, 0, cdg, 's'); }, TypeError, 'invalid path2brngEnd ‘s’')); | ||
test('rounding errors', () => LatLon.intersection(new LatLon(51, 0), 120, new LatLon(50, 0), 60).toString().should.equal('50.4921°N, 001.3612°E')); | ||
}); | ||
describe('cross-track', function() { | ||
test('cross-track b', () => new LatLon(10, 0).crossTrackDistanceTo(new LatLon(0, 0), 90).toPrecision(4).should.equal('-1.112e+6')); | ||
test('cross-track p', () => new LatLon(10, 1).crossTrackDistanceTo(new LatLon(0, 0), new LatLon(0, 2)).toPrecision(4).should.equal('-1.112e+6')); | ||
test('cross-track -', () => new LatLon(10, 0).crossTrackDistanceTo(new LatLon(0, 0), 270).toPrecision(4).should.equal('1.112e+6')); | ||
test('cross-track (fail)', () => should.Throw(function() { new LatLon(0, 0).crossTrackDistanceTo(null, 0); }, TypeError, '‘pathStart’ is not (NvectorSpherical) LatLon object')); | ||
describe('cross-track / along-track', function() { | ||
test('cross-track end-p', () => new LatLon(10, 1).crossTrackDistanceTo(new LatLon(0, 0), new LatLon(0, 2)).toPrecision(4).should.equal('-1.112e+6')); | ||
test('cross-track brng', () => new LatLon(10, 0).crossTrackDistanceTo(new LatLon(0, 0), 90).toPrecision(4).should.equal('-1.112e+6')); | ||
test('cross-track -', () => new LatLon(10, 0).crossTrackDistanceTo(new LatLon(0, 0), 270).toPrecision(4).should.equal('1.112e+6')); | ||
const bradwell = new LatLon(53.3206, -1.7297), dunham = new LatLon(53.2611, -0.7972), partney = new LatLon(53.1887, 0.1334); | ||
test('cross-track', () => dunham.crossTrackDistanceTo(bradwell, partney).toPrecision(4).should.equal('-307.5')); | ||
test('cross-track (fail)', () => should.Throw(function() { new LatLon(10, 1).crossTrackDistanceTo(null, new LatLon(0, 2)); }, TypeError, 'invalid pathStart ‘null’')); | ||
test('cross-track (fail)', () => should.Throw(function() { new LatLon(10, 1).crossTrackDistanceTo(new LatLon(0, 0), 'x'); }, TypeError, 'invalid pathBrngEnd ‘x’')); | ||
test('along-track end-p', () => dunham.alongTrackDistanceTo(bradwell, partney).toPrecision(4).should.equal('6.233e+4')); | ||
test('along-track brng', () => dunham.alongTrackDistanceTo(bradwell, 96.0).toPrecision(4).should.equal('6.233e+4')); | ||
test('cross-track NE', () => new LatLon(1, 1).crossTrackDistanceTo(new LatLon(0, 0), new LatLon(0, 2)).toPrecision(4).should.equal('-1.112e+5')); | ||
test('cross-track SE', () => new LatLon(-1, 1).crossTrackDistanceTo(new LatLon(0, 0), new LatLon(0, 2)).toPrecision(4).should.equal('1.112e+5')); | ||
test('cross-track SW?', () => new LatLon(-1, -1).crossTrackDistanceTo(new LatLon(0, 0), new LatLon(0, 2)).toPrecision(4).should.equal('1.112e+5')); | ||
test('cross-track NW?', () => new LatLon( 1, -1).crossTrackDistanceTo(new LatLon(0, 0), new LatLon(0, 2)).toPrecision(4).should.equal('-1.112e+5')); // eslint-disable-line space-in-parens | ||
test('along-track NE', () => new LatLon( 1, 1).alongTrackDistanceTo(new LatLon(0, 0), new LatLon(0, 2)).toPrecision(4).should.equal('1.112e+5')); // eslint-disable-line space-in-parens | ||
test('along-track SE', () => new LatLon(-1, 1).alongTrackDistanceTo(new LatLon(0, 0), new LatLon(0, 2)).toPrecision(4).should.equal('1.112e+5')); | ||
test('along-track SW', () => new LatLon(-1, -1).alongTrackDistanceTo(new LatLon(0, 0), new LatLon(0, 2)).toPrecision(4).should.equal('-1.112e+5')); | ||
test('along-track NW', () => new LatLon( 1, -1).alongTrackDistanceTo(new LatLon(0, 0), new LatLon(0, 2)).toPrecision(4).should.equal('-1.112e+5')); // eslint-disable-line space-in-parens | ||
test('cross-track brng w-e', () => new LatLon(1, 0).crossTrackDistanceTo(new LatLon(0, 0), 90).toPrecision(4).should.equal('-1.112e+5')); | ||
test('cross-track brng e-w', () => new LatLon(1, 0).crossTrackDistanceTo(new LatLon(0, 0), 270).toPrecision(4).should.equal('1.112e+5')); | ||
test('cross-track coinc', () => new LatLon(10, 0).crossTrackDistanceTo(new LatLon(10, 0), 0).should.be.NaN); | ||
test('cross-track (fail)', () => should.Throw(function() { new LatLon(0, 0).crossTrackDistanceTo(null, 0); }, TypeError, 'invalid pathStart ‘null’')); | ||
test('cross-track (fail)', () => should.Throw(function() { new LatLon(0, 0).crossTrackDistanceTo(new LatLon(0, 0), 'x'); }, TypeError, 'invalid pathBrngEnd ‘x’')); | ||
test('along-track (fail)', () => should.Throw(function() { new LatLon(0, 0).alongTrackDistanceTo(null, 0); }, TypeError, 'invalid pathStart ‘null’')); | ||
test('along-track (fail)', () => should.Throw(function() { new LatLon(0, 0).alongTrackDistanceTo(new LatLon(0, 0), 'x'); }, TypeError, 'invalid pathBrngEnd ‘x’')); | ||
}); | ||
describe('triangulate', function() { | ||
// TODO | ||
}); | ||
describe('trilaterate', function() { // http://gis.stackexchange.com/a/415/41129 | ||
@@ -183,6 +226,13 @@ const p1 = new LatLon(37.418436, -121.963477), d1 = 265.710701754; | ||
test('gis.stackexchange', () => LatLon.trilaterate(p1, d1, p2, d2, p3, d3).toString('d', 6).should.equal('37.419078°N, 121.960579°W')); | ||
test('coincident', () => should.equal(LatLon.trilaterate(p1, d1, p1, d2, p1, d3), null)); | ||
}); | ||
describe('area', function() { | ||
const polyHemi = [ new LatLon(0, 1), new LatLon(45, 0), new LatLon(89, 90), new LatLon(45, 180), new LatLon(0, 179), new LatLon(-45, 180), new LatLon(-89, 90), new LatLon(-45, 0) ]; | ||
describe('area / enclosed (polygon-based)', function() { | ||
const polyTriangle = [ new LatLon(1, 1), new LatLon(2, 1), new LatLon(1, 2) ]; | ||
const polySquareCw = [ new LatLon(1, 1), new LatLon(2, 1), new LatLon(2, 2), new LatLon(1, 2) ]; | ||
const polySquareCcw = [ new LatLon(1, 1), new LatLon(1, 2), new LatLon(2, 2), new LatLon(2, 1) ]; | ||
const polyOctant = [ new LatLon(0, ε), new LatLon(90, 0), new LatLon(0, 90-ε) ]; | ||
const polyOctantS = [ new LatLon(-ε, ε), new LatLon(90, 0), new LatLon(-ε, 90-ε) ]; | ||
// const polyQuadrant = [ new LatLon(ε, ε), new LatLon(90, ε), new LatLon(ε, 180-ε), new LatLon(ε, 90) ]; | ||
const polyHemiE = [ new LatLon(ε, ε), new LatLon(90-ε, 0), new LatLon(90-ε, 180), new LatLon(ε, 180), new LatLon(-ε, 180), new LatLon(-90+ε, 180), new LatLon(-90+ε, 0), new LatLon(-ε, ε) ]; | ||
const polyGc = [ new LatLon(10, 0), new LatLon(10, 90), new LatLon(0, 45) ]; | ||
@@ -192,4 +242,17 @@ const polyPole = [ new LatLon(89, 0), new LatLon(89, 120), new LatLon(89, -120) ]; | ||
const polyConcave = [ new LatLon(1, 1), new LatLon(5, 1), new LatLon(5, 3), new LatLon(1, 3), new LatLon(3, 2) ]; | ||
test('hemisphere enclosed y', () => new LatLon(22.5, 0.59).isEnclosedBy(polyHemi).should.be.true); | ||
test('hemisphere enclosed n', () => new LatLon(22.5, 0.58).isEnclosedBy(polyHemi).should.be.false); | ||
test('triangle area', () => LatLon.areaOf(polyTriangle).toFixed(0).should.equal('6181527888')); | ||
test('triangle area radius', () => LatLon.areaOf(polyTriangle, 6371e3).toFixed(0).should.equal('6181527888')); | ||
test('triangle area closed', () => LatLon.areaOf(polyTriangle.concat(polyTriangle[0])).toFixed(0).should.equal('6181527888')); | ||
test('square cw area', () => LatLon.areaOf(polySquareCw).toFixed(0).should.equal('12360230987')); | ||
test('square ccw area', () => LatLon.areaOf(polySquareCcw).toFixed(0).should.equal('12360230987')); | ||
test('octant area', () => LatLon.areaOf(polyOctant).should.equal(π*R*R/2)); | ||
test('super-octant area', () => LatLon.areaOf(polyOctantS).should.equal(π*R*R/2)); | ||
// TODO: fails: test('quadrant area', () => LatLon.areaOf(polyQuadrant).should.equal(π*R*R)); | ||
test('hemisphere area', () => LatLon.areaOf(polyHemiE).toFixed(1).should.equal((2*π*R*R).toFixed(1))); | ||
test('pole area', () => LatLon.areaOf(polyPole).toFixed(0).should.equal('16063139192')); | ||
test('concave area', () => LatLon.areaOf(polyConcave).toFixed(0).should.equal('74042699236')); | ||
test('hemisphere enclosed y', () => new LatLon(45, 1).isEnclosedBy(polyHemiE).should.be.true); | ||
// TODO: fails: test('hemisphere enclosed n', () => new LatLon(45, -1).isEnclosedBy(polyHemiE).should.be.false); | ||
test('gc enclosed y', () => new LatLon(14, 45).isEnclosedBy(polyGc).should.be.true); | ||
@@ -201,10 +264,25 @@ test('gc enclosed n', () => new LatLon(15, 45).isEnclosedBy(polyGc).should.be.false); | ||
test('concave enclosed n', () => new LatLon(2, 2).isEnclosedBy(polyConcave).should.be.false); | ||
}); | ||
test('int.pt normal', () => new LatLon(90, 0).intermediatePointTo(new LatLon(0, 90), 0.75).toString().should.equal('22.5000°N, 090.0000°E')); | ||
test('int.pt direct', () => new LatLon(90, 0).intermediatePointOnChordTo(new LatLon(0, 90), 0.75).toString().should.equal('18.4349°N, 090.0000°E')); | ||
describe('Ed Williams', function() { // www.edwilliams.org/avform.htm | ||
const lax = new LatLon(Dms.parse('33° 57′N'), Dms.parse('118° 24′W')); | ||
const jfk = new LatLon(Dms.parse('40° 38′N'), Dms.parse('073° 47′W')); | ||
const r = 180*60/π; // earth radius in nautical miles | ||
test('distance nm', () => lax.distanceTo(jfk, r).toPrecision(4).should.equal('2144')); | ||
test('bearing', () => lax.initialBearingTo(jfk).toPrecision(2).should.equal('66')); | ||
test('intermediate', () => lax.intermediatePointTo(jfk, 100/2144).toString('dm', 0).should.equal('34°37′N, 116°33′W')); | ||
const d = new LatLon(Dms.parse('34:30N'), Dms.parse('116:30W')); | ||
test('cross-track', () => d.crossTrackDistanceTo(lax, jfk, r).toPrecision(5).should.equal('7.4523')); | ||
test('along-track', () => d.alongTrackDistanceTo(lax, jfk, r).toPrecision(5).should.equal('99.588')); | ||
test('intermediate', () => lax.intermediatePointTo(jfk, 0.4).toString('dm', 3).should.equal('38°40.167′N, 101°37.570′W')); | ||
const reo = new LatLon(Dms.parse('42.600N'), Dms.parse('117.866W')); | ||
const bke = new LatLon(Dms.parse('44.840N'), Dms.parse('117.806W')); | ||
test('intersection', () => LatLon.intersection(reo, 51, bke, 137).toString('d', 3).should.equal('43.572°N, 116.189°W')); | ||
}); | ||
describe('misc', function() { | ||
test('equals (fail)', () => should.Throw(function() { new LatLon(52.205, 0.119).equals('cambg'); }, TypeError, '‘point’ is not (NvectorSpherical) LatLon object')); | ||
test('equals true', () => new LatLon(52.205, 0.119).equals(new LatLon(52.205, 0.119)).should.be.true); | ||
test('equals false', () => new LatLon(52.206, 0.119).equals(new LatLon(52.205, 0.119)).should.be.false); | ||
test('equals (fail)', () => should.Throw(function() { new LatLon(52.205, 0.119).equals('cambg'); }, TypeError, 'invalid point ‘cambg’')); | ||
test('toGeoJSON', () => new LatLon(52.205, 0.119).toGeoJSON().should.deep.equal({ type: 'Point', coordinates: [ 0.119, 52.205 ] })); | ||
}); | ||
@@ -211,0 +289,0 @@ }); |
@@ -18,2 +18,3 @@ /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ | ||
const π = Math.PI; | ||
const ε = Number.EPSILON; | ||
@@ -23,12 +24,11 @@ Dms.separator = ''; // tests are easier without any DMS separator | ||
describe('@examples', function() { | ||
const cambg = '52.2050°N, 000.1190°E'; | ||
test('constructor', () => new LatLon(52.205, 0.119).toString().should.equal('52.2050°N, 000.1190°E')); | ||
test('parse p1', () => LatLon.parse(52.205, 0.119).toString().should.equal(cambg)); | ||
test('parse p2', () => LatLon.parse('52.205', '0.119').toString().should.equal(cambg)); | ||
test('parse p3', () => LatLon.parse('52.205, 0.119').toString().should.equal(cambg)); | ||
test('parse p4', () => LatLon.parse('52°12′18.0″N', '000°07′08.4″E').toString().should.equal(cambg)); | ||
test('parse p5', () => LatLon.parse('52°12′18.0″N, 000°07′08.4″E').toString().should.equal(cambg)); | ||
test('parse p6', () => LatLon.parse({ lat: 52.205, lon: 0.119 }).toString().should.equal(cambg)); | ||
test('parse p7', () => LatLon.parse({ lat: '52°12′18.0″N', lng: '000°07′08.4″E' }).toString().should.equal(cambg)); | ||
test('parse p8', () => LatLon.parse({ type: 'Point', coordinates: [ 0.119, 52.205 ] }).toString().should.equal(cambg)); | ||
test('parse p1', () => LatLon.parse(52.205, 0.119).toString().should.equal('52.2050°N, 000.1190°E')); | ||
test('parse p2', () => LatLon.parse('52.205', '0.119').toString().should.equal('52.2050°N, 000.1190°E')); | ||
test('parse p3', () => LatLon.parse('52.205, 0.119').toString().should.equal('52.2050°N, 000.1190°E')); | ||
test('parse p4', () => LatLon.parse('52°12′18.0″N', '000°07′08.4″E').toString().should.equal('52.2050°N, 000.1190°E')); | ||
test('parse p5', () => LatLon.parse('52°12′18.0″N, 000°07′08.4″E').toString().should.equal('52.2050°N, 000.1190°E')); | ||
test('parse p6', () => LatLon.parse({ lat: 52.205, lon: 0.119 }).toString().should.equal('52.2050°N, 000.1190°E')); | ||
test('parse p7', () => LatLon.parse({ lat: '52°12′18.0″N', lng: '000°07′08.4″E' }).toString().should.equal('52.2050°N, 000.1190°E')); | ||
test('parse p8', () => LatLon.parse({ type: 'Point', coordinates: [ 0.119, 52.205 ] }).toString().should.equal('52.2050°N, 000.1190°E')); | ||
test('distanceTo d', () => new LatLon(52.205, 0.119).distanceTo(new LatLon(48.857, 2.351)).toFixed().should.equal('404279')); | ||
@@ -50,35 +50,25 @@ test('distanceTo m', () => new LatLon(52.205, 0.119).distanceTo(new LatLon(48.857, 2.351), 3959).toFixed(1).should.equal('251.2')); | ||
test('equals', () => new LatLon(52.205, 0.119).equals(new LatLon(52.205, 0.119)).should.be.true); | ||
test('toString 1', () => new LatLon(51.47788, -0.00147).toString().should.equal('51.4779°N, 000.0015°W')); | ||
test('toString 2', () => new LatLon(51.47788, -0.00147).toString('dms', 2).should.equal('51°28′40.37″N, 000°00′05.29″W')); | ||
test('toString 3', () => new LatLon(51.47788, -0.00147).toString('n').should.equal('51.4779,-0.0015')); | ||
const greenwich = new LatLon(51.47788, -0.00147); | ||
test('toString d', () => greenwich.toString().should.equal('51.4779°N, 000.0015°W')); | ||
test('toString dms', () => greenwich.toString('dms', 2).should.equal('51°28′40.37″N, 000°00′05.29″W')); | ||
test('toString lat,lon', () => greenwich.toString('n').split(',').should.deep.equal([ '51.4779', '-0.0015' ])); | ||
}); | ||
describe('constructor fail', function() { | ||
test('non-numeric lat fail', () => should.Throw(function() { new LatLon('x', 0, 0); }, TypeError, 'Invalid lat ‘x’')); | ||
test('non-numeric lon fail', () => should.Throw(function() { new LatLon(0, 'x', 0); }, TypeError, 'Invalid lon ‘x’')); | ||
test('non-numeric lat fail', () => should.Throw(function() { new LatLon('x', 0, 0); }, TypeError, 'invalid lat ‘x’')); | ||
test('non-numeric lon fail', () => should.Throw(function() { new LatLon(0, 'x', 0); }, TypeError, 'invalid lon ‘x’')); | ||
}); | ||
describe('parse fail', function() { | ||
test('empty', () => should.Throw(function() { LatLon.parse(); }, TypeError, 'Invalid (empty) coordinate')); | ||
test('single arg num', () => should.Throw(function() { LatLon.parse(1); }, TypeError, 'Invalid coordinate ‘1’')); | ||
test('invalid comma arg', () => should.Throw(function() { LatLon.parse('cam', 'bridge'); }, TypeError, 'Invalid coordinate ‘cam,bridge’')); | ||
test('single arg str', () => should.Throw(function() { LatLon.parse('cambridge'); }, TypeError, 'Invalid coordinate ‘cambridge’')); | ||
test('invalid object 1', () => should.Throw(function() { LatLon.parse({ y: 51.47788, x: -0.00147 }); }, TypeError, 'Invalid coordinate ‘{"y":51.47788,"x":-0.00147}’')); | ||
test('invalid object 2', () => should.Throw(function() { LatLon.parse({ lat: 'y', lon: 'x' }); }, TypeError, 'Invalid coordinate ‘{"lat":"y","lon":"x"}’')); | ||
test('null 1', () => should.Throw(function() { LatLon.parse(null); }, TypeError, 'Invalid (null) coordinate')); | ||
test('null 2', () => should.Throw(function() { LatLon.parse(null, null); }, TypeError, 'Invalid (null) coordinate')); | ||
test('null 3', () => should.Throw(function() { LatLon.parse(1.23, null); }, TypeError, 'Invalid (null) coordinate')); | ||
test('empty', () => should.Throw(function() { LatLon.parse(); }, TypeError, 'invalid (empty) point')); | ||
test('single arg num', () => should.Throw(function() { LatLon.parse(1); }, TypeError, 'invalid point ‘1’')); | ||
test('invalid comma arg', () => should.Throw(function() { LatLon.parse('cam', 'bridge'); }, TypeError, 'invalid point ‘cam,bridge’')); | ||
test('single arg str', () => should.Throw(function() { LatLon.parse('cambridge'); }, TypeError, 'invalid point ‘cambridge’')); | ||
test('invalid object 1', () => should.Throw(function() { LatLon.parse({ y: 51.47788, x: -0.00147 }); }, TypeError, 'invalid point ‘{"y":51.47788,"x":-0.00147}’')); | ||
test('invalid object 2', () => should.Throw(function() { LatLon.parse({ lat: 'y', lon: 'x' }); }, TypeError, 'invalid point ‘{"lat":"y","lon":"x"}’')); | ||
test('null 1', () => should.Throw(function() { LatLon.parse(null); }, TypeError, 'invalid (null) point')); | ||
test('null 2', () => should.Throw(function() { LatLon.parse(null, null); }, TypeError, 'invalid (null) point')); | ||
test('null 3', () => should.Throw(function() { LatLon.parse(1.23, null); }, TypeError, 'invalid (null) point')); | ||
}); | ||
describe('toString', function() { | ||
const btTower = new LatLon(51.521470, -0.138833); | ||
test('default', () => btTower.toString().should.equal('51.5215°N, 000.1388°W')); | ||
test('d,6', () => btTower.toString('d', 6).should.equal('51.521470°N, 000.138833°W')); | ||
test('dm,4', () => btTower.toString('dm', 4).should.equal('51°31.2882′N, 000°08.3300′W')); | ||
test('dms,2', () => btTower.toString('dms', 2).should.equal('51°31′17.29″N, 000°08′19.80″W')); | ||
test('n', () => btTower.toString('n').should.equal('51.5215,-0.1388')); | ||
test('n,6', () => btTower.toString('n', 6).should.equal('51.521470,-0.138833')); | ||
test('bad fmt', () => should.Throw(function() { btTower.toString('x', 6); }, RangeError, 'Invalid format ‘x’')); | ||
}); | ||
describe('getters/setters', function() { | ||
@@ -107,10 +97,21 @@ const camb = new LatLon(0, 0); | ||
const camb = new LatLon(0, 0); | ||
test('lat', () => should.Throw(function() { camb.lat = 'xxx'; }, TypeError, 'Invalid lat ‘xxx’')); | ||
test('latitude', () => should.Throw(function() { camb.latitude = 'xxx'; }, TypeError, 'Invalid latitude ‘xxx’')); | ||
test('lon', () => should.Throw(function() { camb.lon = 'xxx'; }, TypeError, 'Invalid lon ‘xxx’')); | ||
test('lgn', () => should.Throw(function() { camb.lng = 'xxx'; }, TypeError, 'Invalid lng ‘xxx’')); | ||
test('longitude', () => should.Throw(function() { camb.longitude = 'xxx'; }, TypeError, 'Invalid longitude ‘xxx’')); | ||
test('lat', () => should.Throw(function() { camb.lat = 'xxx'; }, TypeError, 'invalid lat ‘xxx’')); | ||
test('latitude', () => should.Throw(function() { camb.latitude = 'xxx'; }, TypeError, 'invalid latitude ‘xxx’')); | ||
test('lon', () => should.Throw(function() { camb.lon = 'xxx'; }, TypeError, 'invalid lon ‘xxx’')); | ||
test('lgn', () => should.Throw(function() { camb.lng = 'xxx'; }, TypeError, 'invalid lng ‘xxx’')); | ||
test('longitude', () => should.Throw(function() { camb.longitude = 'xxx'; }, TypeError, 'invalid longitude ‘xxx’')); | ||
}); | ||
describe('alternate formats', function() { | ||
describe('toString', function() { | ||
const btTower = new LatLon(51.521470, -0.138833); | ||
test('default', () => btTower.toString().should.equal('51.5215°N, 000.1388°W')); | ||
test('d,6', () => btTower.toString('d', 6).should.equal('51.521470°N, 000.138833°W')); | ||
test('dm,4', () => btTower.toString('dm', 4).should.equal('51°31.2882′N, 000°08.3300′W')); | ||
test('dms,2', () => btTower.toString('dms', 2).should.equal('51°31′17.29″N, 000°08′19.80″W')); | ||
test('n', () => btTower.toString('n').should.equal('51.5215,-0.1388')); | ||
test('n,6', () => btTower.toString('n', 6).should.equal('51.521470,-0.138833')); | ||
test('bad fmt', () => should.Throw(function() { btTower.toString('x', 6); }, RangeError, 'invalid format ‘x’')); | ||
}); | ||
describe('alternate point formats', function() { | ||
const cambg = LatLon.parse({ lat: 52.205, lon: 0.119 }); | ||
@@ -120,6 +121,11 @@ test('distance {lat,lon}', () => cambg.distanceTo({ lat: 48.857, lon: 2.351 }).toPrecision(4).should.equal('4.043e+5')); | ||
test('distance {latitude,longitude}', () => cambg.distanceTo({ latitude: 48.857, longitude: 2.351 }).toPrecision(4).should.equal('4.043e+5')); | ||
test('distance {x,y} fails', () => should.Throw(function() { cambg.distanceTo(({ x: 48.857, y: 2.351 })); }, TypeError, 'Invalid coordinate ‘{"x":48.857,"y":2.351}’')); | ||
test('distance GeoJSON', () => cambg.distanceTo({ type: 'Point', coordinates: [ 2.351, 48.857 ] }).toPrecision(4).should.equal('4.043e+5')); | ||
test('distance {x,y} fails', () => should.Throw(function() { cambg.distanceTo(({ x: 48.857, y: 2.351 })); }, TypeError, 'invalid point ‘{"x":48.857,"y":2.351}’')); | ||
test('distance "d"', () => cambg.distanceTo('48.857, 2.351').toPrecision(4).should.equal('4.043e+5')); | ||
test('distance "dms"', () => cambg.distanceTo('48°51′25.2″N, 002°21′03.6″E').toPrecision(4).should.equal('4.043e+5')); | ||
test('object (fail)', () => should.Throw(function() { cambg.distanceTo({ x: 48.857, y: 2.351 }); }, TypeError, 'invalid point ‘{"x":48.857,"y":2.351}’')); | ||
test('string (fail)', () => should.Throw(function() { cambg.distanceTo('paris'); }, TypeError, 'invalid point ‘paris’')); | ||
}); | ||
describe('geodesics', function() { | ||
describe('dist/brng/dest', function() { | ||
const cambg = new LatLon(52.205, 0.119), paris = new LatLon(48.857, 2.351); | ||
@@ -130,27 +136,83 @@ test('distance', () => cambg.distanceTo(paris).toPrecision(4).should.equal('4.043e+5')); | ||
test('final bearing', () => cambg.finalBearingTo(paris).toFixed(1).should.equal('157.9')); | ||
test('initial brng coinc', () => cambg.initialBearingTo(cambg).should.be.NaN); | ||
test('final brng coinc', () => cambg.finalBearingTo(cambg).should.be.NaN); | ||
test('bearing (reverse)', () => paris.initialBearingTo(cambg).toFixed(1).should.equal('337.9')); | ||
test('midpoint', () => cambg.midpointTo(paris).toString().should.equal('50.5363°N, 001.2746°E')); | ||
test('int.point', () => cambg.intermediatePointTo(paris, 0.25).toString().should.equal('51.3721°N, 000.7073°E')); | ||
test('distance (fail)', () => should.Throw(function() { cambg.distanceTo('paris'); }, TypeError, 'Invalid coordinate ‘paris’')); | ||
test('distance (fail)', () => should.Throw(function() { cambg.distanceTo(paris, 'xxx'); }, TypeError, 'Radius is not a number')); | ||
test('init brng (fail)', () => should.Throw(function() { cambg.initialBearingTo('paris'); }, TypeError, 'Invalid coordinate ‘paris’')); | ||
test('final brng (fail)', () => should.Throw(function() { cambg.finalBearingTo('paris'); }, TypeError, 'Invalid coordinate ‘paris’')); | ||
test('midpoint (fail)', () => should.Throw(function() { cambg.midpointTo('paris'); }, TypeError, 'Invalid coordinate ‘paris’')); | ||
test('int.point (fail)', () => should.Throw(function() { cambg.intermediatePointTo('paris', 0.5); }, TypeError, 'Invalid coordinate ‘paris’')); | ||
test('int.point coinc', () => cambg.intermediatePointTo(cambg, 0.25).toString().should.equal('52.2050°N, 000.1190°E')); | ||
const greenwich = new LatLon(51.47788, -0.00147), dist = 7794, brng = 300.7; | ||
test('dest’n', () => greenwich.destinationPoint(dist, brng).toString().should.equal('51.5136°N, 000.0983°W')); | ||
test('dest’n r', () => greenwich.destinationPoint(dist, brng, 6371e3).toString().should.equal('51.5136°N, 000.0983°W')); | ||
test('destination', () => greenwich.destinationPoint(dist, brng).toString().should.equal('51.5136°N, 000.0983°W')); | ||
test('destination r', () => greenwich.destinationPoint(dist, brng, 6371e3).toString().should.equal('51.5136°N, 000.0983°W')); | ||
}); | ||
describe('dist/brng/dest fails', function() { | ||
const cambg = new LatLon(52.205, 0.119), paris = new LatLon(48.857, 2.351); | ||
test('distance (fail p)', () => should.Throw(function() { cambg.distanceTo({}); }, TypeError, 'invalid point ‘{}’')); | ||
test('distance (fail p)', () => should.Throw(function() { cambg.distanceTo('paris'); }, TypeError, 'invalid point ‘paris’')); | ||
test('distance (fail r)', () => should.Throw(function() { cambg.distanceTo(paris, 'xxx'); }, TypeError, 'invalid radius ‘xxx’')); | ||
test('init brng (fail)', () => should.Throw(function() { cambg.initialBearingTo('paris'); }, TypeError, 'invalid point ‘paris’')); | ||
test('final brng (fail)', () => should.Throw(function() { cambg.finalBearingTo('paris'); }, TypeError, 'invalid point ‘paris’')); | ||
test('midpoint (fail)', () => should.Throw(function() { cambg.midpointTo('paris'); }, TypeError, 'invalid point ‘paris’')); | ||
test('int.point (fail)', () => should.Throw(function() { cambg.intermediatePointTo('paris', 0.5); }, TypeError, 'invalid point ‘paris’')); | ||
}); | ||
describe('intersection', function() { | ||
const N = 0, E = 90, S = 180, W = 270; | ||
test('toward 1,1 N,E nearest', () => LatLon.intersection(new LatLon(0, 1), N, new LatLon(1, 0), E).toString().should.equal('00.9998°N, 001.0000°E')); | ||
test('toward 1,1 E,N nearest', () => LatLon.intersection(new LatLon(1, 0), E, new LatLon(0, 1), N).toString().should.equal('00.9998°N, 001.0000°E')); | ||
test('toward 1,1 N,E antipodal', () => should.equal(LatLon.intersection(new LatLon(2, 1), N, new LatLon(1, 0), E), null)); | ||
test('toward/away 1,1 N,W antipodal', () => should.equal(LatLon.intersection(new LatLon(0, 1), N, new LatLon(1, 0), W), null)); | ||
test('toward/away 1,1 W,N antipodal', () => should.equal(LatLon.intersection(new LatLon(1, 0), W, new LatLon(0, 1), N), null)); | ||
test('toward/away 1,1 S,E antipodal', () => should.equal(LatLon.intersection(new LatLon(0, 1), S, new LatLon(1, 0), E), null)); | ||
test('toward/away 1,1 E,S antipodal', () => should.equal(LatLon.intersection(new LatLon(1, 0), E, new LatLon(0, 1), S), null)); | ||
test('away 1,1 S,W antipodal', () => LatLon.intersection(new LatLon(0, 1), S, new LatLon(1, 0), W).toString().should.equal('00.9998°S, 179.0000°W')); | ||
test('away 1,1 W,S antipodal', () => LatLon.intersection(new LatLon(1, 0), W, new LatLon(0, 1), S).toString().should.equal('00.9998°S, 179.0000°W')); | ||
test('1E/90E N,E antipodal', () => should.equal(LatLon.intersection(new LatLon(0, 1), N, new LatLon(1, 90), E), null)); | ||
test('1E/90E N,E nearest', () => LatLon.intersection(new LatLon(0, 1), N, new LatLon(1, 92), E).toString().should.equal('00.0175°N, 179.0000°W')); | ||
test('brng+end 1a', () => should.Throw(function() { LatLon.intersection(new LatLon(1, 0), new LatLon(1, 3), new LatLon(2, 2), S); }, TypeError, 'invalid brng1 ‘01.0000°N, 003.0000°E’')); | ||
test('brng+end 1b', () => should.Throw(function() { LatLon.intersection(new LatLon(2, 2), S, new LatLon(1, 0), new LatLon(1, 3)); }, TypeError, 'invalid brng2 ‘01.0000°N, 003.0000°E’')); | ||
test('coincident', () => LatLon.intersection(new LatLon(1, 1), N, new LatLon(1, 1), E).toString().should.equal('01.0000°N, 001.0000°E')); | ||
const stn = new LatLon(51.8853, 0.2545), cdg = new LatLon(49.0034, 2.5735); | ||
test('intersec’n', () => LatLon.intersection(stn, 108.547, cdg, 32.435).toString().should.equal('50.9078°N, 004.5084°E')); | ||
test('stn-cdg-bxl', () => LatLon.intersection(stn, 108.547, cdg, 32.435).toString().should.equal('50.9078°N, 004.5084°E')); | ||
const bradwell = new LatLon(53.3206, -1.7297); | ||
test('cross-track', () => new LatLon(53.2611, -0.7972).crossTrackDistanceTo(bradwell, new LatLon(53.1887, 0.1334)).toPrecision(4).should.equal('-307.5')); | ||
test('bad point 1', () => should.Throw(function() { LatLon.intersection(false, N, new LatLon(1, 0), E); }, TypeError, 'invalid point ‘false’')); | ||
test('bad point 2', () => should.Throw(function() { LatLon.intersection(new LatLon(0, 1), N, false, E); }, TypeError, 'invalid point ‘false’')); | ||
test('int’n (fail 1)', () => should.Throw(function() { LatLon.intersection(null, 'n', null, 's'); }, TypeError, 'invalid (null) point')); | ||
test('int’n (fail 2)', () => should.Throw(function() { LatLon.intersection(stn, 'n', null, 's'); }, TypeError, 'invalid (null) point')); | ||
test('int’n (fail 3)', () => should.Throw(function() { LatLon.intersection(stn, 'n', cdg, 's'); }, TypeError, 'invalid brng1 ‘n’')); | ||
test('int’n (fail 4)', () => should.Throw(function() { LatLon.intersection(stn, 0, cdg, 's'); }, TypeError, 'invalid brng2 ‘s’')); | ||
test('rounding errors', () => LatLon.intersection(new LatLon(51, 0), 120, new LatLon(50, 0), 60).toString().should.equal('50.4921°N, 001.3612°E')); | ||
}); | ||
describe('cross-track / along-track', function() { | ||
test('cross-track p', () => new LatLon(10, 1).crossTrackDistanceTo(new LatLon(0, 0), new LatLon(0, 2)).toPrecision(4).should.equal('-1.112e+6')); | ||
test('cross-track (fail)', () => should.Throw(function() { new LatLon(10, 1).crossTrackDistanceTo(null, new LatLon(0, 2)); }, TypeError, 'Invalid (null) coordinate')); | ||
test('cross-track (fail)', () => should.Throw(function() { new LatLon(10, 1).crossTrackDistanceTo(new LatLon(0, 0), null); }, TypeError, 'Invalid (null) coordinate')); | ||
test('along-track', () => new LatLon(53.2611, -0.7972).alongTrackDistanceTo(bradwell, new LatLon(53.1887, 0.1334)).toPrecision(4).should.equal('6.233e+4')); | ||
test('along-track', () => new LatLon(53.2611, -0.7972).alongTrackDistanceTo('53.3206, -1.7297', '53.1887, 0.1334').toPrecision(4).should.equal('6.233e+4')); | ||
const bradwell = new LatLon(53.3206, -1.7297), dunham = new LatLon(53.2611, -0.7972), partney = new LatLon(53.1887, 0.1334); | ||
test('cross-track', () => dunham.crossTrackDistanceTo(bradwell, partney).toPrecision(4).should.equal('-307.5')); | ||
test('cross-track (fail)', () => should.Throw(function() { new LatLon(10, 1).crossTrackDistanceTo(null, new LatLon(0, 2)); }, TypeError, 'invalid (null) point')); | ||
test('cross-track (fail)', () => should.Throw(function() { new LatLon(10, 1).crossTrackDistanceTo(new LatLon(0, 0), null); }, TypeError, 'invalid (null) point')); | ||
test('along-track', () => dunham.alongTrackDistanceTo(bradwell, partney).toPrecision(4).should.equal('6.233e+4')); | ||
test('cross-track NE', () => new LatLon(1, 1).crossTrackDistanceTo(new LatLon(0, 0), new LatLon(0, 2)).toPrecision(4).should.equal('-1.112e+5')); | ||
test('cross-track SE', () => new LatLon(-1, 1).crossTrackDistanceTo(new LatLon(0, 0), new LatLon(0, 2)).toPrecision(4).should.equal('1.112e+5')); | ||
test('cross-track SW?', () => new LatLon(-1, -1).crossTrackDistanceTo(new LatLon(0, 0), new LatLon(0, 2)).toPrecision(4).should.equal('1.112e+5')); | ||
test('cross-track NW?', () => new LatLon( 1, -1).crossTrackDistanceTo(new LatLon(0, 0), new LatLon(0, 2)).toPrecision(4).should.equal('-1.112e+5')); // eslint-disable-line space-in-parens | ||
test('along-track NE', () => new LatLon( 1, 1).alongTrackDistanceTo(new LatLon(0, 0), new LatLon(0, 2)).toPrecision(4).should.equal('1.112e+5')); // eslint-disable-line space-in-parens | ||
test('along-track SE', () => new LatLon(-1, 1).alongTrackDistanceTo(new LatLon(0, 0), new LatLon(0, 2)).toPrecision(4).should.equal('1.112e+5')); | ||
test('along-track SW', () => new LatLon(-1, -1).alongTrackDistanceTo(new LatLon(0, 0), new LatLon(0, 2)).toPrecision(4).should.equal('-1.112e+5')); | ||
test('along-track NW', () => new LatLon( 1, -1).alongTrackDistanceTo(new LatLon(0, 0), new LatLon(0, 2)).toPrecision(4).should.equal('-1.112e+5')); // eslint-disable-line space-in-parens | ||
test('cross-track coinc', () => new LatLon(10, 0).crossTrackDistanceTo(new LatLon(10, 0), new LatLon(0, 2)).should.be.NaN); | ||
test('cross-track (fail)', () => should.Throw(function() { new LatLon(0, 0).crossTrackDistanceTo(null, 0); }, TypeError, 'invalid (null) point')); | ||
test('cross-track (fail)', () => should.Throw(function() { new LatLon(0, 0).crossTrackDistanceTo(new LatLon(0, 0), 'x'); }, TypeError, 'invalid point ‘x’')); | ||
test('along-track (fail)', () => should.Throw(function() { new LatLon(0, 0).alongTrackDistanceTo(null, 0); }, TypeError, 'invalid (null) point')); | ||
test('along-track (fail)', () => should.Throw(function() { new LatLon(0, 0).alongTrackDistanceTo(new LatLon(0, 0), 'x'); }, TypeError, 'invalid point ‘x’')); | ||
}); | ||
describe('misc', function() { | ||
test('Clairaut 0°', () => new LatLon(0, 0).maxLatitude(0).should.equal(90)); | ||
@@ -164,4 +226,29 @@ test('Clairaut 1°', () => new LatLon(0, 0).maxLatitude(1).should.equal(89)); | ||
test('parallels -', () => should.equal(LatLon.crossingParallels(new LatLon(0, 0), new LatLon(10, 60), 60), null)); | ||
test('parallels coinc', () => should.equal(LatLon.crossingParallels(new LatLon(0, 0), new LatLon(0, 0), 0), null)); | ||
}); | ||
describe('area (polygon-based)', function() { | ||
const polyTriangle = [ new LatLon(1, 1), new LatLon(2, 1), new LatLon(1, 2) ]; | ||
const polySquareCw = [ new LatLon(1, 1), new LatLon(2, 1), new LatLon(2, 2), new LatLon(1, 2) ]; | ||
const polySquareCcw = [ new LatLon(1, 1), new LatLon(1, 2), new LatLon(2, 2), new LatLon(2, 1) ]; | ||
const polyOctant = [ new LatLon(0, ε), new LatLon(90, 0), new LatLon(0, 90-ε) ]; | ||
const polyOctantS = [ new LatLon(-ε, ε), new LatLon(90, 0), new LatLon(-ε, 90-ε) ]; | ||
const polyQuadrant = [ new LatLon(ε, ε), new LatLon(90, ε), new LatLon(ε, 180-ε), new LatLon(ε, 90) ]; | ||
const polyHemiE = [ new LatLon(ε, ε), new LatLon(90-ε, 0), new LatLon(90-ε, 180), new LatLon(ε, 180), new LatLon(-ε, 180), new LatLon(-90+ε, 180), new LatLon(-90+ε, 0), new LatLon(-ε, ε) ]; | ||
const polyPole = [ new LatLon(89, 0), new LatLon(89, 120), new LatLon(89, -120) ]; | ||
const polyConcave = [ new LatLon(1, 1), new LatLon(5, 1), new LatLon(5, 3), new LatLon(1, 3), new LatLon(3, 2) ]; | ||
test('triangle area', () => LatLon.areaOf(polyTriangle).toFixed(0).should.equal('6181527888')); | ||
test('triangle area radius', () => LatLon.areaOf(polyTriangle, 6371e3).toFixed(0).should.equal('6181527888')); | ||
test('triangle area closed', () => LatLon.areaOf(polyTriangle.concat(polyTriangle[0])).toFixed(0).should.equal('6181527888')); | ||
test('square cw area', () => LatLon.areaOf(polySquareCw).toFixed(0).should.equal('12360230987')); | ||
test('square ccw area', () => LatLon.areaOf(polySquareCcw).toFixed(0).should.equal('12360230987')); | ||
test('octant area', () => LatLon.areaOf(polyOctant).toFixed(1).should.equal((π*R*R/2).toFixed(1))); | ||
test('super-octant area', () => LatLon.areaOf(polyOctantS).toFixed(1).should.equal((π*R*R/2).toFixed(1))); | ||
test('quadrant area', () => LatLon.areaOf(polyQuadrant).should.equal(π*R*R)); | ||
test('hemisphere area', () => LatLon.areaOf(polyHemiE).toFixed(1).should.equal((2*π*R*R).toFixed(1))); | ||
test('pole area', () => LatLon.areaOf(polyPole).toFixed(0).should.equal('16063139192')); | ||
test('concave area', () => LatLon.areaOf(polyConcave).toFixed(0).should.equal('74042699236')); | ||
}); | ||
describe('Ed Williams', function() { // www.edwilliams.org/avform.htm | ||
@@ -183,39 +270,2 @@ const lax = new LatLon(Dms.parse('33° 57′N'), Dms.parse('118° 24′W')); | ||
describe('intersections', function() { | ||
const N = 0, E = 90, S = 180, W = 270; | ||
test('toward 1,1 N,E nearest', () => LatLon.intersection(new LatLon(0, 1), N, new LatLon(1, 0), E).toString('d').should.equal('00.9998°N, 001.0000°E')); | ||
test('toward 1,1 E,N nearest', () => LatLon.intersection(new LatLon(1, 0), E, new LatLon(0, 1), N).toString('d').should.equal('00.9998°N, 001.0000°E')); | ||
test('away 1,1 S,W antipodal', () => LatLon.intersection(new LatLon(0, 1), S, new LatLon(1, 0), W).toString('d').should.equal('00.9998°S, 179.0000°W')); | ||
test('away 1,1 W,S antipodal', () => LatLon.intersection(new LatLon(1, 0), W, new LatLon(0, 1), S).toString('d').should.equal('00.9998°S, 179.0000°W')); | ||
test('1E/90E N,E nearest', () => LatLon.intersection(new LatLon(0, 1), N, new LatLon(1, 92), E).toString('d').should.equal('00.0175°N, 179.0000°W')); | ||
const stn = new LatLon(51.8853, 0.2545), cdg = new LatLon(49.0034, 2.5735); | ||
test('stn-cdg-bxl', () => LatLon.intersection(stn, 108.547, cdg, 32.435).toString('d').should.equal('50.9078°N, 004.5084°E')); | ||
test('bad point 1', () => should.Throw(function() { LatLon.intersection(false, N, new LatLon(1, 0), E); }, TypeError, 'Invalid coordinate ‘false’')); | ||
test('bad point 2', () => should.Throw(function() { LatLon.intersection(new LatLon(0, 1), N, false, E); }, TypeError, 'Invalid coordinate ‘false’')); | ||
test('coincident points', () => should.equal(LatLon.intersection(new LatLon(0, 1), N, new LatLon(0, 1), E), null)); | ||
test('rounding errors', () => LatLon.intersection(new LatLon(51, 0), 120, new LatLon(50, 0), 60).toString('d').should.equal('50.4921°N, 001.3612°E')); | ||
}); | ||
describe('area', function() { | ||
const polyTriangle = [ new LatLon(1, 1), new LatLon(2, 1), new LatLon(1, 2) ]; | ||
const polySquareCw = [ new LatLon(1, 1), new LatLon(2, 1), new LatLon(2, 2), new LatLon(1, 2) ]; | ||
const polySquareCcw = [ new LatLon(1, 1), new LatLon(1, 2), new LatLon(2, 2), new LatLon(2, 1) ]; | ||
const polyQuadrant = [ new LatLon(0, 1e-99), new LatLon(0, 180), new LatLon(90, 0) ]; | ||
const polyHemi = [ new LatLon(0, 1), new LatLon(45, 0), new LatLon(89, 90), new LatLon(45, 180), new LatLon(0, 179), new LatLon(-45, 180), new LatLon(-89, 90), new LatLon(-45, 0) ]; | ||
const polyPole = [ new LatLon(89, 0), new LatLon(89, 120), new LatLon(89, -120) ]; | ||
const polyConcave = [ new LatLon(1, 1), new LatLon(5, 1), new LatLon(5, 3), new LatLon(1, 3), new LatLon(3, 2) ]; | ||
test('triangle area', () => LatLon.areaOf(polyTriangle).toFixed(0).should.equal('6181527888')); | ||
test('triangle area radius', () => LatLon.areaOf(polyTriangle, 6371e3).toFixed(0).should.equal('6181527888')); | ||
test('triangle area closed', () => LatLon.areaOf(polyTriangle.concat(polyTriangle[0])).toFixed(0).should.equal('6181527888')); | ||
test('square cw area', () => LatLon.areaOf(polySquareCw).toFixed(0).should.equal('12360230987')); | ||
test('square ccw area', () => LatLon.areaOf(polySquareCcw).toFixed(0).should.equal('12360230987')); | ||
test('quadrant area', () => LatLon.areaOf(polyQuadrant).should.equal(π*R*R)); | ||
test('hemisphere area', () => LatLon.areaOf(polyHemi).toFixed(0).should.equal('252684679676459')); | ||
test('pole area', () => LatLon.areaOf(polyPole).toFixed(0).should.equal('16063139192')); | ||
test('concave area', () => LatLon.areaOf(polyConcave).toFixed(0).should.equal('74042699236')); | ||
}); | ||
describe('rhumb lines', function() { | ||
@@ -226,32 +276,25 @@ const dov = new LatLon(51.127, 1.338), cal = new LatLon(50.964, 1.853); | ||
test('distance dateline E-W', () => new LatLon(1, -179).rhumbDistanceTo(new LatLon(1, 179)).toFixed(6).should.equal(new LatLon(1, 1).rhumbDistanceTo(new LatLon(1, -1)).toFixed(6))); | ||
test('distance err', () => should.Throw(function() { dov.rhumbDistanceTo(false); }, TypeError, 'Invalid coordinate ‘false’')); | ||
test('distance err', () => should.Throw(function() { dov.rhumbDistanceTo(false); }, TypeError, 'invalid point ‘false’')); | ||
test('bearing', () => dov.rhumbBearingTo(cal).toFixed(1).should.equal('116.7')); | ||
test('bearing dateline', () => new LatLon(1, -179).rhumbBearingTo(new LatLon(1, 179)).should.equal(270)); | ||
test('bearing dateline', () => new LatLon(1, 179).rhumbBearingTo(new LatLon(1, -179)).should.equal(90)); | ||
test('bearing err', () => should.Throw(function() { dov.rhumbBearingTo(false); }, TypeError, 'Invalid coordinate ‘false’')); | ||
test('dest’n', () => dov.rhumbDestinationPoint(40310, 116.7).toString('d').should.equal('50.9641°N, 001.8531°E')); | ||
test('dest’n', () => dov.rhumbDestinationPoint(40310, 116.7, 6371e3).toString('d').should.equal('50.9641°N, 001.8531°E')); | ||
test('dest’n', () => new LatLon(1, 1).rhumbDestinationPoint(111178, 90).toString('d').should.equal('01.0000°N, 002.0000°E')); | ||
test('dest’n dateline', () => new LatLon(1, 179).rhumbDestinationPoint(222356, 90).toString('d').should.equal('01.0000°N, 179.0000°W')); | ||
test('dest’n dateline', () => new LatLon(1, -179).rhumbDestinationPoint(222356, 270).toString('d').should.equal('01.0000°N, 179.0000°E')); | ||
test('midpoint', () => dov.rhumbMidpointTo(cal).toString('d').should.equal('51.0455°N, 001.5957°E')); | ||
test('midpoint dateline', () => new LatLon(1, -179).rhumbMidpointTo(new LatLon(1, 178)).toString('d').should.equal('01.0000°N, 179.5000°E')); | ||
test('midpoint err', () => should.Throw(function() { dov.rhumbMidpointTo(false); }, TypeError, 'Invalid coordinate ‘false’')); | ||
test('bearing coincident', () => dov.rhumbBearingTo(dov).should.be.NaN); | ||
test('bearing err', () => should.Throw(function() { dov.rhumbBearingTo(false); }, TypeError, 'invalid point ‘false’')); | ||
test('dest’n', () => dov.rhumbDestinationPoint(40310, 116.7).toString().should.equal('50.9641°N, 001.8531°E')); | ||
test('dest’n', () => dov.rhumbDestinationPoint(40310, 116.7, 6371e3).toString().should.equal('50.9641°N, 001.8531°E')); | ||
test('dest’n', () => new LatLon(1, 1).rhumbDestinationPoint(111178, 90).toString().should.equal('01.0000°N, 002.0000°E')); | ||
test('dest’n dateline', () => new LatLon(1, 179).rhumbDestinationPoint(222356, 90).toString().should.equal('01.0000°N, 179.0000°W')); | ||
test('dest’n dateline', () => new LatLon(1, -179).rhumbDestinationPoint(222356, 270).toString().should.equal('01.0000°N, 179.0000°E')); | ||
test('midpoint', () => dov.rhumbMidpointTo(cal).toString().should.equal('51.0455°N, 001.5957°E')); | ||
test('midpoint dateline', () => new LatLon(1, -179).rhumbMidpointTo(new LatLon(1, 178)).toString().should.equal('01.0000°N, 179.5000°E')); | ||
test('midpoint err', () => should.Throw(function() { dov.rhumbMidpointTo(false); }, TypeError, 'invalid point ‘false’')); | ||
}); | ||
describe('point param as literal', function() { // is this being over-flexible? | ||
const cambg = new LatLon(52.205, 0.119); | ||
test('string', () => cambg.distanceTo('48.857, 2.351').toPrecision(4).should.equal('4.043e+5')); | ||
test('object', () => cambg.distanceTo({ lat: 48.857, lon: 2.351 }).toPrecision(4).should.equal('4.043e+5')); | ||
test('string (fail)', () => should.Throw(function() { cambg.distanceTo('paris'); }, TypeError, 'Invalid coordinate ‘paris’')); | ||
test('object (fail)', () => should.Throw(function() { cambg.distanceTo({ x: 48.857, y: 2.351 }); }, TypeError, 'Invalid coordinate ‘{"x":48.857,"y":2.351}’')); | ||
}); | ||
describe('misc', function() { | ||
test('equals true', () => new LatLon(52.205, 0.119).equals(new LatLon(52.205, 0.119)).should.be.true); | ||
test('equals false', () => new LatLon(52.206, 0.119).equals(new LatLon(52.205, 0.119)).should.be.false); | ||
test('equals error', () => should.Throw(function() { new LatLon(52.206, 0.119).equals(false); }, TypeError, 'Invalid coordinate ‘false’')); | ||
test('toGeoJSON', () => new LatLon(52.205, 0.119).toGeoJSON().should.deep.equal({ type: 'Point', coordinates: [ 0.119, 52.205 ] })); | ||
test('equals true', () => new LatLon(52.205, 0.119).equals(new LatLon(52.205, 0.119)).should.be.true); | ||
test('equals false', () => new LatLon(52.206, 0.119).equals(new LatLon(52.205, 0.119)).should.be.false); | ||
test('equals (fail)', () => should.Throw(function() { new LatLon(52.205, 0.119).equals('cambg'); }, TypeError, 'invalid point ‘cambg’')); | ||
test('toGeoJSON', () => new LatLon(52.205, 0.119).toGeoJSON().should.deep.equal({ type: 'Point', coordinates: [ 0.119, 52.205 ] })); | ||
}); | ||
}); |
@@ -32,15 +32,15 @@ /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ | ||
describe('constructor fail', function() { | ||
test('Invalid northing', () => should.Throw(function() { new OsGridRef(0, 1301e3); }, Error, 'Invalid northing ‘1301000’')); | ||
test('Invalid easting', () => should.Throw(function() { new OsGridRef(701e3, 0); }, Error, 'Invalid easting ‘701000’')); | ||
test('texts', () => should.Throw(function() { new OsGridRef('e', 'n'); }, Error, 'Invalid easting ‘e’')); | ||
test('Invalid northing', () => should.Throw(function() { new OsGridRef(0, 1301e3); }, Error, 'invalid northing ‘1301000’')); | ||
test('Invalid easting', () => should.Throw(function() { new OsGridRef(701e3, 0); }, Error, 'invalid easting ‘701000’')); | ||
test('texts', () => should.Throw(function() { new OsGridRef('e', 'n'); }, Error, 'invalid easting ‘e’')); | ||
}); | ||
describe('parse fail', function() { | ||
test('text', () => should.Throw(function() { OsGridRef.parse('Cambridge'); }, Error, 'Invalid grid reference ‘Cambridge’')); | ||
test('outside range', () => should.Throw(function() { OsGridRef.parse('AA 1 2'); }, Error, 'Invalid grid reference ‘AA 1 2’')); | ||
test('unbalanced numerics', () => should.Throw(function() { OsGridRef.parse('SV 1 20'); }, Error, 'Invalid grid reference ‘SV 1 20’')); | ||
test('text', () => should.Throw(function() { OsGridRef.parse('Cambridge'); }, Error, 'invalid grid reference ‘Cambridge’')); | ||
test('outside range', () => should.Throw(function() { OsGridRef.parse('AA 1 2'); }, Error, 'invalid grid reference ‘AA 1 2’')); | ||
test('unbalanced numerics', () => should.Throw(function() { OsGridRef.parse('SV 1 20'); }, Error, 'invalid grid reference ‘SV 1 20’')); | ||
}); | ||
describe('toString fail', function() { | ||
test('1bad precision', () => should.Throw(function() { new OsGridRef(651409, 313177).toString(20); }, Error, 'Invalid precision ‘20’')); | ||
test('1bad precision', () => should.Throw(function() { new OsGridRef(651409, 313177).toString(20); }, Error, 'invalid precision ‘20’')); | ||
}); | ||
@@ -47,0 +47,0 @@ |
@@ -26,2 +26,3 @@ /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ | ||
test('toString', () => new Utm('31', 'N', 448251, 5411932).toString(4).should.equal('31 N 448251.0000 5411932.0000')); | ||
test('README', () => Utm.parse('48 N 377298.745 1483034.794').toLatLon().toUtm().toString(3).should.equal('48 N 377298.745 1483034.794')); | ||
}); | ||
@@ -31,3 +32,3 @@ | ||
test('constructor', () => new Mgrs(31, 'U', 'D', 'Q', 48251, 11932).toString().should.equal('31U DQ 48251 11932')); | ||
test('toUtm', () => new Mgrs(31, 'U', 'D', 'Q', 48251, 11932).toUtm().toString().should.equal('31 N 448251 5411932')); | ||
test('toUtm', () => Mgrs.parse('31U DQ 48251 11932').toUtm().toString().should.equal('31 N 448251 5411932')); | ||
test('parse', () => Mgrs.parse('31U DQ 48251 11932').toString().should.equal('31U DQ 48251 11932')); | ||
@@ -42,5 +43,8 @@ test('parse military-style', () => Mgrs.parse('31UDQ4825111932').toString().should.equal('31U DQ 48251 11932')); | ||
describe('UTM constructor fail', function() { | ||
test('zone fail', () => should.Throw(function() { new Utm(0, 'N', 0, 0); }, RangeError, 'Invalid UTM zone ‘0’')); | ||
test('zone fail', () => should.Throw(function() { new Utm(61, 'N', 0, 0); }, RangeError, 'Invalid UTM zone ‘61’')); | ||
test('hemisphere fail', () => should.Throw(function() { new Utm(1, 'E', 0, 0); }, RangeError, 'Invalid UTM hemisphere ‘E’')); | ||
test('zone fail', () => should.Throw(function() { new Utm(0, 'N', 0, 0); }, RangeError, 'invalid UTM zone ‘0’')); | ||
test('zone fail', () => should.Throw(function() { new Utm(61, 'N', 0, 0); }, RangeError, 'invalid UTM zone ‘61’')); | ||
test('hemisphere fail', () => should.Throw(function() { new Utm(1, 'E', 0, 0); }, RangeError, 'invalid UTM hemisphere ‘E’')); | ||
test('easting fail', () => should.Throw(function() { new Utm(1, 'N', 1001e3, 0); }, RangeError, 'invalid UTM easting ‘1001000’')); | ||
test('northing N fail', () => should.Throw(function() { new Utm(1, 'N', 0, 9329e3); }, RangeError, 'invalid UTM northing ‘9329000’')); | ||
test('northing S fail', () => should.Throw(function() { new Utm(1, 'S', 0, 1118e3); }, RangeError, 'invalid UTM northing ‘1118000’')); | ||
}); | ||
@@ -50,24 +54,24 @@ | ||
test('bad zone', () => should.Throw(function() { new Mgrs(0, 'C', 'A', 'A', 0, 0); }, RangeError, 'MGRS zone ‘0’ out of range')); | ||
test('bad band', () => should.Throw(function() { new Mgrs(1, 'A', 'A', 'A', 0, 0); }, RangeError, 'Invalid MGRS band ‘A’')); | ||
test('bad grid sq easting', () => should.Throw(function() { new Mgrs(1, 'C', 'I', 'A', 0, 0); }, RangeError, 'Invalid MGRS 100km grid square column ‘I’ for zone 1')); | ||
test('bad grid sq northing', () => should.Throw(function() { new Mgrs(1, 'C', 'A', 'I', 0, 0); }, RangeError, 'Invalid MGRS 100km grid square row ‘I’')); | ||
test('invalid grid sq e', () => should.Throw(function() { new Mgrs(2, 'C', 'A', 'A', 0, 0); }, RangeError, 'Invalid MGRS 100km grid square column ‘A’ for zone 2')); | ||
test('bad easting', () => should.Throw(function() { new Mgrs(1, 'C', 'A', 'A', 'x', 0); }, RangeError, 'Invalid MGRS easting ‘x’')); | ||
test('bad northing', () => should.Throw(function() { new Mgrs(1, 'C', 'A', 'A', 0, 'x'); }, RangeError, 'Invalid MGRS northing ‘x’')); | ||
test('too far north', () => should.Throw(function() { new Mgrs(1, 'C', 'A', 'A', 0, 'x'); }, RangeError, 'Invalid MGRS northing ‘x’')); | ||
test('bad multiples', () => should.Throw(function() { new Mgrs(1, 'A', 'A', 'I', 0, 0); }, RangeError, 'Invalid MGRS band ‘A’, Invalid MGRS 100km grid square row ‘I’')); | ||
test('bad band', () => should.Throw(function() { new Mgrs(1, 'A', 'A', 'A', 0, 0); }, RangeError, 'invalid MGRS band ‘A’')); | ||
test('bad grid sq easting', () => should.Throw(function() { new Mgrs(1, 'C', 'I', 'A', 0, 0); }, RangeError, 'invalid MGRS 100km grid square column ‘I’ for zone 1')); | ||
test('bad grid sq northing', () => should.Throw(function() { new Mgrs(1, 'C', 'A', 'I', 0, 0); }, RangeError, 'invalid MGRS 100km grid square row ‘I’')); | ||
test('invalid grid sq e', () => should.Throw(function() { new Mgrs(2, 'C', 'A', 'A', 0, 0); }, RangeError, 'invalid MGRS 100km grid square column ‘A’ for zone 2')); | ||
test('bad easting', () => should.Throw(function() { new Mgrs(1, 'C', 'A', 'A', 'x', 0); }, RangeError, 'invalid MGRS easting ‘x’')); | ||
test('bad northing', () => should.Throw(function() { new Mgrs(1, 'C', 'A', 'A', 0, 'x'); }, RangeError, 'invalid MGRS northing ‘x’')); | ||
test('too far north', () => should.Throw(function() { new Mgrs(1, 'C', 'A', 'A', 0, 'x'); }, RangeError, 'invalid MGRS northing ‘x’')); | ||
test('bad multiples', () => should.Throw(function() { new Mgrs(1, 'A', 'A', 'I', 0, 0); }, RangeError, 'invalid MGRS band ‘A’, invalid MGRS 100km grid square row ‘I’')); | ||
}); | ||
describe('UTM parse', function() { | ||
test('parse fail', () => should.Throw(function() { Utm.parse('Cambridge'); }, Error, 'Invalid UTM coordinate ‘Cambridge’')); | ||
test('parse fail', () => should.Throw(function() { Utm.parse('Cambridge'); }, Error, 'invalid UTM coordinate ‘Cambridge’')); | ||
}); | ||
describe('toString', function() { | ||
test('toString fail', () => should.Throw(function() { new Mgrs(1, 'C', 'A', 'A', 0, 0).toString(3); }, Error, 'Invalid precision ‘3’')); | ||
test('toString fail', () => should.Throw(function() { new Mgrs(1, 'C', 'A', 'A', 0, 0).toString(3); }, Error, 'invalid precision ‘3’')); | ||
}); | ||
describe('MGRS parse', function() { | ||
test('parse fail 1', () => should.Throw(function() { Mgrs.parse(null); }, Error, 'Invalid MGRS grid reference ‘null’')); | ||
test('parse fail 2', () => should.Throw(function() { Mgrs.parse('Cambridge'); }, Error, 'Invalid MGRS grid reference ‘Cambridge’')); | ||
test('parse fail 3', () => should.Throw(function() { Mgrs.parse('New York'); }, Error, 'Invalid MGRS grid reference ‘New York’')); | ||
test('parse fail 1', () => should.Throw(function() { Mgrs.parse(null); }, Error, 'invalid MGRS grid reference ‘null’')); | ||
test('parse fail 2', () => should.Throw(function() { Mgrs.parse('Cambridge'); }, Error, 'invalid MGRS grid reference ‘Cambridge’')); | ||
test('parse fail 3', () => should.Throw(function() { Mgrs.parse('New York'); }, Error, 'invalid MGRS grid reference ‘New York’')); | ||
}); | ||
@@ -157,3 +161,3 @@ | ||
describe('UTM fail', function() { | ||
test('zone fail', () => should.Throw(function() { new LatLon(85, 0).toUtm(); }, RangeError, 'Latitude outside UTM limits')); | ||
test('zone fail', () => should.Throw(function() { new LatLon(85, 0).toUtm(); }, RangeError, 'latitude ‘85’ outside UTM limits')); | ||
}); | ||
@@ -160,0 +164,0 @@ |
@@ -21,3 +21,3 @@ /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ | ||
describe('constructor fail', function() { | ||
test('texts', () => should.Throw(function() { new Vector3d('x', 'y', 'z'); }, TypeError, 'Invalid vector')); | ||
test('texts', () => should.Throw(function() { new Vector3d('x', 'y', 'z'); }, TypeError, 'invalid vector [x,y,z]')); | ||
}); | ||
@@ -31,2 +31,3 @@ | ||
test('times', () => v123.times(2).should.deep.equal(new Vector3d(2, 4, 6))); | ||
test('times str', () => v123.times('2').should.deep.equal(new Vector3d(2, 4, 6))); | ||
test('dividedBy', () => v123.dividedBy(2).should.deep.equal(new Vector3d(0.5, 1, 1.5))); | ||
@@ -38,3 +39,6 @@ test('dot', () => v123.dot(v321).should.equal(10)); | ||
test('unit', () => v123.unit().toString().should.equal('[0.267,0.535,0.802]')); | ||
test('angleTo', () => (v123.angleTo(v321).toDegrees().toFixed(3)).should.equal('44.415')); | ||
test('angleTo', () => v123.angleTo(v321).toDegrees().toFixed(3).should.equal('44.415')); | ||
test('angleTo +', () => v123.angleTo(v321, v123.cross(v321)).toDegrees().toFixed(3).should.equal('44.415')); | ||
test('angleTo -', () => v123.angleTo(v321, v321.cross(v123)).toDegrees().toFixed(3).should.equal('-44.415')); | ||
test('angleTo 0', () => v123.angleTo(v321, v123).toDegrees().toFixed(3).should.equal('44.415')); | ||
test('rotateAround', () => v123.rotateAround(new Vector3d(0, 0, 1), 90).toString().should.equal('[-0.535,0.267,0.802]')); | ||
@@ -50,2 +54,4 @@ test('toString', () => v123.toString().should.equal('[1.000,2.000,3.000]')); | ||
test('minus', () => should.Throw(function() { v123.minus(1); }, TypeError, 'v is not Vector3d object')); | ||
test('times', () => should.Throw(function() { v123.times('x'); }, TypeError, 'invalid scalar value ‘x’')); | ||
test('dividedBy', () => should.Throw(function() { v123.dividedBy('x'); }, TypeError, 'invalid scalar value ‘x’')); | ||
test('dot', () => should.Throw(function() { v123.dot(1); }, TypeError, 'v is not Vector3d object')); | ||
@@ -52,0 +58,0 @@ test('cross', () => should.Throw(function() { v123.cross(1); }, TypeError, 'v is not Vector3d object')); |
30
utm.js
@@ -45,3 +45,3 @@ /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ | ||
* @param {number} [convergence=null] - Meridian convergence (bearing of grid north | ||
* clockwise from true north), in degrees. | ||
* clockwise from true north), in degrees. | ||
* @param {number} [scale=null] - Grid scale factor. | ||
@@ -55,7 +55,7 @@ * @throws {TypeError} Invalid UTM coordinate. | ||
constructor(zone, hemisphere, easting, northing, datum=LatLonEllipsoidal.datums.WGS84, convergence=null, scale=null) { | ||
if (!(1<=zone && zone<=60)) throw new RangeError(`Invalid UTM zone ‘${zone}’`); | ||
if (!hemisphere.match(/[NS]/i)) throw new RangeError(`Invalid UTM hemisphere ‘${hemisphere}’`); | ||
// range-check easting/northing (with 40km overlap between zones) - TODO is this worthwhile? | ||
//if (!(120e3<=easting && easting<=880e3)) throw new Error('Invalid UTM easting '+ easting); | ||
//if (!(0<=northing && northing<=10000e3)) throw new Error('Invalid UTM northing '+ northing); | ||
if (!(1<=zone && zone<=60)) throw new RangeError(`invalid UTM zone ‘${zone}’`); | ||
if (!hemisphere.match(/[NS]/i)) throw new RangeError(`invalid UTM hemisphere ‘${hemisphere}’`); | ||
if (!(0<=easting && easting<=1000e3)) throw new RangeError(`invalid UTM easting ‘${easting}’`); | ||
if (hemisphere=='N' && !(0<=northing && northing<9328094)) throw new RangeError(`invalid UTM northing ‘${northing}’`); | ||
if (hemisphere=='S' && !(1118414<northing && northing<=10000e3)) throw new RangeError(`invalid UTM northing ‘${northing}’`); | ||
@@ -83,9 +83,7 @@ this.zone = Number(zone); | ||
toLatLon() { | ||
const z = this.zone; | ||
const h = this.hemisphere; | ||
const { zone: z, hemisphere: h } = this; | ||
const falseEasting = 500e3, falseNorthing = 10000e3; | ||
const a = this.datum.ellipsoid.a, f = this.datum.ellipsoid.f; | ||
// WGS-84: a = 6378137, b = 6356752.314245, f = 1/298.257223563; | ||
const { a, f } = this.datum.ellipsoid; // WGS-84: a = 6378137, f = 1/298.257223563; | ||
@@ -174,3 +172,3 @@ const k0 = 0.9996; // UTM scale on the central meridian | ||
const latLong = new LatLonEllipsoidal(lat, lon, 0, this.datum); | ||
const latLong = new LatLon_Utm(lat, lon, 0, this.datum); | ||
// ... and add the convergence and scale into the LatLon object ... wonderful JavaScript! | ||
@@ -206,3 +204,3 @@ latLong.convergence = convergence; | ||
if (utmCoord==null || utmCoord.length!=4) throw new Error(`Invalid UTM coordinate ‘${utmCoord}’`); | ||
if (utmCoord==null || utmCoord.length!=4) throw new Error(`invalid UTM coordinate ‘${utmCoord}’`); | ||
@@ -266,3 +264,3 @@ const zone = utmCoord[0], hemisphere = utmCoord[1], easting = utmCoord[2], northing = utmCoord[3]; | ||
toUtm() { | ||
if (!(-80<=this.lat && this.lat<=84)) throw new RangeError('Latitude outside UTM limits'); | ||
if (!(-80<=this.lat && this.lat<=84)) throw new RangeError(`latitude ‘${this.lat}’ outside UTM limits`); | ||
@@ -291,5 +289,5 @@ const falseEasting = 500e3, falseNorthing = 10000e3; | ||
const ellipsoid = this.datum ? this.datum.ellipsoid : this.referenceFrame ? this.referenceFrame.ellipsoid : LatLonEllipsoidal.ellipsoids.WGS84; | ||
const a = ellipsoid.a, f = ellipsoid.f; | ||
// WGS-84: a = 6378137, b = 6356752.314245, f = 1/298.257223563; | ||
// allow alternative ellipsoid to be specified | ||
const ellipsoid = this.datum ? this.datum.ellipsoid : LatLonEllipsoidal.ellipsoids.WGS84; | ||
const { a, f } = ellipsoid; // WGS-84: a = 6378137, f = 1/298.257223563; | ||
@@ -296,0 +294,0 @@ const k0 = 0.9996; // UTM scale on the central meridian |
@@ -40,3 +40,3 @@ /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ | ||
constructor(x, y, z) { | ||
if (isNaN(x) || isNaN(x) || isNaN(x)) throw new TypeError('Invalid vector'); | ||
if (isNaN(x) || isNaN(x) || isNaN(x)) throw new TypeError(`invalid vector [${x},${y},${z}]`); | ||
@@ -92,3 +92,3 @@ this.x = Number(x); | ||
times(x) { | ||
x = Number(x); | ||
if (isNaN(x)) throw new TypeError(`invalid scalar value ‘${x}’`); | ||
@@ -106,3 +106,3 @@ return new Vector3d(this.x * x, this.y * x, this.z * x); | ||
dividedBy(x) { | ||
x = Number(x); | ||
if (isNaN(x)) throw new TypeError(`invalid scalar value ‘${x}’`); | ||
@@ -173,8 +173,10 @@ return new Vector3d(this.x / x, this.y / x, this.z / x); | ||
/** | ||
* Calculates the angle between ‘this’ vector and supplied vector atan2(|p₁×p₂|, p₁·p₂). | ||
* Calculates the angle between ‘this’ vector and supplied vector atan2(|p₁×p₂|, p₁·p₂) (or if | ||
* (extra-planar) ‘n’ supplied then atan2(n·p₁×p₂, p₁·p₂). | ||
* | ||
* @param {Vector3d} v | ||
* @param {Vector3d} [n] - Plane normal: if supplied, angle is -π..+π, signed +ve if this->v is | ||
* clockwise looking along n, -ve in opposite direction (if not supplied, angle is always 0..π). | ||
* @returns {number} Angle (in radians) between this vector and supplied vector. | ||
* @param {Vector3d} v - Vector whose angle is to be determined from ‘this’ vector. | ||
* @param {Vector3d} [n] - Plane normal: if supplied, angle is signed +ve if this->v is | ||
* clockwise looking along n, -ve in opposite direction. | ||
* @returns {number} Angle (in radians) between this vector and supplied vector (in range 0..π | ||
* if n not supplied, range -π..+π if n supplied). | ||
*/ | ||
@@ -185,3 +187,8 @@ angleTo(v, n=undefined) { | ||
const sign = n==undefined ? 1 : Math.sign(this.cross(v).dot(n)); | ||
// q.v. stackoverflow.com/questions/14066933#answer-16544330, but n·p₁×p₂ is numerically | ||
// ill-conditioned, so just calculate sign to apply to |p₁×p₂| | ||
// if n·p₁×p₂ is -ve, negate |p₁×p₂| | ||
const sign = n==undefined || this.cross(v).dot(n)>=0 ? 1 : -1; | ||
const sinθ = this.cross(v).length * sign; | ||
@@ -188,0 +195,0 @@ const cosθ = this.dot(v); |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
417624
6240
218
0
8