/* Copyright (C) 2001, 2008 United States Government as represented by the Administrator of the National Aeronautics and Space Administration. All Rights Reserved. */ package gov.nasa.worldwind.geom.coords; /** * Converter used to translate Transverse Mercator coordinates to and from geodetic latitude and longitude. * * @author Patrick Murris * @version $Id$ * @see TMCoord, UTMCoordConverter, MGRSCoordConverter */ /** * Ported to Java from the NGA GeoTrans code tranmerc.c and tranmerc.h * * @author Garrett Headley, Patrick Murris */ class TMCoordConverter { public final static int TRANMERC_NO_ERROR = 0x0000; private final static int TRANMERC_LAT_ERROR = 0x0001; private final static int TRANMERC_LON_ERROR = 0x0002; public final static int TRANMERC_EASTING_ERROR = 0x0004; public final static int TRANMERC_NORTHING_ERROR = 0x0008; private final static int TRANMERC_ORIGIN_LAT_ERROR = 0x0010; private final static int TRANMERC_CENT_MER_ERROR = 0x0020; private final static int TRANMERC_A_ERROR = 0x0040; private final static int TRANMERC_INV_F_ERROR = 0x0080; private final static int TRANMERC_SCALE_FACTOR_ERROR = 0x0100; public final static int TRANMERC_LON_WARNING = 0x0200; private final static double PI = 3.14159265358979323; /* PI */ public final static double PI_OVER = (PI / 2.0); /* PI over 2 */ private final static double MAX_LAT = ((PI * 89.99) / 180.0); /* 90 degrees in radians */ private final static double MAX_DELTA_LONG = ((PI * 90) / 180.0); /* 90 degrees in radians */ private final static double MIN_SCALE_FACTOR = 0.3; private final static double MAX_SCALE_FACTOR = 3.0; /* Ellipsoid Parameters, default to WGS 84 */ private double TranMerc_a = 6378137.0; /* Semi-major axis of ellipsoid i meters */ private double TranMerc_f = 1 / 298.257223563; /* Flattening of ellipsoid */ private double TranMerc_es = 0.0066943799901413800; /* Eccentricity (0.08181919084262188000) squared */ private double TranMerc_ebs = 0.0067394967565869; /* Second Eccentricity squared */ /* Transverse_Mercator projection Parameters */ private double TranMerc_Origin_Lat = 0.0; /* Latitude of origin in radians */ private double TranMerc_Origin_Long = 0.0; /* Longitude of origin in radians */ private double TranMerc_False_Northing = 0.0; /* False northing in meters */ private double TranMerc_False_Easting = 0.0; /* False easting in meters */ private double TranMerc_Scale_Factor = 1.0; /* Scale factor */ /* Isometeric to geodetic latitude parameters, default to WGS 84 */ private double TranMerc_ap = 6367449.1458008; private double TranMerc_bp = 16038.508696861; private double TranMerc_cp = 16.832613334334; private double TranMerc_dp = 0.021984404273757; private double TranMerc_ep = 3.1148371319283e-005; /* Maximum variance for easting and northing values for WGS 84. */ private double TranMerc_Delta_Easting = 40000000.0; private double TranMerc_Delta_Northing = 40000000.0; private double Easting; private double Northing; private double Longitude; private double Latitude; TMCoordConverter() { } public double getA() {return TranMerc_a;} public double getF() {return TranMerc_f;} /** * The function Set_Tranverse_Mercator_Parameters receives the ellipsoid parameters and Tranverse Mercator * projection parameters as inputs, and sets the corresponding state variables. If any errors occur, the error * code(s) are returned by the function, otherwise TRANMERC_NO_ERROR is returned. * * @param a Semi-major axis of ellipsoid, in meters * @param f Flattening of ellipsoid * @param Origin_Latitude Latitude in radians at the origin of the projection * @param Central_Meridian Longitude in radians at the center of the projection * @param False_Easting Easting/X at the center of the projection * @param False_Northing Northing/Y at the center of the projection * @param Scale_Factor Projection scale factor * @return error code */ public long setTransverseMercatorParameters(double a, double f, double Origin_Latitude, double Central_Meridian, double False_Easting, double False_Northing, double Scale_Factor) { double tn; /* True Meridianal distance constant */ double tn2; double tn3; double tn4; double tn5; double TranMerc_b; /* Semi-minor axis of ellipsoid, in meters */ double inv_f = 1 / f; long Error_Code = TRANMERC_NO_ERROR; if (a <= 0.0) { /* Semi-major axis must be greater than zero */ Error_Code |= TRANMERC_A_ERROR; } if ((inv_f < 250) || (inv_f > 350)) { /* Inverse flattening must be between 250 and 350 */ Error_Code |= TRANMERC_INV_F_ERROR; } if ((Origin_Latitude < -MAX_LAT) || (Origin_Latitude > MAX_LAT)) { /* origin latitude out of range */ Error_Code |= TRANMERC_ORIGIN_LAT_ERROR; } if ((Central_Meridian < -PI) || (Central_Meridian > (2 * PI))) { /* origin longitude out of range */ Error_Code |= TRANMERC_CENT_MER_ERROR; } if ((Scale_Factor < MIN_SCALE_FACTOR) || (Scale_Factor > MAX_SCALE_FACTOR)) { Error_Code |= TRANMERC_SCALE_FACTOR_ERROR; } if (Error_Code == TRANMERC_NO_ERROR) { /* no errors */ TranMerc_a = a; TranMerc_f = f; TranMerc_Origin_Lat = 0; TranMerc_Origin_Long = 0; TranMerc_False_Northing = 0; TranMerc_False_Easting = 0; TranMerc_Scale_Factor = 1; /* Eccentricity Squared */ TranMerc_es = 2 * TranMerc_f - TranMerc_f * TranMerc_f; /* Second Eccentricity Squared */ TranMerc_ebs = (1 / (1 - TranMerc_es)) - 1; TranMerc_b = TranMerc_a * (1 - TranMerc_f); /*True meridianal constants */ tn = (TranMerc_a - TranMerc_b) / (TranMerc_a + TranMerc_b); tn2 = tn * tn; tn3 = tn2 * tn; tn4 = tn3 * tn; tn5 = tn4 * tn; TranMerc_ap = TranMerc_a * (1.e0 - tn + 5.e0 * (tn2 - tn3) / 4.e0 + 81.e0 * (tn4 - tn5) / 64.e0); TranMerc_bp = 3.e0 * TranMerc_a * (tn - tn2 + 7.e0 * (tn3 - tn4) / 8.e0 + 55.e0 * tn5 / 64.e0) / 2.e0; TranMerc_cp = 15.e0 * TranMerc_a * (tn2 - tn3 + 3.e0 * (tn4 - tn5) / 4.e0) / 16.0; TranMerc_dp = 35.e0 * TranMerc_a * (tn3 - tn4 + 11.e0 * tn5 / 16.e0) / 48.e0; TranMerc_ep = 315.e0 * TranMerc_a * (tn4 - tn5) / 512.e0; convertGeodeticToTransverseMercator(MAX_LAT, MAX_DELTA_LONG); TranMerc_Delta_Easting = getEasting(); TranMerc_Delta_Northing = getNorthing(); convertGeodeticToTransverseMercator(0, MAX_DELTA_LONG); TranMerc_Delta_Easting = getEasting(); TranMerc_Origin_Lat = Origin_Latitude; if (Central_Meridian > PI) Central_Meridian -= (2 * PI); TranMerc_Origin_Long = Central_Meridian; TranMerc_False_Northing = False_Northing; TranMerc_False_Easting = False_Easting; TranMerc_Scale_Factor = Scale_Factor; } return (Error_Code); } /** * The function Convert_Geodetic_To_Transverse_Mercator converts geodetic (latitude and longitude) coordinates to * Transverse Mercator projection (easting and northing) coordinates, according to the current ellipsoid and * Transverse Mercator projection coordinates. If any errors occur, the error code(s) are returned by the function, * otherwise TRANMERC_NO_ERROR is returned. * * @param Latitude Latitude in radians * @param Longitude Longitude in radians * @return error code */ public long convertGeodeticToTransverseMercator(double Latitude, double Longitude) { double c; /* Cosine of latitude */ double c2; double c3; double c5; double c7; double dlam; /* Delta longitude - Difference in Longitude */ double eta; /* constant - TranMerc_ebs *c *c */ double eta2; double eta3; double eta4; double s; /* Sine of latitude */ double sn; /* Radius of curvature in the prime vertical */ double t; /* Tangent of latitude */ double tan2; double tan3; double tan4; double tan5; double tan6; double t1; /* Term in coordinate conversion formula - GP to Y */ double t2; /* Term in coordinate conversion formula - GP to Y */ double t3; /* Term in coordinate conversion formula - GP to Y */ double t4; /* Term in coordinate conversion formula - GP to Y */ double t5; /* Term in coordinate conversion formula - GP to Y */ double t6; /* Term in coordinate conversion formula - GP to Y */ double t7; /* Term in coordinate conversion formula - GP to Y */ double t8; /* Term in coordinate conversion formula - GP to Y */ double t9; /* Term in coordinate conversion formula - GP to Y */ double tmd; /* True Meridional distance */ double tmdo; /* True Meridional distance for latitude of origin */ long Error_Code = TRANMERC_NO_ERROR; double temp_Origin; double temp_Long; if ((Latitude < -MAX_LAT) || (Latitude > MAX_LAT)) { /* Latitude out of range */ Error_Code |= TRANMERC_LAT_ERROR; } if (Longitude > PI) Longitude -= (2 * PI); if ((Longitude < (TranMerc_Origin_Long - MAX_DELTA_LONG)) || (Longitude > (TranMerc_Origin_Long + MAX_DELTA_LONG))) { if (Longitude < 0) temp_Long = Longitude + 2 * PI; else temp_Long = Longitude; if (TranMerc_Origin_Long < 0) temp_Origin = TranMerc_Origin_Long + 2 * PI; else temp_Origin = TranMerc_Origin_Long; if ((temp_Long < (temp_Origin - MAX_DELTA_LONG)) || (temp_Long > (temp_Origin + MAX_DELTA_LONG))) Error_Code |= TRANMERC_LON_ERROR; } if (Error_Code == TRANMERC_NO_ERROR) { /* no errors */ /* * Delta Longitude */ dlam = Longitude - TranMerc_Origin_Long; if (Math.abs(dlam) > (9.0 * PI / 180)) { /* Distortion will result if Longitude is more than 9 degrees from the Central Meridian */ Error_Code |= TRANMERC_LON_WARNING; } if (dlam > PI) dlam -= (2 * PI); if (dlam < -PI) dlam += (2 * PI); if (Math.abs(dlam) < 2.e-10) dlam = 0.0; s = Math.sin(Latitude); c = Math.cos(Latitude); c2 = c * c; c3 = c2 * c; c5 = c3 * c2; c7 = c5 * c2; t = Math.tan(Latitude); tan2 = t * t; tan3 = tan2 * t; tan4 = tan3 * t; tan5 = tan4 * t; tan6 = tan5 * t; eta = TranMerc_ebs * c2; eta2 = eta * eta; eta3 = eta2 * eta; eta4 = eta3 * eta; /* radius of curvature in prime vertical */ // sn = SPHSN(Latitude); sn = TranMerc_a / Math.sqrt(1 - TranMerc_es * Math.pow(Math.sin(Latitude), 2)); /* True Meridianal Distances */ // tmd = SPHTMD(Latitude); tmd = TranMerc_ap * Latitude - TranMerc_bp * Math.sin(2.0 * Latitude) + TranMerc_cp * Math.sin(4.0 * Latitude) - TranMerc_dp * Math.sin(6.0 * Latitude) + TranMerc_ep * Math.sin(8.0 * Latitude); /* Origin */ // tmdo = SPHTMD (TranMerc_Origin_Lat); tmdo = TranMerc_ap * TranMerc_Origin_Lat - TranMerc_bp * Math.sin(2.0 * TranMerc_Origin_Lat) + TranMerc_cp * Math.sin(4.0 * TranMerc_Origin_Lat) - TranMerc_dp * Math.sin(6.0 * TranMerc_Origin_Lat) + TranMerc_ep * Math.sin(8.0 * TranMerc_Origin_Lat); /* northing */ t1 = (tmd - tmdo) * TranMerc_Scale_Factor; t2 = sn * s * c * TranMerc_Scale_Factor / 2.e0; t3 = sn * s * c3 * TranMerc_Scale_Factor * (5.e0 - tan2 + 9.e0 * eta + 4.e0 * eta2) / 24.e0; t4 = sn * s * c5 * TranMerc_Scale_Factor * (61.e0 - 58.e0 * tan2 + tan4 + 270.e0 * eta - 330.e0 * tan2 * eta + 445.e0 * eta2 + 324.e0 * eta3 - 680.e0 * tan2 * eta2 + 88.e0 * eta4 - 600.e0 * tan2 * eta3 - 192.e0 * tan2 * eta4) / 720.e0; t5 = sn * s * c7 * TranMerc_Scale_Factor * (1385.e0 - 3111.e0 * tan2 + 543.e0 * tan4 - tan6) / 40320.e0; Northing = TranMerc_False_Northing + t1 + Math.pow(dlam, 2.e0) * t2 + Math.pow(dlam, 4.e0) * t3 + Math.pow(dlam, 6.e0) * t4 + Math.pow(dlam, 8.e0) * t5; /* Easting */ t6 = sn * c * TranMerc_Scale_Factor; t7 = sn * c3 * TranMerc_Scale_Factor * (1.e0 - tan2 + eta) / 6.e0; t8 = sn * c5 * TranMerc_Scale_Factor * (5.e0 - 18.e0 * tan2 + tan4 + 14.e0 * eta - 58.e0 * tan2 * eta + 13.e0 * eta2 + 4.e0 * eta3 - 64.e0 * tan2 * eta2 - 24.e0 * tan2 * eta3) / 120.e0; t9 = sn * c7 * TranMerc_Scale_Factor * (61.e0 - 479.e0 * tan2 + 179.e0 * tan4 - tan6) / 5040.e0; Easting = TranMerc_False_Easting + dlam * t6 + Math.pow(dlam, 3.e0) * t7 + Math.pow(dlam, 5.e0) * t8 + Math.pow(dlam, 7.e0) * t9; } return (Error_Code); } /** * @return Easting/X at the center of the projection */ public double getEasting() { return Easting; } /** * @return Northing/Y at the center of the projection */ public double getNorthing() { return Northing; } /** * The function Convert_Transverse_Mercator_To_Geodetic converts Transverse * Mercator projection (easting and northing) coordinates to geodetic * (latitude and longitude) coordinates, according to the current ellipsoid * and Transverse Mercator projection parameters. If any errors occur, the * error code(s) are returned by the function, otherwise TRANMERC_NO_ERROR is * returned. * * @param Easting Easting/X in meters * @param Northing Northing/Y in meters * @return error code */ public long convertTransverseMercatorToGeodetic (double Easting, double Northing) { double c; /* Cosine of latitude */ double de; /* Delta easting - Difference in Easting (Easting-Fe) */ double dlam; /* Delta longitude - Difference in Longitude */ double eta; /* constant - TranMerc_ebs *c *c */ double eta2; double eta3; double eta4; double ftphi; /* Footpoint latitude */ int i; /* Loop iterator */ double s; /* Sine of latitude */ double sn; /* Radius of curvature in the prime vertical */ double sr; /* Radius of curvature in the meridian */ double t; /* Tangent of latitude */ double tan2; double tan4; double t10; /* Term in coordinate conversion formula - GP to Y */ double t11; /* Term in coordinate conversion formula - GP to Y */ double t12; /* Term in coordinate conversion formula - GP to Y */ double t13; /* Term in coordinate conversion formula - GP to Y */ double t14; /* Term in coordinate conversion formula - GP to Y */ double t15; /* Term in coordinate conversion formula - GP to Y */ double t16; /* Term in coordinate conversion formula - GP to Y */ double t17; /* Term in coordinate conversion formula - GP to Y */ double tmd; /* True Meridional distance */ double tmdo; /* True Meridional distance for latitude of origin */ long Error_Code = TRANMERC_NO_ERROR; if ((Easting < (TranMerc_False_Easting - TranMerc_Delta_Easting)) ||(Easting > (TranMerc_False_Easting + TranMerc_Delta_Easting))) { /* Easting out of range */ Error_Code |= TRANMERC_EASTING_ERROR; } if ((Northing < (TranMerc_False_Northing - TranMerc_Delta_Northing)) || (Northing > (TranMerc_False_Northing + TranMerc_Delta_Northing))) { /* Northing out of range */ Error_Code |= TRANMERC_NORTHING_ERROR; } if (Error_Code == TRANMERC_NO_ERROR) { /* True Meridional Distances for latitude of origin */ // tmdo = SPHTMD(TranMerc_Origin_Lat); tmdo = TranMerc_ap * TranMerc_Origin_Lat - TranMerc_bp * Math.sin(2.0 * TranMerc_Origin_Lat) + TranMerc_cp * Math.sin(4.0 * TranMerc_Origin_Lat) - TranMerc_dp * Math.sin(6.0 * TranMerc_Origin_Lat) + TranMerc_ep * Math.sin(8.0 * TranMerc_Origin_Lat); /* Origin */ tmd = tmdo + (Northing - TranMerc_False_Northing) / TranMerc_Scale_Factor; /* First Estimate */ //sr = SPHSR(0.e0); sr = TranMerc_a * (1.e0 - TranMerc_es) / Math.pow(Math.sqrt(1.e0 - TranMerc_es * Math.pow(Math.sin(0.e0), 2)), 3); ftphi = tmd/sr; for (i = 0; i < 5 ; i++) { // t10 = SPHTMD (ftphi); t10 = TranMerc_ap * ftphi - TranMerc_bp * Math.sin(2.0 * ftphi) + TranMerc_cp * Math.sin(4.0 * ftphi) - TranMerc_dp * Math.sin(6.0 * ftphi) + TranMerc_ep * Math.sin(8.0 * ftphi); // sr = SPHSR(ftphi); sr = TranMerc_a * (1.e0 - TranMerc_es) / Math.pow(Math.sqrt(1.e0 - TranMerc_es * Math.pow(Math.sin(ftphi), 2)), 3); ftphi = ftphi + (tmd - t10) / sr; } /* Radius of Curvature in the meridian */ // sr = SPHSR(ftphi); sr = TranMerc_a * (1.e0 - TranMerc_es) / Math.pow(Math.sqrt(1.e0 - TranMerc_es * Math.pow(Math.sin(ftphi), 2)), 3); /* Radius of Curvature in the meridian */ // sn = SPHSN(ftphi); sn = TranMerc_a / Math.sqrt(1.e0 - TranMerc_es * Math.pow(Math.sin(ftphi), 2)); /* Sine Cosine terms */ s = Math.sin(ftphi); c = Math.cos(ftphi); /* Tangent Value */ t = Math.tan(ftphi); tan2 = t * t; tan4 = tan2 * tan2; eta = TranMerc_ebs * Math.pow(c,2); eta2 = eta * eta; eta3 = eta2 * eta; eta4 = eta3 * eta; de = Easting - TranMerc_False_Easting; if (Math.abs(de) < 0.0001) de = 0.0; /* Latitude */ t10 = t / (2.e0 * sr * sn * Math.pow(TranMerc_Scale_Factor, 2)); t11 = t * (5.e0 + 3.e0 * tan2 + eta - 4.e0 * Math.pow(eta,2) - 9.e0 * tan2 * eta) / (24.e0 * sr * Math.pow(sn,3) * Math.pow(TranMerc_Scale_Factor,4)); t12 = t * (61.e0 + 90.e0 * tan2 + 46.e0 * eta + 45.E0 * tan4 - 252.e0 * tan2 * eta - 3.e0 * eta2 + 100.e0 * eta3 - 66.e0 * tan2 * eta2 - 90.e0 * tan4 * eta + 88.e0 * eta4 + 225.e0 * tan4 * eta2 + 84.e0 * tan2* eta3 - 192.e0 * tan2 * eta4) / ( 720.e0 * sr * Math.pow(sn,5) * Math.pow(TranMerc_Scale_Factor, 6) ); t13 = t * ( 1385.e0 + 3633.e0 * tan2 + 4095.e0 * tan4 + 1575.e0 * Math.pow(t,6))/ (40320.e0 * sr * Math.pow(sn,7) * Math.pow(TranMerc_Scale_Factor,8)); Latitude = ftphi - Math.pow(de,2) * t10 + Math.pow(de,4) * t11 - Math.pow(de,6) * t12 + Math.pow(de,8) * t13; t14 = 1.e0 / (sn * c * TranMerc_Scale_Factor); t15 = (1.e0 + 2.e0 * tan2 + eta) / (6.e0 * Math.pow(sn,3) * c * Math.pow(TranMerc_Scale_Factor,3)); t16 = (5.e0 + 6.e0 * eta + 28.e0 * tan2 - 3.e0 * eta2 + 8.e0 * tan2 * eta + 24.e0 * tan4 - 4.e0 * eta3 + 4.e0 * tan2 * eta2 + 24.e0 * tan2 * eta3) / (120.e0 * Math.pow(sn,5) * c * Math.pow(TranMerc_Scale_Factor,5)); t17 = (61.e0 + 662.e0 * tan2 + 1320.e0 * tan4 + 720.e0 * Math.pow(t,6)) / (5040.e0 * Math.pow(sn,7) * c * Math.pow(TranMerc_Scale_Factor,7)); /* Difference in Longitude */ dlam = de * t14 - Math.pow(de,3) * t15 + Math.pow(de,5) * t16 - Math.pow(de,7) * t17; /* Longitude */ Longitude = TranMerc_Origin_Long + dlam; if(Math.abs(Latitude) > (90.0 * PI / 180.0)) Error_Code |= TRANMERC_NORTHING_ERROR; if((Longitude) > (PI)) { Longitude -= (2 * PI); if(Math.abs(Longitude) > PI) Error_Code |= TRANMERC_EASTING_ERROR; } if (Math.abs(dlam) > (9.0 * PI / 180) * Math.cos(Latitude)) { /* Distortion will result if Longitude is more than 9 degrees from the Central Meridian at the equator */ /* and decreases to 0 degrees at the poles */ /* As you move towards the poles, distortion will become more significant */ Error_Code |= TRANMERC_LON_WARNING; } if(Latitude > 1.0e10) Error_Code |= TRANMERC_LON_WARNING; } return (Error_Code); } /** * @return Latitude in radians. */ public double getLatitude() { return Latitude; } /** * @return Longitude in radians. */ public double getLongitude() { return Longitude; } } // end TMConverter class