Coordinates/CoordinateSharp/Coordinate.MGRS.cs
2019-12-09 14:46:34 +01:00

222 lines
9.4 KiB
C#

using System;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
namespace CoordinateSharp {
/// <summary>
/// Military Grid Reference System (MGRS). Uses the WGS 84 Datum.
/// Relies upon values from the UniversalTransverseMercator class
/// </summary>
[Serializable]
public class MilitaryGridReferenceSystem : INotifyPropertyChanged {
/// <summary>
/// Create an MGRS object with WGS84 datum
/// </summary>
/// <param name="latz">Lat Zone</param>
/// <param name="longz">Long Zone</param>
/// <param name="d">Digraph</param>
/// <param name="e">Easting</param>
/// <param name="n">Northing</param>
public MilitaryGridReferenceSystem(String latz, Int32 longz, String d, Double e, Double n) {
String digraphLettersE = "ABCDEFGHJKLMNPQRSTUVWXYZ";
String digraphLettersN = "ABCDEFGHJKLMNPQRSTUV";
if (longz < 1 || longz > 60) { Debug.WriteLine("Longitudinal zone out of range", "UTM longitudinal zones must be between 1-60."); }
if (!this.Verify_Lat_Zone(latz)) { throw new ArgumentException("Latitudinal zone invalid", "UTM latitudinal zone was unrecognized."); }
if (n < 0 || n > 10000000) { throw new ArgumentOutOfRangeException("Northing out of range", "Northing must be between 0-10,000,000."); }
if (d.Count() < 2 || d.Count() > 2) { throw new ArgumentException("Digraph invalid", "MGRS Digraph was unrecognized."); }
if (digraphLettersE.ToCharArray().ToList().Where(x => x.ToString() == d.ToUpper()[0].ToString()).Count() == 0) { throw new ArgumentException("Digraph invalid", "MGRS Digraph was unrecognized."); }
if (digraphLettersN.ToCharArray().ToList().Where(x => x.ToString() == d.ToUpper()[1].ToString()).Count() == 0) { throw new ArgumentException("Digraph invalid", "MGRS Digraph was unrecognized."); }
this.LatZone = latz;
this.LongZone = longz;
this.Digraph = d;
this.Easting = e;
this.Northing = n;
//WGS84
this.equatorialRadius = 6378137.0;
this.inverseFlattening = 298.257223563;
}
/// <summary>
/// Create an MGRS object with custom datum
/// </summary>
/// <param name="latz">Lat Zone</param>
/// <param name="longz">Long Zone</param>
/// <param name="d">Digraph</param>
/// <param name="e">Easting</param>
/// <param name="n">Northing</param>
/// <param name="rad">Equatorial Radius</param>
/// <param name="flt">Inverse Flattening</param>
public MilitaryGridReferenceSystem(String latz, Int32 longz, String d, Double e, Double n, Double rad, Double flt) {
String digraphLettersE = "ABCDEFGHJKLMNPQRSTUVWXYZ";
String digraphLettersN = "ABCDEFGHJKLMNPQRSTUV";
if (longz < 1 || longz > 60) { Debug.WriteLine("Longitudinal zone out of range", "UTM longitudinal zones must be between 1-60."); }
if (!this.Verify_Lat_Zone(latz)) { throw new ArgumentException("Latitudinal zone invalid", "UTM latitudinal zone was unrecognized."); }
if (n < 0 || n > 10000000) { throw new ArgumentOutOfRangeException("Northing out of range", "Northing must be between 0-10,000,000."); }
if (d.Count() < 2 || d.Count() > 2) { throw new ArgumentException("Digraph invalid", "MGRS Digraph was unrecognized."); }
if (digraphLettersE.ToCharArray().ToList().Where(x => x.ToString() == d.ToUpper()[0].ToString()).Count() == 0) { throw new ArgumentException("Digraph invalid", "MGRS Digraph was unrecognized."); }
if (digraphLettersN.ToCharArray().ToList().Where(x => x.ToString() == d.ToUpper()[1].ToString()).Count() == 0) { throw new ArgumentException("Digraph invalid", "MGRS Digraph was unrecognized."); }
this.LatZone = latz;
this.LongZone = longz;
this.Digraph = d;
this.Easting = e;
this.Northing = n;
this.equatorialRadius = rad;
this.inverseFlattening = flt;
}
private Double equatorialRadius;
private Double inverseFlattening;
private Boolean Verify_Lat_Zone(String l) => LatZones.longZongLetters.Where(x => x == l.ToUpper()).Count() == 1;
/// <summary>
/// MGRS Zone Letter
/// </summary>
public String LatZone { get; private set; }
/// <summary>
/// MGRS Zone Number
/// </summary>
public Int32 LongZone { get; private set; }
/// <summary>
/// MGRS Easting
/// </summary>
public Double Easting { get; private set; }
/// <summary>
/// MGRS Northing
/// </summary>
public Double Northing { get; private set; }
/// <summary>
/// MGRS Digraph
/// </summary>
public String Digraph { get; private set; }
/// <summary>
/// Is MGRS conversion within the coordinate system's accurate boundaries after conversion from Lat/Long.
/// </summary>
public Boolean WithinCoordinateSystemBounds { get; private set; } = true;
internal MilitaryGridReferenceSystem(UniversalTransverseMercator utm) => this.ToMGRS(utm);
internal void ToMGRS(UniversalTransverseMercator utm) {
Digraphs digraphs = new Digraphs();
String digraph1 = digraphs.GetDigraph1(utm.LongZone, utm.Easting);
String digraph2 = digraphs.GetDigraph2(utm.LongZone, utm.Northing);
this.Digraph = digraph1 + digraph2;
this.LatZone = utm.LatZone;
this.LongZone = utm.LongZone;
//String easting = String.valueOf((int)_easting);
String e = ((Int32)utm.Easting).ToString();
if (e.Length < 5) {
e = "00000" + ((Int32)utm.Easting).ToString();
}
e = e.Substring(e.Length - 5);
this.Easting = Convert.ToInt32(e);
String n = ((Int32)utm.Northing).ToString();
if (n.Length < 5) {
n = "0000" + ((Int32)utm.Northing).ToString();
}
n = n.Substring(n.Length - 5);
this.Northing = Convert.ToInt32(n);
this.equatorialRadius = utm.equatorial_radius;
this.inverseFlattening = utm.inverse_flattening;
this.WithinCoordinateSystemBounds = utm.WithinCoordinateSystemBounds;
}
/// <summary>
/// Creates a Coordinate object from an MGRS/NATO UTM Coordinate
/// </summary>
/// <param name="mgrs">MilitaryGridReferenceSystem</param>
/// <returns>Coordinate object</returns>
public static Coordinate MGRStoLatLong(MilitaryGridReferenceSystem mgrs) {
String latz = mgrs.LatZone;
String digraph = mgrs.Digraph;
Char eltr = digraph[0];
Char nltr = digraph[1];
String digraphLettersE = "ABCDEFGHJKLMNPQRSTUVWXYZ";
//String digraphLettersN = "ABCDEFGHJKLMNPQRSTUV";
String digraphLettersAll = "";
for (Int32 lt = 1; lt < 25; lt++) {
digraphLettersAll += "ABCDEFGHJKLMNPQRSTUV";
}
Int32 eidx = digraphLettersE.IndexOf(eltr);
//Int32 nidx = digraphLettersN.IndexOf(nltr);
if (mgrs.LongZone / 2.0 == Math.Floor(mgrs.LongZone / 2.0)) {
//nidx -= 5; // correction for even numbered zones
}
Double ebase = 100000 * (1 + eidx - 8 * Math.Floor(Convert.ToDouble(eidx) / 8));
Int32 latBand = digraphLettersE.IndexOf(latz);
Int32 latBandLow = 8 * latBand - 96;
Int32 latBandHigh = 8 * latBand - 88;
if (latBand < 2) {
latBandLow = -90;
latBandHigh = -80;
} else if (latBand == 21) {
latBandLow = 72;
latBandHigh = 84;
} else if (latBand > 21) {
latBandLow = 84;
latBandHigh = 90;
}
Double lowLetter = Math.Floor(100 + 1.11 * latBandLow);
Double highLetter = Math.Round(100 + 1.11 * latBandHigh);
Int32 l = Convert.ToInt32(lowLetter);
Int32 h = Convert.ToInt32(highLetter);
String latBandLetters = mgrs.LongZone / 2.0 == Math.Floor(mgrs.LongZone / 2.0) ? digraphLettersAll.Substring(l + 5, h + 5).ToString() : digraphLettersAll.Substring(l, h).ToString();
Double nbase = 100000 * (lowLetter + latBandLetters.IndexOf(nltr));
//latBandLetters.IndexOf(nltr) value causing incorrect Northing below -80
Double x = ebase + mgrs.Easting;
Double y = nbase + mgrs.Northing;
if (y > 10000000) {
y -= 10000000;
}
if (nbase >= 10000000) {
y = nbase + mgrs.Northing - 10000000;
}
_ = nbase < 10000000;
UniversalTransverseMercator utm = new UniversalTransverseMercator(mgrs.LatZone, mgrs.LongZone, x, y) {
equatorial_radius = mgrs.equatorialRadius,
inverse_flattening = mgrs.inverseFlattening
};
Coordinate c = UniversalTransverseMercator.ConvertUTMtoLatLong(utm);
c.Set_Datum(mgrs.equatorialRadius, mgrs.inverseFlattening);
return c;
}
/// <summary>
/// MGRS Default String Format
/// </summary>
/// <returns>MGRS Formatted Coordinate String</returns>
public override String ToString() => !this.WithinCoordinateSystemBounds ? "" : this.LongZone.ToString() + this.LatZone + " " + this.Digraph + " " + ((Int32)this.Easting).ToString("00000") + " " + ((Int32)this.Northing).ToString("00000");
//MGRS Coordinate is outside its reliable boundaries. Return empty.
/// <summary>
/// Property changed event
/// </summary>
public event PropertyChangedEventHandler PropertyChanged;
/// <summary>
/// Notify property changed
/// </summary>
/// <param name="propName">Property name</param>
public void NotifyPropertyChanged(String propName) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propName));
}
}