using System; using System.Linq; using System.Diagnostics; using System.ComponentModel; namespace CoordinateSharp { /// /// Universal Transverse Mercator (UTM) coordinate system. Uses the WGS 84 Datum. /// [Serializable] public class UniversalTransverseMercator : INotifyPropertyChanged { /// /// Creates a UniversalTransverMercator object with a WGS84 Datum. /// /// Latitude zone /// Longitude zone /// Easting /// Northing public UniversalTransverseMercator(string latz, int longz, double est, double nrt) { if (longz < 1 || longz > 60) { Debug.WriteLine("Longitudinal zone out of range", "UTM longitudinal zones must be between 1-60."); } if (!Verify_Lat_Zone(latz)) { Debug.WriteLine("Latitudinal zone invalid", "UTM latitudinal zone was unrecognized."); } if (est < 160000 || est > 834000) { Debug.WriteLine("The Easting value provided is outside the max allowable range. Use with caution."); } if (nrt < 0 || nrt > 10000000) { Debug.WriteLine("Northing out of range", "Northing must be between 0-10,000,000."); } latZone = latz; longZone =longz; easting = est; northing = nrt; equatorial_radius = 6378137.0; inverse_flattening = 298.257223563; } /// /// Creates a UniversalTransverMercator object with a custom Datum. /// /// Latitude zone /// Longitude zone /// Easting /// Northing /// Equatorial Radius /// Inverse Flattening public UniversalTransverseMercator(string latz, int longz, double est, double nrt, double radius, double flaten) { if (longz < 1 || longz > 60) { Debug.WriteLine("Longitudinal zone out of range", "UTM longitudinal zones must be between 1-60."); } if (!Verify_Lat_Zone(latz)) { Debug.WriteLine("Latitudinal zone invalid", "UTM latitudinal zone was unrecognized."); } if (est < 160000 || est > 834000) { Debug.WriteLine("The Easting value provided is outside the max allowable range. Use with caution."); } if (nrt < 0 || nrt > 10000000) { Debug.WriteLine("Northing out of range", "Northing must be between 0-10,000,000."); } latZone = latz; longZone = longz; easting = est; northing = nrt; equatorial_radius = radius; inverse_flattening = flaten; } private Coordinate coordinate; internal double equatorial_radius; internal double inverse_flattening; private string latZone; private int longZone; private double easting; private double northing; private bool withinCoordinateSystemBounds = true; /// /// UTM Zone Letter /// public string LatZone { get { return latZone; } set { if (latZone != value) { latZone = value; } } } /// /// UTM Zone Number /// public int LongZone { get { return longZone; } set { if (longZone != value) { longZone = value; } } } /// /// UTM Easting /// public double Easting { get { return easting; } set { if (easting != value) { easting = value; } } } /// /// UTM Northing /// public double Northing { get { return northing; } set { if (northing != value) { northing = value; } } } /// /// Datum Equatorial Radius / Semi Major Axis /// public double Equatorial_Radius { get { return equatorial_radius; } } /// /// Datum Flattening /// public double Inverse_Flattening { get { return inverse_flattening; } } /// /// Is the UTM conversion within the coordinate system's accurate boundaries after conversion from Lat/Long. /// public bool WithinCoordinateSystemBounds { get { return withinCoordinateSystemBounds; } } /// /// Constructs a UTM object based off DD Lat/Long /// /// DD Latitude /// DD Longitide /// Parent Coordinate Object internal UniversalTransverseMercator(double lat, double longi, Coordinate c) { //validate coords //if (lat > 180) { throw new ArgumentOutOfRangeException("Degrees out of range", "Longitudinal coordinate decimal cannot be greater than 180."); } //if (lat < -180) { throw new ArgumentOutOfRangeException("Degrees out of range", "Longitudinal coordinate decimal cannot be less than 180."); } //if (longi > 90) { throw new ArgumentOutOfRangeException("Degrees out of range", "Latitudinal coordinate decimal cannot be greater than 90."); } //if (longi < -90) { throw new ArgumentOutOfRangeException("Degrees out of range", "Latitudinal coordinate decimal cannot be less than 90."); } equatorial_radius = 6378137.0; inverse_flattening = 298.257223563; ToUTM(lat, longi, this); coordinate = c; } /// /// Constructs a UTM object based off DD Lat/Long /// /// DD Latitude /// DD Longitide /// Parent Coordinate Object /// Equatorial Radius /// Flattening internal UniversalTransverseMercator(double lat, double longi, Coordinate c,double rad,double flt) { equatorial_radius = rad; inverse_flattening = flt; ToUTM(lat, longi, this); coordinate = c; } /// /// Constructs a UTM object based off a UTM coordinate /// Not yet implemented /// /// Zone Letter /// Zone Number /// Easting /// Northing /// Parent Coordinate Object /// Equatorial Radius /// Inverse Flattening internal UniversalTransverseMercator(string latz, int longz, double e, double n, Coordinate c, double rad, double flt) { //validate utm if (longz < 1 || longz > 60) { Debug.WriteLine("Longitudinal zone out of range", "UTM longitudinal zones must be between 1-60."); } if (!Verify_Lat_Zone(latz)) { throw new ArgumentException("Latitudinal zone invalid", "UTM latitudinal zone was unrecognized."); } if (e < 160000 || e > 834000) { Debug.WriteLine("The Easting value provided is outside the max allowable range. If this is intentional, use with caution."); } if (n < 0 || n > 10000000) { throw new ArgumentOutOfRangeException("Northing out of range", "Northing must be between 0-10,000,000."); } equatorial_radius = rad; inverse_flattening = flt; latZone = latz; longZone = longz; easting = e; northing = n; coordinate = c; if (c.Latitude.DecimalDegree <= -80 || c.Latitude.DecimalDegree >= 84) { withinCoordinateSystemBounds = false; } else { withinCoordinateSystemBounds = true; } } /// /// Verifies Lat zone when convert from UTM to DD Lat/Long /// /// Zone Letter /// boolean private bool Verify_Lat_Zone(string l) { if (LatZones.longZongLetters.Where(x => x == l.ToUpper()).Count() != 1) { return false; } return true; } private double degreeToRadian(double degree) { return degree * Math.PI / 180; } /// /// Assigns UTM values based of Lat/Long /// /// DD Latitude /// DD longitude /// UTM Object to modify internal void ToUTM(double lat, double longi, UniversalTransverseMercator utm) { string letter = ""; double easting = 0; double northing = 0; int zone = (int)Math.Floor(longi / 6 + 31); if (lat < -72) letter = "C"; else if (lat < -64) letter = "D"; else if (lat < -56) letter = "E"; else if (lat < -48) letter = "F"; else if (lat < -40) letter = "G"; else if (lat < -32) letter = "H"; else if (lat < -24) letter = "J"; else if (lat < -16) letter = "K"; else if (lat < -8) letter = "L"; else if (lat < 0) letter = "M"; else if (lat < 8) letter = "N"; else if (lat < 16) letter = "P"; else if (lat < 24) letter = "Q"; else if (lat < 32) letter = "R"; else if (lat < 40) letter = "S"; else if (lat < 48) letter = "T"; else if (lat < 56) letter = "U"; else if (lat < 64) letter = "V"; else if (lat < 72) letter = "W"; else letter = "X"; double a = utm.equatorial_radius; double f = 1.0 / utm.inverse_flattening; double b = a * (1 - f); // polar radius double e = Math.Sqrt(1 - Math.Pow(b, 2) / Math.Pow(a, 2)); double e0 = e / Math.Sqrt(1 - Math.Pow(e, 1)); double drad = Math.PI / 180; double k0 = 0.9996; double phi = lat * drad; // convert latitude to radians double lng = longi * drad; // convert longitude to radians double utmz = 1 + Math.Floor((longi + 180) / 6.0); // longitude to utm zone double zcm = 3 + 6.0 * (utmz - 1) - 180; // central meridian of a zone // this gives us zone A-B for below 80S double esq = (1 - (b / a) * (b / a)); double e0sq = e * e / (1 - Math.Pow(e, 2)); double M = 0; double N = a / Math.Sqrt(1 - Math.Pow(e * Math.Sin(phi), 2)); double T = Math.Pow(Math.Tan(phi), 2); double C = e0sq * Math.Pow(Math.Cos(phi), 2); double A = (longi - zcm) * drad * Math.Cos(phi); // calculate M (USGS style) M = phi * (1 - esq * (1.0 / 4.0 + esq * (3.0 / 64.0 + 5.0 * esq / 256.0))); M = M - Math.Sin(2.0 * phi) * (esq * (3.0 / 8.0 + esq * (3.0 / 32.0 + 45.0 * esq / 1024.0))); M = M + Math.Sin(4.0 * phi) * (esq * esq * (15.0 / 256.0 + esq * 45.0 / 1024.0)); M = M - Math.Sin(6.0 * phi) * (esq * esq * esq * (35.0 / 3072.0)); M = M * a;//Arc length along standard meridian double M0 = 0;// if another point of origin is used than the equator // Calculate the UTM values... // first the easting var x = k0 * N * A * (1 + A * A * ((1 - T + C) / 6 + A * A * (5 - 18 * T + T * T + 72.0 * C - 58 * e0sq) / 120.0)); //Easting relative to CM x = x + 500000; // standard easting // Northing double y = k0 * (M - M0 + N * Math.Tan(phi) * (A * A * (1 / 2.0 + A * A * ((5 - T + 9 * C + 4 * C * C) / 24.0 + A * A * (61 - 58 * T + T * T + 600 * C - 330 * e0sq) / 720.0)))); // first from the equator double yg = y + 10000000; //yg = y global, from S. Pole if (y < 0) { y = 10000000 + y; // add in false northing if south of the equator } easting = Math.Round(10 * (x)) / 10.0; northing = Math.Round(10 * y) / 10.0; utm.latZone = letter; utm.longZone = zone; utm.easting = easting; utm.northing = northing; if(lat<=-80 || lat >= 84) { withinCoordinateSystemBounds = false; } else { withinCoordinateSystemBounds = true; } } /// /// UTM Default String Format /// /// UTM Formatted Coordinate String public override string ToString() { if (!withinCoordinateSystemBounds) { return ""; }//MGRS Coordinate is outside its reliable boundaries. Return empty. return longZone.ToString() + LatZone + " " + (int)easting + "mE " + (int)northing + "mN"; } private static Coordinate UTMtoLatLong(double x, double y, double zone, double equatorialRadius, double flattening) { //x easting //y northing //http://home.hiwaay.net/~taylorc/toolbox/geography/geoutm.html double phif, Nf, Nfpow, nuf2, ep2, tf, tf2, tf4, cf; double x1frac, x2frac, x3frac, x4frac, x5frac, x6frac, x7frac, x8frac; double x2poly, x3poly, x4poly, x5poly, x6poly, x7poly, x8poly; double sm_a = equatorialRadius; double sm_b = equatorialRadius * (1 - (1.0 / flattening)); //Polar Radius /* Get the value of phif, the footpoint latitude. */ phif = FootpointLatitude(y,equatorialRadius,flattening); /* Precalculate ep2 */ ep2 = (Math.Pow(sm_a, 2.0) - Math.Pow(sm_b, 2.0)) / Math.Pow(sm_b, 2.0); /* Precalculate cos (phif) */ cf = Math.Cos(phif); /* Precalculate nuf2 */ nuf2 = ep2 * Math.Pow(cf, 2.0); /* Precalculate Nf and initialize Nfpow */ Nf = Math.Pow(sm_a, 2.0) / (sm_b * Math.Sqrt(1 + nuf2)); Nfpow = Nf; /* Precalculate tf */ tf = Math.Tan(phif); tf2 = tf * tf; tf4 = tf2 * tf2; /* Precalculate fractional coefficients for x**n in the equations below to simplify the expressions for latitude and longitude. */ x1frac = 1.0 / (Nfpow * cf); Nfpow *= Nf; /* now equals Nf**2) */ x2frac = tf / (2.0 * Nfpow); Nfpow *= Nf; /* now equals Nf**3) */ x3frac = 1.0 / (6.0 * Nfpow * cf); Nfpow *= Nf; /* now equals Nf**4) */ x4frac = tf / (24.0 * Nfpow); Nfpow *= Nf; /* now equals Nf**5) */ x5frac = 1.0 / (120.0 * Nfpow * cf); Nfpow *= Nf; /* now equals Nf**6) */ x6frac = tf / (720.0 * Nfpow); Nfpow *= Nf; /* now equals Nf**7) */ x7frac = 1.0 / (5040.0 * Nfpow * cf); Nfpow *= Nf; /* now equals Nf**8) */ x8frac = tf / (40320.0 * Nfpow); /* Precalculate polynomial coefficients for x**n. -- x**1 does not have a polynomial coefficient. */ x2poly = -1.0 - nuf2; x3poly = -1.0 - 2 * tf2 - nuf2; x4poly = 5.0 + 3.0 * tf2 + 6.0 * nuf2 - 6.0 * tf2 * nuf2 - 3.0 * (nuf2 * nuf2) - 9.0 * tf2 * (nuf2 * nuf2); x5poly = 5.0 + 28.0 * tf2 + 24.0 * tf4 + 6.0 * nuf2 + 8.0 * tf2 * nuf2; x6poly = -61.0 - 90.0 * tf2 - 45.0 * tf4 - 107.0 * nuf2 + 162.0 * tf2 * nuf2; x7poly = -61.0 - 662.0 * tf2 - 1320.0 * tf4 - 720.0 * (tf4 * tf2); x8poly = 1385.0 + 3633.0 * tf2 + 4095.0 * tf4 + 1575 * (tf4 * tf2); /* Calculate latitude */ double nLat = phif + x2frac * x2poly * (x * x) + x4frac * x4poly * Math.Pow(x, 4.0) + x6frac * x6poly * Math.Pow(x, 6.0) + x8frac * x8poly * Math.Pow(x, 8.0); /* Calculate longitude */ double nLong = zone + x1frac * x + x3frac * x3poly * Math.Pow(x, 3.0) + x5frac * x5poly * Math.Pow(x, 5.0) + x7frac * x7poly * Math.Pow(x, 7.0); double dLat = RadToDeg(nLat); double dLong = RadToDeg(nLong); if (dLat > 90) { dLat = 90; } if (dLat < -90) { dLat = -90; } if (dLong > 180) { dLong = 180; } if (dLong < -180) { dLong = -180; } Coordinate c = new Coordinate(equatorialRadius,flattening, true); CoordinatePart cLat = new CoordinatePart(dLat, CoordinateType.Lat); CoordinatePart cLng = new CoordinatePart(dLong, CoordinateType.Long); c.Latitude = cLat; c.Longitude = cLng; return c; } private static double RadToDeg(double rad) { double pi = 3.14159265358979; return (rad / pi * 180.0); } private static double DegToRad(double deg) { double pi = 3.14159265358979; return (deg / 180.0 * pi); } private static double FootpointLatitude(double y, double equatorialRadius, double flattening) { double y_, alpha_, beta_, gamma_, delta_, epsilon_, n; double result; /* Ellipsoid model constants (actual values here are for WGS84) */ double sm_a = equatorialRadius; double sm_b = equatorialRadius * (1 - (1.0 / flattening)); /* Precalculate n (Eq. 10.18) */ n = (sm_a - sm_b) / (sm_a + sm_b); /* Precalculate alpha_ (Eq. 10.22) */ /* (Same as alpha in Eq. 10.17) */ alpha_ = ((sm_a + sm_b) / 2.0) * (1 + (Math.Pow(n, 2.0) / 4) + (Math.Pow(n, 4.0) / 64)); /* Precalculate y_ (Eq. 10.23) */ y_ = y / alpha_; /* Precalculate beta_ (Eq. 10.22) */ beta_ = (3.0 * n / 2.0) + (-27.0 * Math.Pow(n, 3.0) / 32.0) + (269.0 * Math.Pow(n, 5.0) / 512.0); /* Precalculate gamma_ (Eq. 10.22) */ gamma_ = (21.0 * Math.Pow(n, 2.0) / 16.0) + (-55.0 * Math.Pow(n, 4.0) / 32.0); /* Precalculate delta_ (Eq. 10.22) */ delta_ = (151.0 * Math.Pow(n, 3.0) / 96.0) + (-417.0 * Math.Pow(n, 5.0) / 128.0); /* Precalculate epsilon_ (Eq. 10.22) */ epsilon_ = (1097.0 * Math.Pow(n, 4.0) / 512.0); /* Now calculate the sum of the series (Eq. 10.21) */ result = y_ + (beta_ * Math.Sin(2.0 * y_)) + (gamma_ * Math.Sin(4.0 * y_)) + (delta_ * Math.Sin(6.0 * y_)) + (epsilon_ * Math.Sin(8.0 * y_)); return result; } /// /// Converts UTM coordinate to Lat/Long /// /// utm /// Coordinate object public static Coordinate ConvertUTMtoLatLong(UniversalTransverseMercator utm) { bool southhemi = false; if (utm.latZone == "A" || utm.latZone == "B" || utm.latZone == "C" || utm.latZone == "D" || utm.latZone == "E" || utm.latZone == "F" || utm.latZone == "G" || utm.latZone == "H" || utm.latZone == "J" || utm.latZone == "K" || utm.latZone == "L" || utm.latZone == "M") { southhemi = true; } double cmeridian; double x = utm.Easting - 500000.0; double UTMScaleFactor = 0.9996; x /= UTMScaleFactor; /* If in southern hemisphere, adjust y accordingly. */ double y = utm.Northing; if (southhemi) { y -= 10000000.0; } y /= UTMScaleFactor; cmeridian = UTMCentralMeridian(utm.LongZone); Coordinate c = UTMtoLatLong(x, y, cmeridian, utm.equatorial_radius, utm.inverse_flattening); if (c.Latitude.ToDouble() > 85 || c.Latitude.ToDouble() < -85) { Debug.WriteLine("UTM conversions greater than 85 degrees or less than -85 degree latitude contain major deviations and should be used with caution."); } return c; } private static double UTMCentralMeridian(double zone) { double cmeridian; cmeridian = DegToRad(-183.0 + (zone * 6.0)); return cmeridian; } /// /// Property changed event /// public event PropertyChangedEventHandler PropertyChanged; /// /// Notify property changed /// /// Property name public void NotifyPropertyChanged(string propName) { if (this.PropertyChanged != null) { PropertyChanged(this, new PropertyChangedEventArgs(propName)); } } } }