package uk.me.jstott.jcoord; /** * Class to represent a latitude/longitude pair. * * (c) 2006 Jonathan Stott * * Created on 11-02-2006 * * @author Jonathan Stott * @version 1.0 * @since 1.0 */ public class LatLng { /** * Latitude in degrees. */ private double lat; /** * Longitude in degrees. */ private double lng; /** * Create a new LatLng object to represent a latitude/longitude pair. * * @param lat * the latitude in degrees * @param lng * the longitude in degrees * @since 1.0 */ public LatLng(double lat, double lng) { this.lat = lat; this.lng = lng; } /** * Get a String representation of this LatLng object. * * @return a String representation of this LatLng object. * @since 1.0 */ public String toString() { return "(" + this.lat + ", " + this.lng + ")"; } /** * Convert this latitude and longitude into an OSGB (Ordnance Survey of Great * Britain) grid reference. * * @return the converted OSGB grid reference * @since 1.0 */ public OSRef toOSRef() { RefEll airy1830 = new RefEll(6377563.396, 6356256.909); double OSGB_F0 = 0.9996012717; double N0 = -100000.0; double E0 = 400000.0; double phi0 = Math.toRadians(49.0); double lambda0 = Math.toRadians(-2.0); double a = airy1830.getMaj(); double b = airy1830.getMin(); double eSquared = airy1830.getEcc(); double phi = Math.toRadians(getLat()); double lambda = Math.toRadians(getLng()); double E = 0.0; double N = 0.0; double n = (a - b) / (a + b); double v = a * OSGB_F0 * Math.pow(1.0 - eSquared * Util.sinSquared(phi), -0.5); double rho = a * OSGB_F0 * (1.0 - eSquared) * Math.pow(1.0 - eSquared * Util.sinSquared(phi), -1.5); double etaSquared = (v / rho) - 1.0; double 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)))); double I = M + N0; double II = (v / 2.0) * Math.sin(phi) * Math.cos(phi); double III = (v / 24.0) * Math.sin(phi) * Math.pow(Math.cos(phi), 3.0) * (5.0 - Util.tanSquared(phi) + (9.0 * etaSquared)); double IIIA = (v / 720.0) * Math.sin(phi) * Math.pow(Math.cos(phi), 5.0) * (61.0 - (58.0 * Util.tanSquared(phi)) + Math.pow(Math.tan(phi), 4.0)); double IV = v * Math.cos(phi); double V = (v / 6.0) * Math.pow(Math.cos(phi), 3.0) * ((v / rho) - Util.tanSquared(phi)); double VI = (v / 120.0) * Math.pow(Math.cos(phi), 5.0) * (5.0 - (18.0 * Util.tanSquared(phi)) + (Math.pow(Math.tan(phi), 4.0)) + (14 * etaSquared) - (58 * Util .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 this latitude and longitude to a UTM reference. * * @return the converted UTM reference * @since 1.0 */ public UTMRef toUTMRef() { double UTM_F0 = 0.9996; double a = RefEll.WGS84.getMaj(); double eSquared = RefEll.WGS84.getEcc(); double longitude = this.lng; double latitude = this.lat; double latitudeRad = latitude * (Math.PI / 180.0); double longitudeRad = longitude * (Math.PI / 180.0); int longitudeZone = (int) 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; } } double longitudeOrigin = (longitudeZone - 1) * 6 - 180 + 3; double longitudeOriginRad = longitudeOrigin * (Math.PI / 180.0); char UTMZone = UTMRef.getUTMLatitudeZoneLetter(latitude); double ePrimeSquared = (eSquared) / (1 - eSquared); double n = a / Math.sqrt(1 - eSquared * Math.sin(latitudeRad) * Math.sin(latitudeRad)); double t = Math.tan(latitudeRad) * Math.tan(latitudeRad); double c = ePrimeSquared * Math.cos(latitudeRad) * Math.cos(latitudeRad); double A = Math.cos(latitudeRad) * (longitudeRad - longitudeOriginRad); double 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)); double 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); double 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); } /** * Convert this LatLng from the OSGB36 datum to the WGS84 datum using an * approximate Helmert transformation. * * @since 1.0 */ public void toWGS84() { double a = RefEll.AIRY_1830.getMaj(); double eSquared = RefEll.AIRY_1830.getEcc(); double phi = Math.toRadians(lat); double lambda = Math.toRadians(lng); double v = a / (Math.sqrt(1 - eSquared * Util.sinSquared(phi))); double H = 0; // height double x = (v + H) * Math.cos(phi) * Math.cos(lambda); double y = (v + H) * Math.cos(phi) * Math.sin(lambda); double z = ((1 - eSquared) * v + H) * Math.sin(phi); double tx = 446.448; double ty = -124.157; double tz = 542.060; double s = -0.0000204894; double rx = Math.toRadians(0.00004172222); double ry = Math.toRadians(0.00006861111); double rz = Math.toRadians(0.00023391666); double xB = tx + (x * (1 + s)) + (-rx * y) + (ry * z); double yB = ty + (rz * x) + (y * (1 + s)) + (-rx * z); double zB = tz + (-ry * x) + (rx * y) + (z * (1 + s)); a = RefEll.WGS84.getMaj(); eSquared = RefEll.WGS84.getEcc(); double lambdaB = Math.toDegrees(Math.atan(yB / xB)); double p = Math.sqrt((xB * xB) + (yB * yB)); double phiN = Math.atan(zB / (p * (1 - eSquared))); for (int i = 1; i < 10; i++) { v = a / (Math.sqrt(1 - eSquared * Util.sinSquared(phiN))); double phiN1 = Math.atan((zB + (eSquared * v * Math.sin(phiN))) / p); phiN = phiN1; } double phiB = Math.toDegrees(phiN); lat = phiB; lng = lambdaB; } /** * Convert this LatLng from the WGS84 datum to the OSGB36 datum using an * approximate Helmert transformation. * * @since 1.0 */ public void toOSGB36() { RefEll wgs84 = new RefEll(6378137.000, 6356752.3141); double a = wgs84.getMaj(); double eSquared = wgs84.getEcc(); double phi = Math.toRadians(this.lat); double lambda = Math.toRadians(this.lng); double v = a / (Math.sqrt(1 - eSquared * Util.sinSquared(phi))); double H = 0; // height double x = (v + H) * Math.cos(phi) * Math.cos(lambda); double y = (v + H) * Math.cos(phi) * Math.sin(lambda); double z = ((1 - eSquared) * v + H) * Math.sin(phi); double tx = -446.448; double ty = 124.157; double tz = -542.060; double s = 0.0000204894; double rx = Math.toRadians(-0.00004172222); double ry = Math.toRadians(-0.00006861111); double rz = Math.toRadians(-0.00023391666); double xB = tx + (x * (1 + s)) + (-rx * y) + (ry * z); double yB = ty + (rz * x) + (y * (1 + s)) + (-rx * z); double zB = tz + (-ry * x) + (rx * y) + (z * (1 + s)); RefEll airy1830 = new RefEll(6377563.396, 6356256.909); a = airy1830.getMaj(); eSquared = airy1830.getEcc(); double lambdaB = Math.toDegrees(Math.atan(yB / xB)); double p = Math.sqrt((xB * xB) + (yB * yB)); double phiN = Math.atan(zB / (p * (1 - eSquared))); for (int i = 1; i < 10; i++) { v = a / (Math.sqrt(1 - eSquared * Util.sinSquared(phiN))); double phiN1 = Math.atan((zB + (eSquared * v * Math.sin(phiN))) / p); phiN = phiN1; } double phiB = Math.toDegrees(phiN); lat = phiB; lng = lambdaB; } /** * Calculate the surface distance in kilometres from the this LatLng to the * given LatLng. * * @param ll * @return the surface distance in km * @since 1.0 */ public double distance(LatLng ll) { double er = 6366.707; double latFrom = Math.toRadians(getLat()); double latTo = Math.toRadians(ll.getLat()); double lngFrom = Math.toRadians(getLng()); double lngTo = Math.toRadians(ll.getLng()); double d = Math.acos(Math.sin(latFrom) * Math.sin(latTo) + Math.cos(latFrom) * Math.cos(latTo) * Math.cos(lngTo - lngFrom)) * er; return d; } /** * Return the latitude in degrees. * * @return the latitude in degrees * @since 1.0 */ public double getLat() { return lat; } /** * Return the longitude in degrees. * * @return the longitude in degrees * @since 1.0 */ public double getLng() { return lng; } }