diff --git a/src/main/kotlin/uk/co/wedgetech/geodesy/Dms.kt b/src/main/kotlin/uk/co/wedgetech/geodesy/Dms.kt index 3d83358..a60bbc4 100644 --- a/src/main/kotlin/uk/co/wedgetech/geodesy/Dms.kt +++ b/src/main/kotlin/uk/co/wedgetech/geodesy/Dms.kt @@ -124,7 +124,7 @@ import kotlin.math.roundToInt internal fun toDegreesMinutes(degrees: Double, dp: Int, separator : String) : String { var d = Math.floor(degrees).toInt() // get component deg - var m = ((degrees * 60.0) % 60).toFixed(dp) // get component min & round/right-pad + var m = ((degrees * 60.0) % 60.0).toFixed(dp) // get component min & round/right-pad if (m == 60.0) { m = 0.0 d++ @@ -135,8 +135,8 @@ import kotlin.math.roundToInt internal fun toDegreesMinutesSeconds(degrees: Double, dp: Int, separator: String) : String { var d = Math.floor(degrees).toInt() // get component deg - var m = (Math.floor((degrees * 3600) / 60) % 60).toInt() // get component min - var s = (degrees * 3600 % 60).toFixed(dp); // get component sec & round/right-pad + var m = (Math.floor((degrees * 3600.0) / 60.0) % 60.0).toInt() // get component min + var s = (degrees * 3600.0 % 60.0).toFixed(dp); // get component sec & round/right-pad if (s == 60.0) { s = 0.0 m++ @@ -165,10 +165,10 @@ import kotlin.math.roundToInt * @param {number} [dp=0|2|4] - Number of decimal places to use – default 0 for dms, 2 for dm, 4 for d. * @returns {string} Degrees formatted as deg/min/secs according to specified format. */ - fun Double.toLatitude(format: String, dp: Int = 0, separator: String = ""): String + fun Double.toLatitude(format: String, dp: Int? = null, separator: String = ""): String { val lat = this.toDMS(format, dp, separator) - return if (lat == null) "–" else lat+separator+(if (this<0.0) 'S' else 'N'); // knock off initial '0' for lat! + return if (lat == null) "–" else lat.substring(1)+separator+(if (this<0.0) 'S' else 'N'); // knock off initial '0' for lat! }; @@ -180,7 +180,7 @@ import kotlin.math.roundToInt * @param {number} [dp=0|2|4] - Number of decimal places to use – default 0 for dms, 2 for dm, 4 for d. * @returns {string} Degrees formatted as deg/min/secs according to specified format. */ - fun Double.toLongitude(format: String, dp: Int = 0, separator : String = ""): String + fun Double.toLongitude(format: String, dp: Int? = null, separator : String = ""): String { val lon = this.toDMS(format, dp, separator); return if (lon == null) "–" else lon+separator+(if (this<0.0) 'W' else 'E'); diff --git a/src/main/kotlin/uk/co/wedgetech/geodesy/LatLon-Vectors.kt b/src/main/kotlin/uk/co/wedgetech/geodesy/LatLon-Vectors.kt index 27d40f6..99cd07a 100644 --- a/src/main/kotlin/uk/co/wedgetech/geodesy/LatLon-Vectors.kt +++ b/src/main/kotlin/uk/co/wedgetech/geodesy/LatLon-Vectors.kt @@ -2,6 +2,7 @@ import uk.co.wedgetech.geodesy.LatLon import uk.co.wedgetech.geodesy.Vector3d import uk.co.wedgetech.geodesy.toDegrees import uk.co.wedgetech.geodesy.toRadians +import java.util.* /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ /* Vector-based spherical geodetic (latitude/longitude) functions (c) Chris Veness 2011-2017 */ @@ -396,6 +397,7 @@ fun LatLon.intersection(path1start: LatLon, path1brngEnd: LatLon, path2start: La * var p1 = new LatLon(53.3206, -1.7297), p2 = new LatLon(53.1887, 0.1334); * var d = pCurrent.crossTrackDistanceTo(p1, p2); // -307.5 m */ +/* fun LatLon.crossTrackDistanceTo(pathStart: LatLon, pathBrngEnd, radius: Double = 6371e3): Double { var p = this.toVector(); @@ -410,7 +412,7 @@ fun LatLon.crossTrackDistanceTo(pathStart: LatLon, pathBrngEnd, radius: Double = return d; }; - +*/ /** * Returns how far ‘this’ point is along a path from from start-point, heading on bearing or towards @@ -429,6 +431,7 @@ fun LatLon.crossTrackDistanceTo(pathStart: LatLon, pathBrngEnd, radius: Double = * var p2 = new LatLon(53.1887, 0.1334); * var d = pCurrent.alongTrackDistanceTo(p1, p2); // 62.331 km */ +/* fun LatLon.alongTrackDistanceTo(pathStart, pathBrngEnd, radius: Double = 6371e3) { if (!(pathStart instanceof LatLon)) throw new TypeError('pathStart is not LatLon object'); @@ -446,7 +449,7 @@ fun LatLon.alongTrackDistanceTo(pathStart, pathBrngEnd, radius: Double = 6371e3) return d; }; - +*/ /** * Returns closest point on great circle segment between point1 & point2 to ‘this’ point. @@ -530,34 +533,33 @@ fun LatLon.isBetween(point1: LatLon, point2: LatLon):Boolean { * var p = new LatLon(45.1, 1.1); * var inside = p.enclosedBy(bounds); // true */ -fun LatLon.enclosedBy(polygon: Array): Boolean { +fun LatLon.enclosedBy(polygon_: Array): Boolean { // this method uses angle summation test; on a plane, angles for an enclosed point will sum // to 360°, angles for an exterior point will sum to 0°. On a sphere, enclosed point angles // will sum to less than 360° (due to spherical excess), exterior point angles will be small // but non-zero. TODO: are any winding number optimisations applicable to spherical surface? // close the polygon so that the last point equals the first point - var closed = polygon[0].equals(polygon[polygon.length-1]); - if (!closed) polygon.push(polygon[0]); + val polygon = if(polygon_[0] == polygon_[polygon_.size-1]) polygon_ else polygon_ + arrayOf(polygon_[0]) - var nVertices = polygon.length - 1; + var nVertices = polygon.size - 1; var p = this.toVector(); // get vectors from p to each vertex - var vectorToVertex = []; - for (var v=0; v() + for (value in polygon) vectorToVertex.add(p - value.toVector()) + vectorToVertex.add(vectorToVertex[0]); // sum subtended angles of each edge (using vector p to determine sign) - var Σθ = 0; - for (var v=0; v Math.PI; - - if (!closed) polygon.pop(); // restore polygon to pristine condition + val enclosed = Math.abs(Σθ) > Math.PI; return enclosed; }; @@ -575,37 +577,41 @@ fun LatLon.enclosedBy(polygon: Array): Boolean { * var polygon = [ new LatLon(0,0), new LatLon(1,0), new LatLon(0,1) ]; * var area = LatLon.areaOf(polygon); // 6.18e9 m² */ -fun LatLon.areaOf(polygon: Array, radius: Double = 6371e3) { +fun LatLon.areaOf(polygon_: Array, radius: Double = 6371e3):Double { // uses Girard’s theorem: A = [Σθᵢ − (n−2)·π]·R² + if (polygon_.size < 2) return 0.0 // close the polygon so that the last point equals the first point - var closed = polygon[0].equals(polygon[polygon.length-1]); - if (!closed) polygon.push(polygon[0]); + val polygon = if(polygon_[0] == polygon_[polygon_.size-1]) polygon_ else polygon_ + arrayOf(polygon_[0]) - var n = polygon.length - 1; // number of vertices + val n = polygon.size - 1; // number of vertices // get great-circle vector for each edge - var c = []; - for (var v=0; v() + var j : Vector3d? = null + for (v in polygon) { + val i = v.toVector(); + if (j!=null) c.add(i.cross(j)); // great circle for segment v..v+1 + j = i } - c.push(c[0]); + c.add(c[0]); // sum interior angles; depending on whether polygon is cw or ccw, angle between edges is // π−α or π+α, where α is angle between great-circle vectors; so sum α, then take n·π − |Σα| // (cannot use Σ(π−|α|) as concave polygons would fail); use vector to 1st point as plane // normal for sign of α var n1 = polygon[0].toVector(); - var Σα = 0; - for (var v=0; v= 0.00001); // ie until < 0.01mm - - val cosφ = Math.cos(φ) - val sinφ = Math.sin(φ); - val ν = a * F0 / Math.sqrt(1 - (e2 * sinφ * sinφ)); // nu = transverse radius of curvature - val ρ = a * F0 * (1 - e2) / Math.pow(1 - (e2 * sinφ * sinφ), 1.5); // rho = meridional radius of curvature - val η2 = ν / ρ - 1; // eta = ? - - val tanφ = Math.tan(φ); - val tan2φ = tanφ * tanφ - val tan4φ = tan2φ*tan2φ - val tan6φ = tan4φ*tan2φ; - val secφ = 1 / cosφ; - val ν3 = ν * ν * ν - val ν5 = ν3*ν*ν - val ν7 = ν5*ν*ν; - val VII = tanφ / (2 * ρ * ν); - val VIII = tanφ / (24 * ρ * ν3) * (5 + 3 * tan2φ + η2 - 9 * tan2φ * η2); - val IX = tanφ / (720 * ρ * ν5) * (61 + 90 * tan2φ + 45 * tan4φ); - val X = secφ / ν; - val XI = secφ / (6 * ν3) * (ν / ρ + 2 * tan2φ); - val XII = secφ / (120 * ν5) * (5 + 28 * tan2φ + 24 * tan4φ); - val XIIA = secφ / (5040 * ν7) * (61 + 662 * tan2φ + 1320 * tan4φ + 720 * tan6φ); - - val dE = (E - E0) - val dE2 = dE*dE - val dE3 = dE2*dE - val dE4 = dE2*dE2 - val dE5 = dE3*dE2 - val dE6 = dE4*dE2 - val dE7 = dE5*dE2; - φ -= (VII * dE2) + (VIII * dE4) - (IX * dE6); - val λ = λ0 + (X * dE) - (XI * dE3) + (XII * dE5) - (XIIA * dE7); - - var point = LatLon (φ.toDegrees(), λ.toDegrees(), LatLon.OSGB36); - if (datum != LatLon.OSGB36) point = point.convertDatum(datum); - - return point; - }; + override fun toString() :String = toString(10) /** * Converts ‘this’ numeric grid reference to standard OS grid reference. @@ -209,7 +51,7 @@ data class OsGridRef(val easting : Double, val northing: Double) { * @example * var ref = new OsGridRef(651409, 313177).toString(); // TG 51409 13177 */ - fun toString(digits: Int = 10): String + fun toString(digits: Int): String { if (digits % 2 != 0 || digits > 16) throw IllegalArgumentException("Invalid precision ${digits}") @@ -311,7 +153,7 @@ fun String.parseOsGridReference() : OsGridRef var en = gridref.substring(2).trim().split(Regex("\\s+")); // if e/n not whitespace separated, split half way if (en.size == 1) { - en = listOf(en[0].substring(IntRange(0, en[0].length / 2)), en[0].substring(en[0].length / 2)) + en = listOf(en[0].substring(IntRange(0, (en[0].length / 2)-1)), en[0].substring(en[0].length / 2)) }; // validation @@ -329,4 +171,161 @@ fun String.parseOsGridReference() : OsGridRef return OsGridRef (e.toDouble(), n.toDouble()); }; +/** + * Converts latitude/longitude to Ordnance Survey grid reference easting/northing coordinate. + * + * Note formulation implemented here due to Thomas, Redfearn, etc is as published by OS, but is + * inferior to Krüger as used by e.g. Karney 2011. + * + * @param {LatLon} point - latitude/longitude. + * @returns {OsGridRef} OS Grid Reference easting/northing. + * + * @example + * var p = new LatLon(52.65798, 1.71605); + * var grid = OsGridRef.latLonToOsGrid(p); // grid.toString(): TG 51409 13177 + * // for conversion of (historical) OSGB36 latitude/longitude point: + * var p = new LatLon(52.65757, 1.71791, LatLon.datum.OSGB36); + */ +fun LatLonDatum.toOsGrid(): OsGridRef { + // if necessary convert to OSGB36 first + val osgbPoint = if (this.datum != LatLonDatum.OSGB36) this.convertDatum(LatLonDatum.OSGB36) + else this + + val φ = osgbPoint.lat.toRadians(); + val λ = osgbPoint.lon.toRadians(); + + val a = 6377563.396 + val b = 6356256.909; // Airy 1830 major & minor semi-axes + val F0 = 0.9996012717 // NatGrid scale factor on central meridian + val φ0 = (49.0).toRadians() + val λ0 = (-2.0).toRadians(); // NatGrid true origin is 49°N,2°W + val N0 = -100000.0 + val E0 = 400000.0 // northing & easting of true origin, metres + val e2 = 1 - (b * b) / (a * a); // eccentricity squared + val n = (a - b) / (a + b) + val n2 = n * n + val n3 = n * n * n; // n, n², n³ + + val cosφ = Math.cos(φ) + val sinφ = Math.sin(φ); + val ν = a * F0 / Math.sqrt(1 - e2 * sinφ * sinφ); // nu = transverse radius of curvature + val ρ = a * F0 * (1.0 - e2) / Math.pow(1.0 - e2 * sinφ * sinφ, 1.5); // rho = meridional radius of curvature + val η2 = ν / ρ - 1.0; // eta = ? + + val Ma = (1.0 + n + (5.0 / 4.0) * n2 + (5.0 / 4.0) * n3) * (φ - φ0); + val Mb = (3.0 * n + 3.0 * n * n + (21.0 / 8.0) * n3) * Math.sin(φ - φ0) * Math.cos(φ + φ0); + val Mc = ((15.0 / 8.0) * n2 + (15.0 / 8.0) * n3) * Math.sin(2.0 * (φ - φ0)) * Math.cos(2 * (φ + φ0)); + val Md = (35.0 / 24.0) * n3 * Math.sin(3.0 * (φ - φ0)) * Math.cos(3.0 * (φ + φ0)); + val M = b * F0 * (Ma - Mb + Mc - Md); // meridional arc + + val cos3φ = cosφ * cosφ * cosφ; + val cos5φ = cos3φ * cosφ * cosφ; + val tan2φ = Math.tan(φ) * Math.tan(φ); + val tan4φ = tan2φ * tan2φ; + + val I = M + N0; + val II = (ν / 2.0) * sinφ * cosφ; + val III = (ν / 24.0) * sinφ * cos3φ * (5.0 - tan2φ + 9.0 * η2); + val IIIA = (ν / 720.0) * sinφ * cos5φ * (61.0 - 58.0 * tan2φ + tan4φ); + val IV = ν * cosφ; + val V = (ν / 6.0) * cos3φ * (ν / ρ - tan2φ); + val VI = (ν / 120.0) * cos5φ * (5.0 - 18.0 * tan2φ + tan4φ + 14.0 * η2 - 58.0 * tan2φ * η2); + + val Δλ = λ - λ0; + val Δλ2 = Δλ * Δλ + val Δλ3 = Δλ2 * Δλ + val Δλ4 = Δλ3 * Δλ + val Δλ5 = Δλ4 * Δλ + val Δλ6 = Δλ5 * Δλ; + + val N = I + II * Δλ2 + III * Δλ4 + IIIA * Δλ6; + val E = E0 + IV * Δλ + V * Δλ3 + VI * Δλ5; + + return OsGridRef(E.toFixed(3), N.toFixed(3)); // gets truncated to SW corner of 1m grid square +}; + +/** + * Converts Ordnance Survey grid reference easting/northing coordinate to latitude/longitude + * (SW corner of grid square). + * + * Note formulation implemented here due to Thomas, Redfearn, etc is as published by OS, but is + * inferior to Krüger as used by e.g. Karney 2011. + * + * @param {OsGridRef} gridref - Grid ref E/N to be converted to lat/long (SW corner of grid square). + * @param {LatLon.datum} [datum=WGS84] - Datum to convert grid reference into. + * @returns {LatLon} Latitude/longitude of supplied grid reference. + * + * @example + * var gridref = new OsGridRef(651409.903, 313177.270); + * var pWgs84 = OsGridRef.osGridToLatLon(gridref); // 52°39′28.723″N, 001°42′57.787″E + * // to obtain (historical) OSGB36 latitude/longitude point: + * var pOsgb = OsGridRef.osGridToLatLon(gridref, LatLon.datum.OSGB36); // 52°39′27.253″N, 001°43′04.518″E + */ +fun OsGridRef.toLatLonDatum(datum: LatLonDatum.Datum = LatLonDatum.WGS84): LatLonDatum +{ + val E = this.easting; + val N = this.northing; + + val a = 6377563.396 + val b = 6356256.909; // Airy 1830 major & minor semi-axes + val F0 = 0.9996012717; // NatGrid scale factor on central meridian + val φ0 = (49.0).toRadians() + val λ0 = (-2.0).toRadians() // NatGrid true origin is 49°N,2°W + val N0 = -100000.0 + val E0 = 400000.0; // northing & easting of true origin, metres + val e2 = 1.0 - (b * b) / (a * a); // eccentricity squared + val n = (a - b) / (a + b) + val n2 = n*n + val n3 = n*n*n; // n, n², n³ + + var φ = φ0 + var M = 0.0; + do { + φ = (N - N0 - M) / (a * F0) + φ; + + val Ma = (1.0 + n + (5.0 / 4.0) * n2 + (5.0 / 4.0) * n3) * (φ - φ0); + val Mb = (3.0 * n + 3.0 * n * n + (21.0 / 8.0) * n3) * Math.sin(φ - φ0) * Math.cos(φ + φ0); + val Mc = ((15.0 / 8.0) * n2 + (15.0 / 8.0) * n3) * Math.sin(2.0 * (φ - φ0)) * Math.cos(2.0 * (φ + φ0)); + val Md = (35.0 / 24.0) * n3 * Math.sin(3.0 * (φ - φ0)) * Math.cos(3.0 * (φ + φ0)); + M = b * F0 * (Ma - Mb + Mc - Md); // meridional arc + + } while ((N - N0 - M) >= 0.00001); // ie until < 0.01mm + + val cosφ = Math.cos(φ) + val sinφ = Math.sin(φ); + val ν = a * F0 / Math.sqrt(1.0 - (e2 * sinφ * sinφ)); // nu = transverse radius of curvature + val ρ = a * F0 * (1.0 - e2) / Math.pow(1.0 - (e2 * sinφ * sinφ), 1.5); // rho = meridional radius of curvature + val η2 = ν / ρ - 1.0; // eta = ? + + val tanφ = Math.tan(φ); + val tan2φ = tanφ * tanφ + val tan4φ = tan2φ*tan2φ + val tan6φ = tan4φ*tan2φ; + val secφ = 1.0 / cosφ; + val ν3 = ν * ν * ν + val ν5 = ν3*ν*ν + val ν7 = ν5*ν*ν + val VII = tanφ / (2.0 * ρ * ν) + val VIII = tanφ / (24.0 * ρ * ν3) * (5.0 + 3.0 * tan2φ + η2 - 9.0 * tan2φ * η2) + val IX = tanφ / (720.0 * ρ * ν5) * (61.0 + 90.0 * tan2φ + 45.0 * tan4φ) + val X = secφ / ν + val XI = secφ / (6.0 * ν3) * (ν / ρ + 2.0 * tan2φ) + val XII = secφ / (120.0 * ν5) * (5.0 + 28.0 * tan2φ + 24.0 * tan4φ); + val XIIA = secφ / (5040.0 * ν7) * (61.0 + 662.0 * tan2φ + 1320.0 * tan4φ + 720.0 * tan6φ); + + val dE = (E - E0) + val dE2 = dE*dE + val dE3 = dE2*dE + val dE4 = dE2*dE2 + val dE5 = dE3*dE2 + val dE6 = dE4*dE2 + val dE7 = dE5*dE2; + φ = φ - VII * dE2 + VIII * dE4 - IX * dE6 + val λ = λ0 + X * dE - XI * dE3 + XII * dE5 - XIIA * dE7 + + var point = LatLonDatum(φ.toDegrees(), λ.toDegrees(), LatLonDatum.OSGB36); + if (datum != LatLonDatum.OSGB36) point = point.convertDatum(datum); + + return point; +}; diff --git a/src/main/kotlin/uk/co/wedgetech/geodesy/Test.java b/src/main/kotlin/uk/co/wedgetech/geodesy/Test.java deleted file mode 100644 index 6fe511e..0000000 --- a/src/main/kotlin/uk/co/wedgetech/geodesy/Test.java +++ /dev/null @@ -1,4 +0,0 @@ -package uk.co.wedgetech.geodesy; - -public class Test { -} diff --git a/src/main/kotlin/uk/co/wedgetech/geodesy/Vector3d.kt b/src/main/kotlin/uk/co/wedgetech/geodesy/Vector3d.kt index f15cd9a..10e2462 100644 --- a/src/main/kotlin/uk/co/wedgetech/geodesy/Vector3d.kt +++ b/src/main/kotlin/uk/co/wedgetech/geodesy/Vector3d.kt @@ -104,7 +104,7 @@ data class Vector3d(val x: Double , val y : Double , val z : Double) { * * @returns {Vector3d} Negated vector. */ - var negate = Vector3d (-this.x, -this.y, -this.z) + val negate by lazy({Vector3d (-this.x, -this.y, -this.z) }) /** diff --git a/src/main/kotlin/uk/co/wedgetech/geodesy/mgrs.kt b/src/main/kotlin/uk/co/wedgetech/geodesy/mgrs.kt index 1f76745..b433d6b 100644 --- a/src/main/kotlin/uk/co/wedgetech/geodesy/mgrs.kt +++ b/src/main/kotlin/uk/co/wedgetech/geodesy/mgrs.kt @@ -4,10 +4,12 @@ /* www.movable-type.co.uk/scripts/latlong-utm-mgrs.html */ /* www.movable-type.co.uk/scripts/geodesy/docs/module-mgrs.html */ /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ +/* 'use strict'; if (typeof module!='undefined' && module.exports) var Utm = require('./utm.js'); // ≡ import Utm from 'utm.js' if (typeof module!='undefined' && module.exports) var LatLon = require('./latlon-ellipsoidal.js'); // ≡ import LatLon from 'latlon-ellipsoidal.js' +*/ /** @@ -25,19 +27,19 @@ if (typeof module!='undefined' && module.exports) var LatLon = require('./latlon /* * Latitude bands C..X 8° each, covering 80°S to 84°N */ -Mgrs.latBands = 'CDEFGHJKLMNPQRSTUVWXX'; // X is repeated for 80-84°N +//Mgrs.latBands = 'CDEFGHJKLMNPQRSTUVWXX'; // X is repeated for 80-84°N /* * 100km grid square column (‘e’) letters repeat every third zone */ -Mgrs.e100kLetters = [ 'ABCDEFGH', 'JKLMNPQR', 'STUVWXYZ' ]; +//Mgrs.e100kLetters = [ 'ABCDEFGH', 'JKLMNPQR', 'STUVWXYZ' ]; /* * 100km grid square row (‘n’) letters repeat every other zone */ -Mgrs.n100kLetters = [ 'ABCDEFGHJKLMNPQRSTUV', 'FGHJKLMNPQRSTUVABCDE' ]; +//Mgrs.n100kLetters = [ 'ABCDEFGHJKLMNPQRSTUV', 'FGHJKLMNPQRSTUVABCDE' ]; /** @@ -56,7 +58,7 @@ Mgrs.n100kLetters = [ 'ABCDEFGHJKLMNPQRSTUV', 'FGHJKLMNPQRSTUVABCDE' ]; * @example * var mgrsRef = new Mgrs(31, 'U', 'D', 'Q', 48251, 11932); // 31U DQ 48251 11932 */ -function Mgrs(zone, band, e100k, n100k, easting, northing, datum) { +/*function Mgrs(zone, band, e100k, n100k, easting, northing, datum) { // allow instantiation without 'new' if (!(this instanceof Mgrs)) return new Mgrs(zone, band, e100k, n100k, easting, northing, datum); @@ -75,7 +77,7 @@ function Mgrs(zone, band, e100k, n100k, easting, northing, datum) { this.easting = Number(easting); this.northing = Number(northing); this.datum = datum; -} +}*/ /** @@ -88,6 +90,7 @@ function Mgrs(zone, band, e100k, n100k, easting, northing, datum) { * var utmCoord = new Utm(31, 'N', 448251, 5411932); * var mgrsRef = utmCoord.toMgrs(); // 31U DQ 48251 11932 */ +/* Utm.prototype.toMgrs = function() { if (isNaN(this.zone + this.easting + this.northing)) throw new Error('Invalid UTM coordinate ‘'+this.toString()+'’'); @@ -117,6 +120,7 @@ Utm.prototype.toMgrs = function() { return new Mgrs(zone, band, e100k, n100k, easting, northing); }; +*/ /** @@ -127,6 +131,7 @@ Utm.prototype.toMgrs = function() { * @example * var utmCoord = Mgrs.parse('31U DQ 448251 11932').toUtm(); // 31 N 448251 5411932 */ +/* Mgrs.prototype.toUtm = function() { var zone = this.zone; var band = this.band; @@ -158,6 +163,7 @@ Mgrs.prototype.toUtm = function() { return new Utm(zone, hemisphere, e100kNum+easting, n2M+n100kNum+northing, this.datum); }; +*/ /** @@ -178,6 +184,7 @@ Mgrs.prototype.toUtm = function() { * var mgrsRef = Mgrs.parse('31UDQ4825111932'); * // mgrsRef: { zone:31, band:'U', e100k:'D', n100k:'Q', easting:48251, northing:11932 } */ +/* Mgrs.parse = function(mgrsGridRef) { mgrsGridRef = mgrsGridRef.trim(); @@ -211,6 +218,7 @@ Mgrs.parse = function(mgrsGridRef) { return new Mgrs(zone, band, e100k, n100k, e, n); }; +*/ /** @@ -231,6 +239,7 @@ Mgrs.parse = function(mgrsGridRef) { * @example * var mgrsStr = new Mgrs(31, 'U', 'D', 'Q', 48251, 11932).toString(); // '31U DQ 48251 11932' */ +/* Mgrs.prototype.toString = function(digits) { digits = (digits === undefined) ? 10 : Number(digits); if ([ 2,4,6,8,10 ].indexOf(digits) == -1) throw new Error('Invalid precision ‘'+digits+'’'); @@ -251,7 +260,5 @@ Mgrs.prototype.toString = function(digits) { return zone+band + ' ' + e100k+n100k + ' ' + easting + ' ' + northing; }; +*/ - -/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ -if (typeof module != 'undefined' && module.exports) module.exports = Mgrs; // ≡ export default Mgrs diff --git a/src/main/kotlin/uk/co/wedgetech/geodesy/utm.kt b/src/main/kotlin/uk/co/wedgetech/geodesy/utm.kt index 9c19362..e903280 100644 --- a/src/main/kotlin/uk/co/wedgetech/geodesy/utm.kt +++ b/src/main/kotlin/uk/co/wedgetech/geodesy/utm.kt @@ -33,7 +33,7 @@ * @example * var utmCoord = new Utm(31, 'N', 448251, 5411932); */ -function Utm(zone, hemisphere, easting, northing, datum, convergence, scale) { +/*function Utm(zone, hemisphere, easting, northing, datum, convergence, scale) { if (!(this instanceof Utm)) { // allow instantiation without 'new' return new Utm(zone, hemisphere, easting, northing, datum, convergence, scale); } @@ -55,7 +55,7 @@ function Utm(zone, hemisphere, easting, northing, datum, convergence, scale) { this.datum = datum; this.convergence = convergence===null ? null : Number(convergence); this.scale = scale===null ? null : Number(scale); -} +}*/ /** @@ -71,6 +71,7 @@ function Utm(zone, hemisphere, easting, northing, datum, convergence, scale) { * var latlong = new LatLon(48.8582, 2.2945); * var utmCoord = latlong.toUtm(); // utmCoord.toString(): '31 N 448252 5411933' */ +/* LatLon.prototype.toUtm = function() { if (isNaN(this.lat) || isNaN(this.lon)) throw new Error('Invalid point'); if (!(-80<=this.lat && this.lat<=84)) throw new Error('Outside UTM limits'); @@ -174,6 +175,7 @@ LatLon.prototype.toUtm = function() { return new Utm(zone, h, x, y, this.datum, convergence, scale); }; +*/ /** * Converts UTM zone/easting/northing coordinate to latitude/longitude @@ -185,6 +187,7 @@ LatLon.prototype.toUtm = function() { * var grid = new Utm(31, 'N', 448251.795, 5411932.678); * var latlong = grid.toLatLonE(); // latlong.toString(): 48°51′29.52″N, 002°17′40.20″E */ +/* Utm.prototype.toLatLonE = function() { var z = this.zone; var h = this.hemisphere; @@ -286,6 +289,7 @@ Utm.prototype.toLatLonE = function() { return latLong; }; +*/ /** @@ -306,6 +310,8 @@ Utm.prototype.toLatLonE = function() { * var utmCoord = Utm.parse('31 N 448251 5411932'); * // utmCoord: {zone: 31, hemisphere: 'N', easting: 448251, northing: 5411932 } */ +/* + Utm.parse = function(utmCoord, datum) { if (datum === undefined) datum = LatLon.datum.WGS84; // default if not supplied @@ -318,6 +324,7 @@ Utm.parse = function(utmCoord, datum) { return new Utm(zone, hemisphere, easting, northing, datum); }; +*/ /** @@ -334,6 +341,7 @@ Utm.parse = function(utmCoord, datum) { * @example * var utm = Utm.parse('31 N 448251 5411932').toString(4); // 31 N 448251.0000 5411932.0000 */ +/* Utm.prototype.toString = function(digits) { digits = Number(digits||0); // default 0 if not supplied @@ -346,43 +354,4 @@ Utm.prototype.toString = function(digits) { return z+' '+h+' '+e.toFixed(digits)+' '+n.toFixed(digits); }; - -/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ - -/** Polyfill Math.sinh for old browsers / IE */ -if (Math.sinh === undefined) { - Math.sinh = function(x) { - return (Math.exp(x) - Math.exp(-x)) / 2; - }; -} - -/** Polyfill Math.cosh for old browsers / IE */ -if (Math.cosh === undefined) { - Math.cosh = function(x) { - return (Math.exp(x) + Math.exp(-x)) / 2; - }; -} - -/** Polyfill Math.tanh for old browsers / IE */ -if (Math.tanh === undefined) { - Math.tanh = function(x) { - return (Math.exp(x) - Math.exp(-x)) / (Math.exp(x) + Math.exp(-x)); - }; -} - -/** Polyfill Math.asinh for old browsers / IE */ -if (Math.asinh === undefined) { - Math.asinh = function(x) { - return Math.log(x + Math.sqrt(1 + x*x)); - }; -} - -/** Polyfill Math.atanh for old browsers / IE */ -if (Math.atanh === undefined) { - Math.atanh = function(x) { - return Math.log((1+x) / (1-x)) / 2; - }; -} - -/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ -if (typeof module != 'undefined' && module.exports) module.exports = Utm; // ≡ export default Utm +*/ diff --git a/src/test/kotlin/uk/co/wedgetech/geodesy/DmsTest.kt b/src/test/kotlin/uk/co/wedgetech/geodesy/DmsTest.kt index 267f55f..4d8b34b 100644 --- a/src/test/kotlin/uk/co/wedgetech/geodesy/DmsTest.kt +++ b/src/test/kotlin/uk/co/wedgetech/geodesy/DmsTest.kt @@ -63,4 +63,26 @@ class DmsTest { assertEquals("037°X00′X00″", uk.co.wedgetech.geodesy.toDegreesMinutesSeconds(36.99999, 0, "X")) } -} \ No newline at end of file + + @Test + fun test_compassPoint() { + //TODO clean up + assertEquals("1 -> N ","N", compassPoint(1.0) ) + assertEquals("0 -> N ","N", compassPoint(0.0) ) + assertEquals("-1 -> N " ,"N", compassPoint(-1.0) ) + assertEquals("359 -> N " ,"N", compassPoint(359.0) ) + assertEquals("24 -> NNE ","NNE", compassPoint(24.0) ) + assertEquals("24:1 -> N ", compassPoint(24.0, 1) ,"N") + assertEquals("24:2 -> NE ", compassPoint(24.0, 2) ,"NE") + assertEquals("24:3 -> NNE ","NNE", compassPoint(24.0, 3) ) + assertEquals("226 -> SW ", compassPoint(226.0) ,"SW") + assertEquals("226:1 -> W ", compassPoint(226.0, 1) ,"W") + assertEquals("226:2 -> SW ", compassPoint(226.0, 2) ,"SW") + assertEquals("226:3 -> SW ", compassPoint(226.0, 3) ,"SW") + assertEquals("237 -> WSW ", compassPoint(237.0) ,"WSW") + assertEquals("237:1 -> W ", compassPoint(237.0, 1) ,"W") + assertEquals("237:2 -> SW ", compassPoint(237.0, 2) ,"SW") + assertEquals("237:3 -> WSW ", compassPoint(237.0, 3) ,"WSW") + } + + } \ No newline at end of file diff --git a/src/test/kotlin/uk/co/wedgetech/geodesy/OsGridRefTest.kt b/src/test/kotlin/uk/co/wedgetech/geodesy/OsGridRefTest.kt index 017cd7b..70bcd42 100644 --- a/src/test/kotlin/uk/co/wedgetech/geodesy/OsGridRefTest.kt +++ b/src/test/kotlin/uk/co/wedgetech/geodesy/OsGridRefTest.kt @@ -1,5 +1,6 @@ package uk.co.wedgetech.geodesy +import jdk.nashorn.internal.objects.NativeFunction.function import org.junit.Assert.assertEquals import org.junit.Test @@ -47,5 +48,52 @@ class OsGridRefTest { assertEquals( "000000,1200000", OsGridRef(0.0, 1200000.0 ).toString(0) ) } + @Test fun fromJSTest() { + var osgb = LatLonDatum("52°39′27.2531″N".parseDegreesMinutesSeconds(), "1°43′4.5177″E".parseDegreesMinutesSeconds(), LatLonDatum.OSGB36) + var gridref = osgb.toOsGrid() + assertEquals( "C1 E", 651409.903, gridref.easting.toFixed(3), 0.001) + assertEquals( "C1 N", 313177.270, gridref.northing.toFixed(3), 0.001) + + val osgb2 = gridref.toLatLonDatum(LatLonDatum.OSGB36); + assertEquals( "C1 round-trip", "52°39′27.2531″N, 001°43′04.5177″E", osgb2.toString("dms", 4)) + + gridref = OsGridRef(651409.903, 313177.270); + osgb = gridref.toLatLonDatum(LatLonDatum.OSGB36); + assertEquals( "C2", "52°39′27.2531″N, 001°43′04.5177″E", osgb2.toString("dms", 4)) + val gridref2 = osgb2.toOsGrid() + + assertEquals("parse 100km origin", "SU 00000 00000", "SU00".parseOsGridReference().toString()) + assertEquals("parse 100km origin", "SU 00000 00000", "SU 0 0".parseOsGridReference().toString()) + assertEquals("parse no whitespace", "SU 38700 14800", "SU387148".parseOsGridReference().toString()) + assertEquals("parse 6-digit", "SU 38700 14800", "SU 387 148".parseOsGridReference().toString()) + assertEquals("parse 10-digit", "SU 38700 14800", "SU 38700 14800".parseOsGridReference().toString()) + assertEquals("parse numeric", "SU 38700 14800", "438700,114800".parseOsGridReference().toString()) + + val greenwichWGS84 = LatLonDatum(51.4778, -0.0016); // default WGS84 + val greenwichOSGB36 = greenwichWGS84.convertDatum(LatLonDatum.OSGB36); + assertEquals("convert WGS84 -> OSGB36", "51.4773°N, 000.0000°E" , greenwichOSGB36.toString("d")) + assertEquals("convert OSGB36 -> WGS84", "51.4778°N, 000.0016°W", greenwichOSGB36.convertDatum(LatLonDatum.WGS84).toString("d")) + + // limits + assertEquals("SW regular", "SV 00000 00000", OsGridRef( 0.0, 0.0).toString()); + assertEquals("NE regular", "JM 99999 99999", OsGridRef( 699999.0, 1299999.0).toString()); + assertEquals("SW numeric", "000000,000000", OsGridRef( 0.0, 0.0).toString(0)); + assertEquals("NW numeric", "699999,1299999", OsGridRef( 699999.0, 1299999.0).toString(0)); + + // DG round-trip + + val dgGridRef: OsGridRef = "TQ 44359 80653".parseOsGridReference() + + // round-tripping OSGB36 works perfectly + val dgOsgb: LatLonDatum = dgGridRef.toLatLonDatum(LatLonDatum.OSGB36) + assertEquals("DG round-trip OSGB36", dgGridRef.toString(), dgOsgb.toOsGrid().toString()) + assertEquals("DG round-trip OSGB36 numeric", "544359,180653", dgOsgb.toOsGrid().toString(0)); + + // reversing Helmert transform (OSGB->WGS->OSGB) introduces small error (≈ 3mm in UK), so WGS84 + // round-trip is not quite perfect: test needs to incorporate 3mm error to pass + val dgWgs = dgGridRef.toLatLonDatum() + assertEquals("DG round-trip WGS84 numeric", "544358.997,180653", dgWgs.toOsGrid().toString(0)); + } + } \ No newline at end of file