// ================================================================== LatLng function LatLng(lat, lng) { this.lat = lat; this.lng = lng; this.distance = LatLngDistance; this.toOSRef = LatLngToOSRef; this.toUTMRef = LatLngToUTMRef; this.WGS84ToOSGB36 = WGS84ToOSGB36; this.OSGB36ToWGS84 = OSGB36ToWGS84; this.toString = LatLngToString; } function LatLngToString() { return "(" + this.lat + ", " + this.lng + ")"; } // =================================================================== OSRef // References given with OSRef are accurate to 1m. function OSRef(easting, northing) { this.easting = easting; this.northing = northing; this.toLatLng = OSRefToLatLng; this.toString = OSRefToString; this.toSixFigureString = OSRefToSixFigureString; } function OSRefToString() { return "(" + this.easting + ", " + this.northing + ")"; } function OSRefToSixFigureString() { var hundredkmE = Math.floor(this.easting / 100000); var hundredkmN = Math.floor(this.northing / 100000); var firstLetter = ""; if (hundredkmN < 5) { if (hundredkmE < 5) { firstLetter = "S"; } else { firstLetter = "T"; } } else if (hundredkmN < 10) { if (hundredkmE < 5) { firstLetter = "N"; } else { firstLetter = "O"; } } else { firstLetter = "H"; } var secondLetter = ""; var index = 65 + ((4 - (hundredkmN % 5)) * 5) + (hundredkmE % 5); var ti = index; if (index >= 73) index++; secondLetter = chr(index); var e = Math.floor((this.easting - (100000 * hundredkmE)) / 100); var n = Math.floor((this.northing - (100000 * hundredkmN)) / 100); var es = e; if (e < 100) es = "0" + es; if (e < 10) es = "0" + es; var ns = n; if (n < 100) ns = "0" + ns; if (n < 10) ns = "0" + ns; return firstLetter + secondLetter + es + ns; } // ================================================================== UTMRef function UTMRef(easting, northing, latZone, lngZone) { this.easting = easting; this.northing = northing; this.latZone = latZone; this.lngZone = lngZone; this.toLatLng = UTMRefToLatLng; this.toString = UTMRefToString; } function UTMRefToString() { return this.lngZone + this.latZone + " " + this.easting + " " + this.northing; } // ================================================================== RefEll function RefEll(maj, min) { this.maj = maj; this.min = min; this.ecc = ((maj * maj) - (min * min)) / (maj * maj); } // ================================================== Mathematical Functions function sinSquared(x) { return Math.sin(x) * Math.sin(x); } function cosSquared(x) { return Math.cos(x) * Math.cos(x); } function tanSquared(x) { return Math.tan(x) * Math.tan(x); } function sec(x) { return 1.0 / Math.cos(x); } function deg2rad(x) { return x * (Math.PI / 180); } function rad2deg(x) { return x * (180 / Math.PI); } function chr(x) { var h = x.toString (16); if (h.length == 1) h = "0" + h; h = "%" + h; return unescape (h); } function ord(x) { var c = x.charAt(0); var i; for (i = 0; i < 256; ++ i) { var h = i.toString (16); if (h.length == 1) h = "0" + h; h = "%" + h; h = unescape (h); if (h == c) break; } return i; } // ========================================================= Other Functions function LatLngDistance(to) { var er = 6366.707; var latFrom = deg2rad(this.lat); var latTo = deg2rad(to.lat); var lngFrom = deg2rad(this.lng); var lngTo = deg2rad(to.lng); var x1 = er * Math.cos(lngFrom) * Math.sin(latFrom); var y1 = er * Math.sin(lngFrom) * Math.sin(latFrom); var z1 = er * Math.cos(latFrom); var x2 = er * Math.cos(lngTo) * Math.sin(latTo); var y2 = er * Math.sin(lngTo) * Math.sin(latTo); var z2 = er * Math.cos(latTo); var d = Math.sqrt((x1-x2)*(x1-x2)+(y1-y2)*(y1-y2)+(z1-z2)*(z1-z2)); return d; } // ==================================================== Conversion Functions function OSGB36ToWGS84() { var airy1830 = new RefEll(6377563.396, 6356256.909); var a = airy1830.maj; var b = airy1830.min; var eSquared = airy1830.ecc; var phi = deg2rad(this.lat); var lambda = deg2rad(this.lng); var v = a / (Math.sqrt(1 - eSquared * sinSquared(phi))); var H = 0; // height var x = (v + H) * Math.cos(phi) * Math.cos(lambda); var y = (v + H) * Math.cos(phi) * Math.sin(lambda); var z = ((1 - eSquared) * v + H) * Math.sin(phi); var tx = 446.448; var ty = -124.157; var tz = 542.060; var s = -0.0000204894; var rx = deg2rad( 0.00004172222); var ry = deg2rad( 0.00006861111); var rz = deg2rad( 0.00023391666); var xB = tx + (x * (1 + s)) + (-rx * y) + (ry * z); var yB = ty + (rz * x) + (y * (1 + s)) + (-rx * z); var zB = tz + (-ry * x) + (rx * y) + (z * (1 + s)); var wgs84 = new RefEll(6378137.000, 6356752.3141); a = wgs84.maj; b = wgs84.min; eSquared = wgs84.ecc; var lambdaB = rad2deg(Math.atan(yB / xB)); var p = Math.sqrt((xB * xB) + (yB * yB)); var phiN = Math.atan(zB / (p * (1 - eSquared))); for (var i = 1; i < 10; i++) { v = a / (Math.sqrt(1 - eSquared * sinSquared(phiN))); phiN1 = Math.atan((zB + (eSquared * v * Math.sin(phiN))) / p); phiN = phiN1; } var phiB = rad2deg(phiN); this.lat = phiB; this.lng = lambdaB; } function WGS84ToOSGB36() { var wgs84 = new RefEll(6378137.000, 6356752.3141); var a = wgs84.maj; var b = wgs84.min; var eSquared = wgs84.ecc; var phi = deg2rad(this.lat); var lambda = deg2rad(this.lng); var v = a / (Math.sqrt(1 - eSquared * sinSquared(phi))); var H = 0; // height var x = (v + H) * Math.cos(phi) * Math.cos(lambda); var y = (v + H) * Math.cos(phi) * Math.sin(lambda); var z = ((1 - eSquared) * v + H) * Math.sin(phi); var tx = -446.448; var ty = 124.157; var tz = -542.060; var s = 0.0000204894; var rx = deg2rad(-0.00004172222); var ry = deg2rad(-0.00006861111); var rz = deg2rad(-0.00023391666); var xB = tx + (x * (1 + s)) + (-rx * y) + (ry * z); var yB = ty + (rz * x) + (y * (1 + s)) + (-rx * z); var zB = tz + (-ry * x) + (rx * y) + (z * (1 + s)); var airy1830 = new RefEll(6377563.396, 6356256.909); a = airy1830.maj; b = airy1830.min; eSquared = airy1830.ecc; var lambdaB = rad2deg(Math.atan(yB / xB)); var p = Math.sqrt((xB * xB) + (yB * yB)); var phiN = Math.atan(zB / (p * (1 - eSquared))); for (var i = 1; i < 10; i++) { v = a / (Math.sqrt(1 - eSquared * sinSquared(phiN))); phiN1 = Math.atan((zB + (eSquared * v * Math.sin(phiN))) / p); phiN = phiN1; } var phiB = rad2deg(phiN); this.lat = phiB; this.lng = lambdaB; } function OSRefToLatLng() { var airy1830 = new RefEll(6377563.396, 6356256.909); var OSGB_F0 = 0.9996012717; var N0 = -100000.0; var E0 = 400000.0; var phi0 = deg2rad(49.0); var lambda0 = deg2rad(-2.0); var a = airy1830.maj; var b = airy1830.min; var eSquared = airy1830.ecc; var phi = 0.0; var lambda = 0.0; var E = this.easting; var N = this.northing; var n = (a - b) / (a + b); var M = 0.0; var phiPrime = ((N - N0) / (a * OSGB_F0)) + phi0; do { M = (b * OSGB_F0) * (((1 + n + ((5.0 / 4.0) * n * n) + ((5.0 / 4.0) * n * n * n)) * (phiPrime - phi0)) - (((3 * n) + (3 * n * n) + ((21.0 / 8.0) * n * n * n)) * Math.sin(phiPrime - phi0) * Math.cos(phiPrime + phi0)) + ((((15.0 / 8.0) * n * n) + ((15.0 / 8.0) * n * n * n)) * Math.sin(2.0 * (phiPrime - phi0)) * Math.cos(2.0 * (phiPrime + phi0))) - (((35.0 / 24.0) * n * n * n) * Math.sin(3.0 * (phiPrime - phi0)) * Math.cos(3.0 * (phiPrime + phi0)))); phiPrime += (N - N0 - M) / (a * OSGB_F0); } while ((N - N0 - M) >= 0.001); var v = a * OSGB_F0 * Math.pow(1.0 - eSquared * sinSquared(phiPrime), -0.5); var rho = a * OSGB_F0 * (1.0 - eSquared) * Math.pow(1.0 - eSquared * sinSquared(phiPrime), -1.5); var etaSquared = (v / rho) - 1.0; var VII = Math.tan(phiPrime) / (2 * rho * v); var VIII = (Math.tan(phiPrime) / (24.0 * rho * Math.pow(v, 3.0))) * (5.0 + (3.0 * tanSquared(phiPrime)) + etaSquared - (9.0 * tanSquared(phiPrime) * etaSquared)); var IX = (Math.tan(phiPrime) / (720.0 * rho * Math.pow(v, 5.0))) * (61.0 + (90.0 * tanSquared(phiPrime)) + (45.0 * tanSquared(phiPrime) * tanSquared(phiPrime))); var X = sec(phiPrime) / v; var XI = (sec(phiPrime) / (6.0 * v * v * v)) * ((v / rho) + (2 * tanSquared(phiPrime))); var XII = (sec(phiPrime) / (120.0 * Math.pow(v, 5.0))) * (5.0 + (28.0 * tanSquared(phiPrime)) + (24.0 * tanSquared(phiPrime) * tanSquared(phiPrime))); var XIIA = (sec(phiPrime) / (5040.0 * Math.pow(v, 7.0))) * (61.0 + (662.0 * tanSquared(phiPrime)) + (1320.0 * tanSquared(phiPrime) * tanSquared(phiPrime)) + (720.0 * tanSquared(phiPrime) * tanSquared(phiPrime) * tanSquared(phiPrime))); phi = phiPrime - (VII * Math.pow(E - E0, 2.0)) + (VIII * Math.pow(E - E0, 4.0)) - (IX * Math.pow(E - E0, 6.0)); lambda = lambda0 + (X * (E - E0)) - (XI * Math.pow(E - E0, 3.0)) + (XII * Math.pow(E - E0, 5.0)) - (XIIA * Math.pow(E - E0, 7.0)); return new LatLng(rad2deg(phi), rad2deg(lambda)); } /** * Convert a latitude and longitude into an OSGB grid reference * * @param latitudeLongitude the latitude and longitude to convert * @return the OSGB grid reference * @since 0.1 */ function LatLngToOSRef() { var airy1830 = new RefEll(6377563.396, 6356256.909); var OSGB_F0 = 0.9996012717; var N0 = -100000.0; var E0 = 400000.0; var phi0 = deg2rad(49.0); var lambda0 = deg2rad(-2.0); var a = airy1830.maj; var b = airy1830.min; var eSquared = airy1830.ecc; var phi = deg2rad(this.lat); var lambda = deg2rad(this.lng); var E = 0.0; var N = 0.0; var n = (a - b) / (a + b); var v = a * OSGB_F0 * Math.pow(1.0 - eSquared * sinSquared(phi), -0.5); var rho = a * OSGB_F0 * (1.0 - eSquared) * Math.pow(1.0 - eSquared * sinSquared(phi), -1.5); var etaSquared = (v / rho) - 1.0; var M = (b * OSGB_F0) * (((1 + n + ((5.0 / 4.0) * n * n) + ((5.0 / 4.0) * n * n * n)) * (phi - phi0)) - (((3 * n) + (3 * n * n) + ((21.0 / 8.0) * n * n * n)) * Math.sin(phi - phi0) * Math.cos(phi + phi0)) + ((((15.0 / 8.0) * n * n) + ((15.0 / 8.0) * n * n * n)) * Math.sin(2.0 * (phi - phi0)) * Math.cos(2.0 * (phi + phi0))) - (((35.0 / 24.0) * n * n * n) * Math.sin(3.0 * (phi - phi0)) * Math.cos(3.0 * (phi + phi0)))); var I = M + N0; var II = (v / 2.0) * Math.sin(phi) * Math.cos(phi); var III = (v / 24.0) * Math.sin(phi) * Math.pow(Math.cos(phi), 3.0) * (5.0 - tanSquared(phi) + (9.0 * etaSquared)); var IIIA = (v / 720.0) * Math.sin(phi) * Math.pow(Math.cos(phi), 5.0) * (61.0 - (58.0 * tanSquared(phi)) + Math.pow(Math.tan(phi), 4.0)); var IV = v * Math.cos(phi); var V = (v / 6.0) * Math.pow(Math.cos(phi), 3.0) * ((v / rho) - tanSquared(phi)); var VI = (v / 120.0) * Math.pow(Math.cos(phi), 5.0) * (5.0 - (18.0 * tanSquared(phi)) + (Math.pow(Math.tan(phi), 4.0)) + (14 * etaSquared) - (58 * tanSquared(phi) * etaSquared)); N = I + (II * Math.pow(lambda - lambda0, 2.0)) + (III * Math.pow(lambda - lambda0, 4.0)) + (IIIA * Math.pow(lambda - lambda0, 6.0)); E = E0 + (IV * (lambda - lambda0)) + (V * Math.pow(lambda - lambda0, 3.0)) + (VI * Math.pow(lambda - lambda0, 5.0)); return new OSRef(E, N); } /** * Convert an UTM reference to a latitude and longitude * * @param ellipsoid A reference ellipsoid to use * @param utm the UTM reference to convert * @return the converted latitude and longitude * @since 0.2 */ function UTMRefToLatLng() { var wgs84 = new RefEll(6378137, 6356752.314); var UTM_F0 = 0.9996; var a = wgs84.maj; var eSquared = wgs84.ecc; var ePrimeSquared = eSquared / (1.0 - eSquared); var e1 = (1 - Math.sqrt(1 - eSquared)) / (1 + Math.sqrt(1 - eSquared)); var x = this.easting - 500000.0;; var y = this.northing; var zoneNumber = this.lngZone; var zoneLetter = this.latZone; var longitudeOrigin = (zoneNumber - 1.0) * 6.0 - 180.0 + 3.0; // Correct y for southern hemisphere if ((ord(zoneLetter) - ord("N")) < 0) { y -= 10000000.0; } var m = y / UTM_F0; var mu = m / (a * (1.0 - eSquared / 4.0 - 3.0 * eSquared * eSquared / 64.0 - 5.0 * Math.pow(eSquared, 3.0) / 256.0)); var phi1Rad = mu + (3.0 * e1 / 2.0 - 27.0 * Math.pow(e1, 3.0) / 32.0) * Math.sin(2.0 * mu) + (21.0 * e1 * e1 / 16.0 - 55.0 * Math.pow(e1, 4.0) / 32.0) * Math.sin(4.0 * mu) + (151.0 * Math.pow(e1, 3.0) / 96.0) * Math.sin(6.0 * mu); var n = a / Math.sqrt(1.0 - eSquared * Math.sin(phi1Rad) * Math.sin(phi1Rad)); var t = Math.tan(phi1Rad) * Math.tan(phi1Rad); var c = ePrimeSquared * Math.cos(phi1Rad) * Math.cos(phi1Rad); var r = a * (1.0 - eSquared) / Math.pow( 1.0 - eSquared * Math.sin(phi1Rad) * Math.sin(phi1Rad), 1.5); var d = x / (n * UTM_F0); var latitude = ( phi1Rad - (n * Math.tan(phi1Rad) / r) * (d * d / 2.0 - (5.0 + (3.0 * t) + (10.0 * c) - (4.0 * c * c) - (9.0 * ePrimeSquared)) * Math.pow(d, 4.0) / 24.0 + (61.0 + (90.0 * t) + (298.0 * c) + (45.0 * t * t) - (252.0 * ePrimeSquared) - (3.0 * c * c)) * Math.pow(d, 6.0) / 720.0)) * (180.0 / Math.PI); var longitude = longitudeOrigin + ( (d - (1.0 + 2.0 * t + c) * Math.pow(d, 3.0) / 6.0 + (5.0 - (2.0 * c) + (28.0 * t) - (3.0 * c * c) + (8.0 * ePrimeSquared) + (24.0 * t * t)) * Math.pow(d, 5.0) / 120.0) / Math.cos(phi1Rad)) * (180.0 / Math.PI); return new LatLng(latitude, longitude); } /** * Convert a latitude and longitude to an UTM reference * * @param ellipsoid A reference ellipsoid to use * @param latitudeLongitude The latitude and longitude to convert * @return the converted UTM reference * @since 0.2 */ function LatLngToUTMRef() { var wgs84 = new RefEll(6378137, 6356752.314); var UTM_F0 = 0.9996; var a = wgs84.maj; var eSquared = wgs84.ecc; var longitude = this.lng; var latitude = this.lat; var latitudeRad = latitude * (Math.PI / 180.0); var longitudeRad = longitude * (Math.PI / 180.0); var longitudeZone = Math.floor((longitude + 180.0) / 6.0) + 1; // Special zone for Norway if (latitude >= 56.0 && latitude < 64.0 && longitude >= 3.0 && longitude < 12.0) { longitudeZone = 32; } // Special zones for Svalbard if (latitude >= 72.0 && latitude < 84.0) { if (longitude >= 0.0 && longitude < 9.0) { longitudeZone = 31; } else if (longitude >= 9.0 && longitude < 21.0) { longitudeZone = 33; } else if (longitude >= 21.0 && longitude < 33.0) { longitudeZone = 35; } else if (longitude >= 33.0 && longitude < 42.0) { longitudeZone = 37; } } var longitudeOrigin = (longitudeZone - 1) * 6 - 180 + 3; var longitudeOriginRad = longitudeOrigin * (Math.PI / 180.0); var UTMZone = getUTMLatitudeZoneLetter(latitude); ePrimeSquared = (eSquared) / (1 - eSquared); var n = a / Math.sqrt(1 - eSquared * Math.sin(latitudeRad) * Math.sin(latitudeRad)); var t = Math.tan(latitudeRad) * Math.tan(latitudeRad); var c = ePrimeSquared * Math.cos(latitudeRad) * Math.cos(latitudeRad); var A = Math.cos(latitudeRad) * (longitudeRad - longitudeOriginRad); var M = a * ((1 - eSquared / 4 - 3 * eSquared * eSquared / 64 - 5 * eSquared * eSquared * eSquared / 256) * latitudeRad - (3 * eSquared / 8 + 3 * eSquared * eSquared / 32 + 45 * eSquared * eSquared * eSquared / 1024) * Math.sin(2 * latitudeRad) + (15 * eSquared * eSquared / 256 + 45 * eSquared * eSquared * eSquared / 1024) * Math.sin(4 * latitudeRad) - (35 * eSquared * eSquared * eSquared / 3072) * Math.sin(6 * latitudeRad)); var UTMEasting = (UTM_F0 * n * (A + (1 - t + c) * Math.pow(A, 3.0) / 6 + (5 - 18 * t + t * t + 72 * c - 58 * ePrimeSquared) * Math.pow(A, 5.0) / 120) + 500000.0); var UTMNorthing = (UTM_F0 * (M + n * Math.tan(latitudeRad) * (A * A / 2 + (5 - t + (9 * c) + (4 * c * c)) * Math.pow(A, 4.0) / 24 + (61 - (58 * t) + (t * t) + (600 * c) - (330 * ePrimeSquared)) * Math.pow(A, 6.0) / 720))); // Adjust for the southern hemisphere if (latitude < 0) { UTMNorthing += 10000000.0; } return new UTMRef(UTMEasting, UTMNorthing, UTMZone, longitudeZone); } /** * Take a string formatted as a six-figure OS grid reference (e.g. * "TG514131") and return a reference to an OSRef object that represents * that grid reference. The first character must be H, N, S, O or T. * The second character can be any uppercase character from A through Z * excluding I. * * @param ref * @return * @since 1.1 */ function getOSRefFromSixFigureReference(ref) { var char1 = ref.substring(0, 1); var char2 = ref.substring(1, 2); // Thanks to Nick Holloway for pointing out the radix bug here var east = parseInt(ref.substring(2, 5), 10) * 100; var north = parseInt(ref.substring(5, 8), 10) * 100; if (char1 == 'H') { north += 1000000; } else if (char1 == 'N') { north += 500000; } else if (char1 == 'O') { north += 500000; east += 500000; } else if (char1 == 'T') { east += 500000; } var char2ord = ord(char2); if (char2ord > 73) char2ord--; // Adjust for no I var nx = ((char2ord - 65) % 5) * 100000; var ny = (4 - Math.floor((char2ord - 65) / 5)) * 100000; return new OSRef(east + nx, north + ny); } /** * Work out the UTM latitude zone from the latitude * * @param latitude * @return * @since 0.2 */ function getUTMLatitudeZoneLetter(latitude) { if ((84 >= latitude) && (latitude >= 72)) return "X"; else if (( 72 > latitude) && (latitude >= 64)) return "W"; else if (( 64 > latitude) && (latitude >= 56)) return "V"; else if (( 56 > latitude) && (latitude >= 48)) return "U"; else if (( 48 > latitude) && (latitude >= 40)) return "T"; else if (( 40 > latitude) && (latitude >= 32)) return "S"; else if (( 32 > latitude) && (latitude >= 24)) return "R"; else if (( 24 > latitude) && (latitude >= 16)) return "Q"; else if (( 16 > latitude) && (latitude >= 8)) return "P"; else if (( 8 > latitude) && (latitude >= 0)) return "N"; else if (( 0 > latitude) && (latitude >= -8)) return "M"; else if (( -8 > latitude) && (latitude >= -16)) return "L"; else if ((-16 > latitude) && (latitude >= -24)) return "K"; else if ((-24 > latitude) && (latitude >= -32)) return "J"; else if ((-32 > latitude) && (latitude >= -40)) return "H"; else if ((-40 > latitude) && (latitude >= -48)) return "G"; else if ((-48 > latitude) && (latitude >= -56)) return "F"; else if ((-56 > latitude) && (latitude >= -64)) return "E"; else if ((-64 > latitude) && (latitude >= -72)) return "D"; else if ((-72 > latitude) && (latitude >= -80)) return "C"; else return 'Z'; }