Init CordinateSharp as library

This commit is contained in:
Philip Schell 2019-06-28 11:14:53 +02:00
commit 570ec853de
24 changed files with 46950 additions and 0 deletions

31
CoordinateSharp.sln Normal file
View File

@ -0,0 +1,31 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15
VisualStudioVersion = 15.0.27703.2026
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CoordinateSharp", "CoordinateSharp\CoordinateSharp.csproj", "{82B32704-3306-49FD-A7FA-DB48B67B7B64}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CoordinateSharp_TestProj", "CoordinateSharp_TestProj\CoordinateSharp_TestProj.csproj", "{B4E46A02-7C45-4A13-8D62-F99FCA853BDC}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{82B32704-3306-49FD-A7FA-DB48B67B7B64}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{82B32704-3306-49FD-A7FA-DB48B67B7B64}.Debug|Any CPU.Build.0 = Debug|Any CPU
{82B32704-3306-49FD-A7FA-DB48B67B7B64}.Release|Any CPU.ActiveCfg = Release|Any CPU
{82B32704-3306-49FD-A7FA-DB48B67B7B64}.Release|Any CPU.Build.0 = Release|Any CPU
{B4E46A02-7C45-4A13-8D62-F99FCA853BDC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B4E46A02-7C45-4A13-8D62-F99FCA853BDC}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B4E46A02-7C45-4A13-8D62-F99FCA853BDC}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B4E46A02-7C45-4A13-8D62-F99FCA853BDC}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {580EC371-2895-4067-9F0F-9E3E9ABED545}
EndGlobalSection
EndGlobal

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,412 @@
using System;
using System.Collections.Generic;
namespace CoordinateSharp
{
//CURRENT ALTITUDE IS SET CONSTANT AT 100M. POSSIBLY NEED TO ADJUST TO ALLOW USER PASS.
//Altitude adjustments appear to have minimal effect on eclipse timing. These were mainly used
//to signify eclipses that had already started during rise and set times on the NASA calculator
//SOME TIMES AND ALTS WERE RETURNED WITH COLOR AND STYLING. DETERMINE WHY AND ADJUST VALUE AS REQUIRED. SEARCH "WAS ITALIC".
//ELLIPSOID ADJUSTMENT
//6378140.0 Ellipsoid is used in the NASA Calculator
//WGS84 Ellipsoid is 6378137.0. Adjustments to the ellipsoid appear to effect eclipse seconds in fractions.
//This can be modified if need to allow users to pass custom number with the Coordinate SetDatum() functions.
//CURRENT RANGE 1601-2600.
internal class LunarEclipseCalc
{
public static List<List<string>> CalculateLunarEclipse(DateTime d, double latRad, double longRad)
{
return Calculate(d, latRad, longRad);
}
public static List<LunarEclipseDetails> CalculateLunarEclipse(DateTime d, double latRad, double longRad, double[] events)
{
List<List<string>> evs = Calculate(d, latRad, longRad, events);
List<LunarEclipseDetails> deetsList = new List<LunarEclipseDetails>();
foreach (List<string> ls in evs)
{
LunarEclipseDetails deets = new LunarEclipseDetails(ls);
deetsList.Add(deets);
}
return deetsList;
}
public static List<List<string>> CalculateLunarEclipse(DateTime d, Coordinate coord)
{
return Calculate(d, coord.Latitude.ToRadians(), coord.Longitude.ToRadians());
}
// CALCULATE!
private static List<List<string>> Calculate(DateTime d, double latRad, double longRad, double[] ev = null)
{
//DECLARE ARRAYS
double[] obsvconst = new double[6];
double[] mid = new double[41];
double[] p1 = new double[41];
double[] u1 = new double[41];
double[] u2 = new double[41];
double[] u3 = new double[41];
double[] u4 = new double[41];
double[] p4 = new double[41];
List<List<string>> events = new List<List<string>>();
double[] el;
if (ev == null)
{
el = Eclipse.LunarData.LunarDateData(d);//Get 100 year solar data;
}
else
{
el = ev;
}
events = new List<List<string>>();
ReadData(latRad, longRad, obsvconst);
for (int i = 0; i < el.Length; i += 22)
{
if (el[5 + i] <= obsvconst[5])
{
List<string> values = new List<string>();
obsvconst[4] = i;
GetAll(el, obsvconst, mid, p1, u1, u2,u3,u4,p4);
// Is there an event...
if (mid[5] != 1)
{
values.Add(GetDate(el, p1, obsvconst));
if (el[5 + i] == 1)
{
values.Add("T");
}
else if (el[5 + i] == 2)
{
values.Add("P");
}
else
{
values.Add("N");
}
// Pen. Mag
values.Add(el[3 + i].ToString());
// Umbral Mag
values.Add(el[4 + i].ToString());
// P1
values.Add(GetTime(el, p1, obsvconst));
// P1 alt
values.Add(GetAlt(p1));
if (u1[5] == 1)
{
values.Add("-");
values.Add("-");
}
else
{
// U1
values.Add(GetTime(el, u1, obsvconst));
// U1 alt
values.Add(GetAlt(u1));
}
if (u2[5] == 1)
{
values.Add("-");
values.Add("-");
}
else
{
// U2
values.Add(GetTime(el, u2, obsvconst));
// U2 alt
values.Add(GetAlt(u2));
}
// mid
values.Add(GetTime(el, mid, obsvconst));
// mid alt
values.Add(GetAlt(mid));
if (u3[5] == 1)
{
values.Add("-");
values.Add("-");
}
else
{
// u3
values.Add(GetTime(el, u3, obsvconst));
// u3 alt
values.Add(GetAlt(u3));
}
if (u4[5] == 1)
{
values.Add("-");
values.Add("-");
}
else
{
// u4
values.Add(GetTime(el, u4, obsvconst));
// u4 alt
values.Add(GetAlt(u4));
}
// P4
values.Add(GetTime(el, p4, obsvconst));
// P4 alt
values.Add(GetAlt(p4));
events.Add(values);
}
}
}
return events;
}
// Read the data that's in the form, and populate the obsvconst array
private static void ReadData(double latRad, double longRad, double[] obsvconst)
{
// Get the latitude
obsvconst[0] = latRad;
// Get the longitude
obsvconst[1] = -1 * longRad; //PASS REVERSE RADIAN.
// Get the altitude
obsvconst[2] = 100; //CHANGE TO ALLOW USER TO PASS.
// Get the time zone
obsvconst[3] = 0; //GMT TIME
obsvconst[4] = 0; //INDEX
//SET MAX ECLIPSE TYPE
obsvconst[5] = 4;//4 is ALL Eclipses
}
// Populate the p1, u1, u2, mid, u3, u4 and p4 arrays
private static void GetAll(double[] elements, double[] obsvconst, double[] mid, double[] p1, double[] u1, double[] u2, double[] u3, double[] u4, double[] p4)
{
int index = (int)obsvconst[4];
p1[1] = elements[index + 9];
PopulateCircumstances(elements, p1, obsvconst);
mid[1] = elements[index + 12];
PopulateCircumstances(elements, mid, obsvconst);
p4[1] = elements[index + 15];
PopulateCircumstances(elements, p4, obsvconst);
if (elements[index + 5] < 3)
{
u1[1] = elements[index + 10];
PopulateCircumstances(elements, u1, obsvconst);
u4[1] = elements[index + 14];
PopulateCircumstances(elements, u4, obsvconst);
if (elements[index + 5] < 2)
{
u2[1] = elements[index + 11];
u3[1] = elements[index + 13];
PopulateCircumstances(elements, u2, obsvconst);
PopulateCircumstances(elements, u3, obsvconst);
}
else
{
u2[5] = 1;
u3[5] = 1;
}
}
else
{
u1[5] = 1;
u2[5] = 1;
u3[5] = 1;
u4[5] = 1;
}
if ((p1[5] != 0) && (u1[5] != 0) && (u2[5] != 0) && (mid[5] != 0) && (u3[5] != 0) && (u4[5] != 0) && (p4[5] != 0))
{
mid[5] = 1;
}
}
// Populate the circumstances array
// entry condition - circumstances[1] must contain the correct value
private static void PopulateCircumstances(double[] elements, double[] circumstances, double[] obsvconst)
{
double t, ra, dec, h;
int index = (int)obsvconst[4];
t = circumstances[1];
ra = elements[18 + index] * t + elements[17 + index];
ra = ra * t + elements[16 + index];
dec = elements[21 + index] * t + elements[20 + index];
dec = dec * t + elements[19 + index];
dec = dec * Math.PI / 180.0;
circumstances[3] = dec;
h = 15.0 * (elements[6 + index] + (t - elements[2 + index] / 3600.0) * 1.00273791) - ra;
h = h * Math.PI / 180.0 - obsvconst[1];
circumstances[2] = h;
circumstances[4] = Math.Asin(Math.Sin(obsvconst[0]) * Math.Sin(dec) + Math.Cos(obsvconst[0]) * Math.Cos(dec) * Math.Cos(h));
circumstances[4] -= Math.Asin(Math.Sin(elements[7 + index] * Math.PI / 180.0) * Math.Cos(circumstances[4]));
if (circumstances[4] * 180.0 / Math.PI < elements[8 + index] - 0.5667)
{
circumstances[5] = 2;
}
else if (circumstances[4] < 0.0)
{
circumstances[4] = 0.0;
circumstances[5] = 0;
}
else
{
circumstances[5] = 0;
}
}
// Get the date of an event
private static string GetDate(double[] elements, double[] circumstances, double[] obsvconst)
{
string[] month = new string[] { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" };//Month string array
double t, jd, a, b, c, d, e;
string ans = "";
int index = (int)obsvconst[4];
// Calculate the JD for noon (TDT) the day before the day that contains T0
jd = Math.Floor(elements[index] - (elements[1 + index] / 24.0));
// Calculate the local time (ie the offset in hours since midnight TDT on the day containing T0).
t = circumstances[1] + elements[1 + index] - obsvconst[3] - (elements[2 + index] - 30.0) / 3600.0;
if (t < 0.0)
{
jd--;
}
if (t >= 24.0)
{
jd++;
}
if (jd >= 2299160.0)
{
a = Math.Floor((jd - 1867216.25) / 36524.25);
a = jd + 1 + a - Math.Floor(a / 4.0);
}
else
{
a = jd;
}
b = a + 1525.0;
c = Math.Floor((b - 122.1) / 365.25);
d = Math.Floor(365.25 * c);
e = Math.Floor((b - d) / 30.6001);
d = b - d - Math.Floor(30.6001 * e);
if (e < 13.5)
{
e = e - 1;
}
else
{
e = e - 13;
}
double year;
if (e > 2.5)
{
ans = c - 4716 + "-";
year = c - 4716;
}
else
{
ans = c - 4715 + "-";
year = c - 4715;
}
string m = month[(int)e - 1];
ans += m+ "-";
if (d < 10)
{
ans = ans + "0";
}
ans = ans + d;
//Leap Year Integrity Check
if (m == "Feb" && d == 29 && !DateTime.IsLeapYear((int)year))
{
ans = year.ToString() + "-Mar-01";
}
return ans;
}
// Get the time of an event
private static string GetTime(double[] elements, double[] circumstances, double[] obsvconst)
{
double t;
string ans = "";
int index = (int)obsvconst[4];
t = circumstances[1] + elements[1 + index] - obsvconst[3] - (elements[2 + index] - 30.0) / 3600.0;
if (t < 0.0)
{
t = t + 24.0;
}
if (t >= 24.0)
{
t = t - 24.0;
}
if (t < 10.0)
{
ans = ans + "0";
}
ans = ans + Math.Floor(t) + ":";
t = (t * 60.0) - 60.0 * Math.Floor(t);
if (t < 10.0)
{
ans = ans + "0";
}
ans = ans + Math.Floor(t);
if (circumstances[5] == 2)
{
return ans; //RETURNED IN ITAL DETERMINE WHY
}
else
{
return ans;
}
}
// Get the altitude
private static string GetAlt(double[] circumstances)
{
double t;
string ans = "";
t = circumstances[4] * 180.0 / Math.PI;
t = Math.Floor(t + 0.5);
if (t < 0.0)
{
ans = "-";
t = -t;
}
else
{
ans = "+";
}
if (t < 10.0)
{
ans = ans + "0";
}
ans = ans + t;
if (circumstances[5] == 2)
{
return ans; //returned in italics determine why
}
else
{
return ans;
}
}
}
}

View File

@ -0,0 +1,221 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace CoordinateSharp
{
internal partial class MeeusTables
{
//Ch 47
private static double[] Table47A_Arguments = new double[]
{
0,0,1,0,
2,0,-1,0,
2,0,0,0,
0,0,2,0,
0,1,0,0,
0,0,0,2,
2,0,-2,0,
2,-1,-1,0,
2,0,1,0,
2,-1,0,0,
0,1,-1,0,
1,0,0,0,
0,1,1,0,
2,0,0,-2,
0,0,1,2,
0,0,1,-2,
4,0,-1,0,
0,0,3,0,
4,0,-2,0,
2,1,-1,0,
2,1,0,0,
1,0,-1,0,
1,1,0,0,
2,-1,1,0,
2,0,2,0,
4,0,0,0,
2,0,-3,0,
0,1,-2,0,
2,0,-1,2,
2,-1,-2,0,
1,0,1,0,
2,-2,0,0,
0,1,2,0,
0,2,0,0,
2,-2,-1,0,
2,0,1,-2,
2,0,0,2,
4,-1,-1,0,
0,0,2,2,
3,0,-1,0,
2,1,1,0,
4,-1,-2,0,
0,2,-1,0,
2,2,-1,0,
2,1,-2,0,
2,-1,0,-2,
4,0,1,0,
0,0,4,0,
4,-1,0,0,
1,0,-2,0,
2,1,0,-2,
0,0,2,-2,
1,1,1,0,
3,0,-2,0,
4,0,-3,0,
2,-1,2,0,
0,2,1,0,
1,1,-1,0,
2,0,3,0,
2,0,-1,-2
};
private static double[] Table47B_Arguments = new double[]
{
0,0,0,1,
0,0,1,1,
0,0,1,-1,
2,0,0,-1,
2,0,-1,1,
2,0,-1,-1,
2,0,0,1,
0,0,2,1,
2,0,1,-1,
0,0,2,-1,
2,-1,0,-1,
2,0,-2,-1,
2,0,1,1,
2,1,0,-1,
2,-1,-1,1,
2,-1,0,1,
2,-1,-1,-1,
0,1,-1,-1,
4,0,-1,-1,
0,1,0,1,
0,0,0,3,
0,1,-1,1,
1,0,0,1,
0,1,1,1,
0,1,1,-1,
0,1,0,-1,
1,0,0,-1,
0,0,3,1,
4,0,0,-1,
4,0,-1,1,
0,0,1,-3,
4,0,-2,1,
2,0,0,-3,
2,0,2,-1,
2,-1,1,-1,
2,0,-2,1,
0,0,3,-1,
2,0,2,1,
2,0,-3,-1,
2,1,-1,1,
2,1,0,1,
4,0,0,1,
2,-1,1,1,
2,-2,0,-1,
0,0,1,3,
2,1,1,-1,
1,1,0,-1,
1,1,0,1,
0,1,-2,-1,
2,1,-1,-1,
1,0,1,1,
2,-1,-2,-1,
0,1,2,1,
4,0,-2,-1,
4,-1,-1,-1,
1,0,1,-1,
4,0,1,-1,
1,0,-1,-1,
4,-1,0,-1,
2,-2,0,1,
};
private static double[] Table47A_El_Er = new double[]
{
//El
6288774, 1274027,658314,213618,-185116,-114332,58793,57066,53322,45758,
-40923,-34720,-30383,15327,-12528,10980,10675,10034,8548,-7888,-6766,-5163,
4987,4036,3994,3861,3665,-2689,-2602,2390,-2348,2236,-2120,-2069,2048,-1773,
-1595,1215,-1110,-892,-810,759,-713,-700,691,596,549,537,520,-487,-399,-381,
351,-340,330,327,-323,299,294,0,
//Er
-20905355,-3699111,-2955968,-569925,48888,-3149,246158,-152138,-170733,-204586,
-129620,108743,104755,10321,0,79661,-34782,-23210,-21636,24208,30824,-8379,-16675,
-12831,-10445,-11650,14403,-7003,0,10056,6322,-9884,5751,0,-4950,4130,0,-3958,0,3258,
2616,-1897,-2117,2354,0,0,-1423,-1117,-1571,-1739,0,-4421,0,0,0,0,1165,0,0,8752
};
private static double[] Table47B_Eb = new double[]
{
5128122,280602,277693,173237,55413,46271,32573,17198,9266,8822,
8216,4324,4200,-3359,2463,2211,2065,-1870,1828,-1794,-1749,-1565,-1491,
-1475,-1410,-1344,-1335,1107,1021,833,
777,671,607,596,491,-451,439,422,421,-366,-351,331,315,302,-283,-229,
223,223,-220,-220,-185,181,-177,176,166,-164,132,-119,115,107
};
private static double Get_Table47A_Values(double[] values, int l, double t, bool sine)
{
//sine true returns El
//sine false return Er
//Er values start at 60 in the Table47A_El_Er array.
int nl = l * 4;
if (sine)
{
double e = 1;
if (Table47A_Arguments[nl + 1] != 0)
{
e = 1 - .002516 * t - .0000074 * Math.Pow(t, 2);
if (Math.Abs(Table47A_Arguments[nl + 1]) == 2)
{
e *= e;
}
}
return (Table47A_El_Er[l] * e) * Math.Sin(Table47A_Arguments[nl] * values[0] + Table47A_Arguments[nl + 1] * values[1] +
Table47A_Arguments[nl + 2] * values[2] + Table47A_Arguments[nl + 3] * values[3]);
}
else
{
double e = 1;
if (Table47A_Arguments[nl + 1] != 0)
{
e = 1 - .002516 * t - .0000074 * Math.Pow(t, 2);
if (Math.Abs(Table47A_Arguments[nl + 1]) == 2)
{
e *= e;
}
}
return (Table47A_El_Er[l + 60] * e) * Math.Cos(Table47A_Arguments[nl] * values[0] + Table47A_Arguments[nl + 1] * values[1] +
Table47A_Arguments[nl + 2] * values[2] + Table47A_Arguments[nl + 3] * values[3]);
}
}
private static double Get_Table47B_Values(double[] values, int l, double t)
{
int nl = l * 4;
double e = 1;
if (Table47B_Arguments[nl + 1] != 0)
{
e = 1 - .002516 * t - .0000074 * Math.Pow(t, 2);
if (Math.Abs(Table47B_Arguments[nl + 1]) == 2)
{
e *= e;
}
}
return (Table47B_Eb[l] * e) * Math.Sin(Table47B_Arguments[nl] * values[0] + Table47B_Arguments[nl + 1] * values[1] +
Table47B_Arguments[nl + 2] * values[2] + Table47B_Arguments[nl + 3] * values[3]);
}
}
}

View File

@ -0,0 +1,872 @@
using System;
using System.Collections.Generic;
namespace CoordinateSharp
{
internal class MoonCalc
{
static double rad = Math.PI / 180; //For converting radians
//obliquity of the ecliptic in radians based on standard equinox 2000.
static double e = rad * 23.4392911;
/// <summary>
/// Gets Moon Times, Altitude and Azimuth
/// </summary>
/// <param name="date">Date</param>
/// <param name="lat">Latitude</param>
/// <param name="lng">Longitude</param>
/// <param name="c">Celestial</param>
public static void GetMoonTimes(DateTime date, double lat, double lng, Celestial c)
{
//Get current Moon Position to populate passed Alt / Azi for user specified date
MoonPosition mp = GetMoonPosition(date, lat, lng, c);
double altRad = mp.Altitude / Math.PI*180; //Convert alt to degrees
c.moonAltitude = (altRad - mp.ParallaxCorection); //Set altitude with adjusted parallax
c.moonAzimuth = mp.Azimuth / Math.PI*180 + 180; //Azimuth in degrees + 180 for E by N.
////New Iterations for Moon set / rise
bool moonRise = false;
bool moonSet = false;
//Start at beginning of day
DateTime t = new DateTime(date.Year, date.Month, date.Day, 0, 0, 0, DateTimeKind.Utc);
//Get start of day Moon Pos
MoonPosition moonPos = GetMoonPosition(t, lat, lng, c);
double alt1 = moonPos.Altitude - (moonPos.ParallaxCorection * rad);
DateTime? setTime = null;
DateTime? riseTime = null;
double hz = -.3 * rad;//Horizon degrees at -.3 for appearant rise / set
//Iterate for each hour of the day
for(int x = 1;x<=24;x++)
{
moonPos = GetMoonPosition(t.AddHours(x), lat, lng, c);//Get the next hours altitude for comparison
double alt2 = moonPos.Altitude - (moonPos.ParallaxCorection * rad);
//If hour 1 is below horizon and hour 2 is above
if(alt1 <hz && alt2 >=hz)
{
//Moon Rise Occurred
moonRise = true;
DateTime dt1 = t.AddHours(x - 1);
moonPos = GetMoonPosition(dt1, lat, lng, c);//Get the next hours altitude for comparison
double altM1 = moonPos.Altitude - (moonPos.ParallaxCorection * rad);
//Iterate through each minute to determine at which minute the horizon is crossed.
//Interpolation is more efficient, but yielded results with deviations up to 5 minutes.
//Investigate formula efficiency
for (int y = 1;y<=60;y++)
{
DateTime dt2 = t.AddHours(x-1).AddMinutes(y);
moonPos = GetMoonPosition(dt2, lat, lng, c);//Get the next hours altitude for comparison
double altM2 = moonPos.Altitude - (moonPos.ParallaxCorection * rad);
if (altM1<hz && altM2>=hz)
{
//interpolate seconds
double p = 60 * ((hz - altM1) / (altM2 - altM1));
riseTime = dt1.AddMinutes(y-1).AddSeconds(p);
break;
}
altM1 = altM2;
}
}
//if hour 2 is above horizon and hour 1 below
if(alt1>=hz && alt2 <hz)
{
//Moon Set Occured
moonSet = true;
DateTime dt1 = t.AddHours(x - 1);
moonPos = GetMoonPosition(dt1, lat, lng, c);//Get the next hours altitude for comparison
double altM1 = moonPos.Altitude - (moonPos.ParallaxCorection * rad);
//Iterate through each minute to determine at which minute the horizon is crossed.
//Interpolation is more efficient, but yielded results with deviations up to 5 minutes.
//Investigate formula efficiency
for (int y = 1; y <= 60; y++)
{
DateTime dt2 = t.AddHours(x - 1).AddMinutes(y);
moonPos = GetMoonPosition(dt2, lat, lng, c);//Get the next hours altitude for comparison
double altM2 = moonPos.Altitude - (moonPos.ParallaxCorection * rad);
if (altM1 >= hz && altM2 < hz)
{
//Interpolate seconds
double p = 60 * ((hz - altM2) / (altM1 - altM2));
setTime = dt1.AddMinutes(y).AddSeconds(-p);
break;
}
altM1 = altM2;
}
}
alt1 = alt2;
if(moonRise && moonSet) { break; }
}
c.moonSet = setTime;
c.moonRise = riseTime;
if (moonRise && moonSet) { c.moonCondition = CelestialStatus.RiseAndSet; }
else
{
if (!moonRise && !moonSet)
{
if (alt1 >= 0) { c.moonCondition = CelestialStatus.UpAllDay; }
else { c.moonCondition = CelestialStatus.DownAllDay; }
}
if (!moonRise && moonSet) { c.moonCondition = CelestialStatus.NoRise; }
if (moonRise && !moonSet) { c.moonCondition = CelestialStatus.NoSet; }
}
}
private static MoonPosition GetMoonPosition(DateTime date, double lat, double lng, Celestial cel)
{
//Set UTC date integrity
date = new DateTime(date.Year, date.Month, date.Day, date.Hour, date.Minute, date.Second, DateTimeKind.Utc);
double d = JulianConversions.GetJulian_Epoch2000(date);
//Ch 47
double JDE = JulianConversions.GetJulian(date);//Get julian
double T = (JDE - 2451545) / 36525; //Get dynamic time.
double[] LDMNF = Get_Moon_LDMNF(T);
CelCoords c = GetMoonCoords(d, cel, LDMNF, T);
Distance dist = GetMoonDistance(date);
double lw = rad * -lng;
double phi = rad * lat;
double H = rad * MeeusFormulas.Get_Sidereal_Time(JDE) - lw - c.ra;
double ra = c.ra; //Adjust current RA formula to avoid needless RAD conversions
double dec = c.dec; //Adjust current RA formula to avoid needless RAD conversions
//Adjust for parallax (low accuracry increases may not be worth cost)
//Investigate
double pSinE = Get_pSinE(dec, dist.Meters) * Math.PI / 180;
double pCosE = Get_pCosE(dec, dist.Meters) * Math.PI / 180;
double cRA = Parallax_RA(dist.Meters, H, pCosE, dec, ra);
double tDEC = Parallax_Dec(dist.Meters, H, pCosE, pSinE, dec, cRA);
double tRA = ra - cRA;
dec = tDEC;
ra = tRA;
//Get true altitude
double h = altitude(H, phi, dec);
// formula 14.1 of "Astronomical Algorithms" 2nd edition by Jean Meeus (Willmann-Bell, Richmond) 1998.
double pa = Math.Atan2(Math.Sin(H), Math.Tan(phi) * Math.Cos(dec) - Math.Sin(dec) * Math.Cos(H));
//altitude correction for refraction
h = h + astroRefraction(h);
MoonPosition mp = new MoonPosition();
mp.Azimuth = azimuth(H, phi, dec);
mp.Altitude = h / Math.PI * 180;
mp.Distance = dist;
mp.ParallacticAngle = pa;
double horParal = 8.794 / (dist.Meters / 149.59787E6); // horizontal parallax (arcseconds), Meeus S. 263
double p = Math.Asin(Math.Cos(h) * Math.Sin(horParal/3600)); // parallax in altitude (degrees)
p *= 1000;
mp.ParallaxCorection = p;
mp.Altitude *= rad;
return mp;
}
private static CelCoords GetMoonCoords(double d, Celestial c, double[] LDMNF, double t)
{
// Legacy function. Updated with Meeus Calcs for increased accuracy.
// geocentric ecliptic coordinates of the moon
// Meeus Ch 47
double[] cs = Get_Moon_Coordinates(LDMNF, t);
double l = cs[0]; // longitude
double b = cs[1]; // latitude
CelCoords mc = new CelCoords();
mc.ra = rightAscension(l, b);
double ra = mc.ra / Math.PI * 180;
mc.dec = declination(l, b);
double dec = mc.dec / Math.PI * 180;
return mc;
}
public static void GetMoonIllumination(DateTime date, Celestial c, double lat, double lng)
{
date = new DateTime(date.Year, date.Month, date.Day, date.Hour, date.Minute, date.Second, DateTimeKind.Utc);
double d = JulianConversions.GetJulian_Epoch2000(date);
CelCoords s = GetSunCoords(d);
double JDE = JulianConversions.GetJulian(date);//Get julian
double T = (JDE - 2451545) / 36525; //Get dynamic time.
double[] LDMNF = Get_Moon_LDMNF(T);
CelCoords m = GetMoonCoords(d, c,LDMNF, T);
double sdist = 149598000,
phi = Math.Acos(Math.Sin(s.dec) * Math.Sin(m.dec) + Math.Cos(s.dec) * Math.Cos(m.dec) * Math.Cos(s.ra - m.ra)),
inc = Math.Atan2(sdist * Math.Sin(phi), m.dist - sdist * Math.Cos(phi)),
angle = Math.Atan2(Math.Cos(s.dec) * Math.Sin(s.ra - m.ra), Math.Sin(s.dec) * Math.Cos(m.dec) -
Math.Cos(s.dec) * Math.Sin(m.dec) * Math.Cos(s.ra - m.ra));
MoonIllum mi = new MoonIllum();
mi.Fraction = (1 + Math.Cos(inc)) / 2;
mi.Phase = 0.5 + 0.5 * inc * (angle < 0 ? -1 : 1) / Math.PI;
mi.Angle = angle;
c.moonIllum = mi;
string moonName = "";
int moonDate = 0;
//GET PHASE NAME
//CHECK MOON AT BEGINNING AT END OF DAY TO GET DAY PHASE
DateTime dMon = new DateTime(date.Year, date.Month, 1);
for(int x = 1;x<= date.Day;x++)
{
DateTime nDate = new DateTime(dMon.Year, dMon.Month, x, 0, 0, 0, DateTimeKind.Utc);
d = JulianConversions.GetJulian_Epoch2000(nDate);
s = GetSunCoords(d);
JDE = JulianConversions.GetJulian(nDate);//Get julian
T = (JDE - 2451545) / 36525; //Get dynamic time.
LDMNF = Get_Moon_LDMNF(T);
m = GetMoonCoords(d, c,LDMNF,T);
phi = Math.Acos(Math.Sin(s.dec) * Math.Sin(m.dec) + Math.Cos(s.dec) * Math.Cos(m.dec) * Math.Cos(s.ra - m.ra));
inc = Math.Atan2(sdist * Math.Sin(phi), m.dist - sdist * Math.Cos(phi));
angle = Math.Atan2(Math.Cos(s.dec) * Math.Sin(s.ra - m.ra), Math.Sin(s.dec) * Math.Cos(m.dec) -
Math.Cos(s.dec) * Math.Sin(m.dec) * Math.Cos(s.ra - m.ra));
double startPhase = 0.5 + 0.5 * inc * (angle < 0 ? -1 : 1) / Math.PI;
nDate = new DateTime(dMon.Year, dMon.Month, x, 23, 59, 59, DateTimeKind.Utc);
d = JulianConversions.GetJulian_Epoch2000(nDate);
s = GetSunCoords(d);
JDE = JulianConversions.GetJulian(nDate);//Get julian
T = (JDE - 2451545) / 36525; //Get dynamic time.
LDMNF = Get_Moon_LDMNF(T);
m = GetMoonCoords(d, c,LDMNF,T);
phi = Math.Acos(Math.Sin(s.dec) * Math.Sin(m.dec) + Math.Cos(s.dec) * Math.Cos(m.dec) * Math.Cos(s.ra - m.ra));
inc = Math.Atan2(sdist * Math.Sin(phi), m.dist - sdist * Math.Cos(phi));
angle = Math.Atan2(Math.Cos(s.dec) * Math.Sin(s.ra - m.ra), Math.Sin(s.dec) * Math.Cos(m.dec) -
Math.Cos(s.dec) * Math.Sin(m.dec) * Math.Cos(s.ra - m.ra));
double endPhase = 0.5 + 0.5 * inc * (angle < 0 ? -1 : 1) / Math.PI;
//Determine Moon Name.
if (startPhase <= .5 && endPhase >= .5)
{
moonDate = x;
moonName = GetMoonName(dMon.Month, moonName);
}
//Get Moon Name (month, string);
//Get Moon Phase Name
if (date.Day == x)
{
if (startPhase > endPhase)
{
mi.PhaseName = "New Moon";
break;
}
if (startPhase <= .25 && endPhase >= .25)
{
mi.PhaseName = "First Quarter";
break;
}
if (startPhase <= .5 && endPhase >= .5)
{
mi.PhaseName = "Full Moon";
break;
}
if (startPhase <= .75 && endPhase >= .75)
{
mi.PhaseName = "Last Quarter";
break;
}
if (startPhase > 0 && startPhase < .25 && endPhase > 0 && endPhase < .25)
{
mi.PhaseName = "Waxing Crescent";
break;
}
if (startPhase > .25 && startPhase < .5 && endPhase > .25 && endPhase < .5)
{
mi.PhaseName = "Waxing Gibbous";
break;
}
if (startPhase > .5 && startPhase < .75 && endPhase > .5 && endPhase < .75)
{
mi.PhaseName = "Waning Gibbous";
break;
}
if (startPhase > .75 && startPhase < 1 && endPhase > .75 && endPhase < 1)
{
mi.PhaseName = "Waning Crescent";
break;
}
}
}
if (date.Day == moonDate)
{
c.AstrologicalSigns.MoonName = moonName;
}
else { c.AstrologicalSigns.MoonName = ""; }
CalculateLunarEclipse(date, lat, lng, c);
}
public static void CalculateLunarEclipse(DateTime date, double lat, double longi, Celestial c)
{
//Convert to Radian
double latR = lat * Math.PI / 180;
double longR = longi * Math.PI / 180;
List<List<string>> se = LunarEclipseCalc.CalculateLunarEclipse(date, latR, longR);
//RETURN FIRST AND LAST
if (se.Count == 0) { return; }
//FIND LAST AND NEXT ECLIPSE
int lastE = -1;
int nextE = -1;
int currentE = 0;
DateTime lastDate = new DateTime();
DateTime nextDate = new DateTime(3300, 1, 1);
//Iterate to get last and next eclipse
foreach (List<string> values in se)
{
DateTime ld = DateTime.ParseExact(values[0], "yyyy-MMM-dd", System.Globalization.CultureInfo.InvariantCulture);
if (ld < date && ld > lastDate) { lastDate = ld; lastE = currentE; }
if (ld >= date && ld < nextDate) { nextDate = ld; nextE = currentE; }
currentE++;
}
//SET ECLIPSE DATA
if (lastE >= 0)
{
c.LunarEclipse.LastEclipse = new LunarEclipseDetails(se[lastE]);
}
if (nextE >= 0)
{
c.LunarEclipse.NextEclipse = new LunarEclipseDetails(se[nextE]);
}
}
private static string GetMoonName(int month, string name)
{
if (name != "") { return "Blue Moon"; }
switch (month)
{
case 1:
return "Wolf Moon";
case 2:
return "Snow Moon";
case 3:
return "Worm Moon";
case 4:
return "Pink Moon";
case 5:
return "Flower Moon";
case 6:
return "Strawberry Moon";
case 7:
return "Buck Moon";
case 8:
return "Sturgeon Moon";
case 9:
return "Corn Moon";
case 10:
return "Hunters Moon";
case 11:
return "Beaver Moon";
case 12:
return "Cold Moon";
default:
return "";
}
}
public static void GetMoonDistance(DateTime date, Celestial c)
{
date = new DateTime(date.Year, date.Month, date.Day, date.Hour, date.Minute, date.Second, DateTimeKind.Utc);
c.moonDistance = GetMoonDistance(date); //Updating distance formula
}
//Moon Time Functions
private static CelCoords GetSunCoords(double d)
{
double M = solarMeanAnomaly(d),
L = eclipticLongitude(M);
CelCoords c = new CelCoords();
c.dec = declination(L, 0);
c.ra = rightAscension(L, 0);
return c;
}
private static double solarMeanAnomaly(double d) { return rad * (357.5291 + 0.98560028 * d); }
private static double eclipticLongitude(double M)
{
double C = rad * (1.9148 * Math.Sin(M) + 0.02 * Math.Sin(2 * M) + 0.0003 * Math.Sin(3 * M)), // equation of center
P = rad * 102.9372; // perihelion of the Earth
return M + C + P + Math.PI;
}
public static void GetMoonSign(DateTime date, Celestial c)
{
//Formulas taken from https://www.astrocal.co.uk/moon-sign-calculator/
double d = date.Day;
double m = date.Month;
double y = date.Year;
double hr = date.Hour;
double mi = date.Minute;
double f = hr + (mi / 60);
double im = 12 * (y + 4800) + m - 3;
double j = (2 * (im - Math.Floor(im / 12) * 12) + 7 + 365 * im) / 12;
j = Math.Floor(j) + d + Math.Floor(im / 48) - 32083;
double jd = j + Math.Floor(im / 4800) - Math.Floor(im / 1200) + 38;
double T = ((jd - 2415020) + f / 24 - .5) / 36525;
double ob = FNr(23.452294 - .0130125 * T);
double ll = 973563 + 1732564379 * T - 4 * T * T;
double g = 1012395 + 6189 * T;
double n = 933060 - 6962911 * T + 7.5 * T * T;
double g1 = 1203586 + 14648523 * T - 37 * T * T;
d = 1262655 + 1602961611 * T - 5 * T * T;
double M = 3600;
double l = (ll - g1) / M;
double l1 = ((ll - d) - g) / M;
f = (ll - n) / M;
d = d / M;
y = 2 * d;
double ml = 22639.6 * FNs(l) - 4586.4 * FNs(l - y);
ml = ml + 2369.9 * FNs(y) + 769 * FNs(2 * l) - 669 * FNs(l1);
ml = ml - 411.6 * FNs(2 * f) - 212 * FNs(2 * l - y);
ml = ml - 206 * FNs(l + l1 - y) + 192 * FNs(l + y);
ml = ml - 165 * FNs(l1 - y) + 148 * FNs(l - l1) - 125 * FNs(d);
ml = ml - 110 * FNs(l + l1) - 55 * FNs(2 * f - y);
ml = ml - 45 * FNs(l + 2 * f) + 40 * FNs(l - 2 * f);
double tn = n + 5392 * FNs(2 * f - y) - 541 * FNs(l1) - 442 * FNs(y);
tn = tn + 423 * FNs(2 * f) - 291 * FNs(2 * l - 2 * f);
g = FNu(FNp(ll + ml));
double sign = Math.Floor(g / 30);
double degree = (g - (sign * 30));
sign = sign + 1;
switch (sign.ToString())
{
case "1": c.AstrologicalSigns.MoonSign = "Aries"; break;
case "2": c.AstrologicalSigns.MoonSign = "Taurus"; break;
case "3": c.AstrologicalSigns.MoonSign = "Gemini"; break;
case "4": c.AstrologicalSigns.MoonSign = "Cancer"; break;
case "5": c.AstrologicalSigns.MoonSign = "Leo"; break;
case "6": c.AstrologicalSigns.MoonSign = "Virgo"; break;
case "7": c.AstrologicalSigns.MoonSign = "Libra"; break;
case "8": c.AstrologicalSigns.MoonSign = "Scorpio"; break;
case "9": c.AstrologicalSigns.MoonSign = "Sagitarius"; break;
case "10": c.AstrologicalSigns.MoonSign = "Capricorn"; break;
case "11": c.AstrologicalSigns.MoonSign = "Aquarius"; break;
case "12": c.AstrologicalSigns.MoonSign = "Pisces"; break;
default: c.AstrologicalSigns.MoonSign = "Pisces"; break;
}
}
private static double FNp(double x)
{
double sgn;
if (x < 0)
{ sgn = -1; }
else
{ sgn = 1; }
return sgn * ((Math.Abs(x) / 3600) / 360 - Math.Floor((Math.Abs(x) / 3600.0) / 360.0)) * 360;
}
private static double FNu(double x)
{ return x - (Math.Floor(x / 360) * 360); }
private static double FNr(double x)
{ return Math.PI / 180 * x; }
private static double FNs(double x)
{ return Math.Sin(Math.PI / 180 * x); }
//v1.1.3 Formulas
//The following formulas are either additions
//or conversions of SunCalcs formulas into Meeus
/// <summary>
/// Grabs Perigee or Apogee of Moon based on specified time.
/// Results will return event just before, or just after specified DateTime
/// </summary>
/// <param name="d">DateTime</param>
/// <param name="md">Event Type</param>
/// <returns>PerigeeApogee</returns>
private static PerigeeApogee MoonPerigeeOrApogee(DateTime d, MoonDistanceType md)
{
//Perigee & Apogee Algorithms from Jean Meeus Astronomical Algorithms Ch. 50
//50.1
//JDE = 2451534.6698 + 27.55454989 * k
// -0.0006691 * Math.Pow(T,2)
// -0.000.01098 * Math.Pow(T,3)
// -0.0000000052 * Math.Pow(T,4)
//50.2
//K approx = (yv - 1999.97)*13.2555
//yv is the year + percentage of days that have occured in the year. 1998 Oct 1 is approx 1998.75
//k ending in .0 represent perigee and .5 apogee. Anything > .5 is an error.
//50.3
//T = k/1325.55
double yt = 365; //days in year
if (DateTime.IsLeapYear(d.Year)) { yt = 366; } //days in year if leap year
double f = d.DayOfYear / yt; //Get percentage of year that as passed
double yv = d.Year + f; //add percentage of year passed to year.
double k = (yv - 1999.97) * 13.2555; //find approximate k using formula 50.2
//Set k decimal based on apogee or perigee
if (md == MoonDistanceType.Apogee)
{
k = Math.Floor(k) + .5;
}
else
{
k = Math.Floor(k);
}
//Find T using formula 50.3
double T = k / 1325.55;
//Find JDE using formula 50.1
double JDE = 2451534.6698 + 27.55454989 * k -
0.0006691 * Math.Pow(T, 2) -
0.00001098 * Math.Pow(T, 3) -
0.0000000052 * Math.Pow(T, 4);
//Find Moon's mean elongation at time JDE.
double D = 171.9179 + 335.9106046 * k -
0.0100383 * Math.Pow(T, 2) -
0.00001156 * Math.Pow(T, 3) +
0.000000055 * Math.Pow(T, 4);
//Find Sun's mean anomaly at time JDE
double M = 347.3477 + 27.1577721 * k -
0.0008130 * Math.Pow(T, 2) -
0.0000010 * Math.Pow(T, 3);
//Find Moon's argument of latitude at Time JDE
double F = 316.6109 + 364.5287911 * k -
0.0125053 * Math.Pow(T, 2) -
0.0000148 * Math.Pow(T, 3);
//Normalize DMF to a 0-360 degree number
D %= 360;
if (D < 0) { D += 360; }
M %= 360;
if (M < 0) { M += 360; }
F %= 360;
if (F < 0) { F += 360; }
//Convert DMF to radians
D = D * Math.PI / 180;
M = M * Math.PI / 180;
F = F * Math.PI / 180;
double termsA;
//Find Terms A from Table 50.A
if (md == MoonDistanceType.Apogee)
{
termsA = MeeusTables.ApogeeTermsA(D, M, F, T);
}
else
{
termsA = MeeusTables.PerigeeTermsA(D, M, F, T);
}
JDE += termsA;
double termsB;
if (md == MoonDistanceType.Apogee)
{
termsB = MeeusTables.ApogeeTermsB(D, M, F, T);
}
else
{
termsB = MeeusTables.PerigeeTermsB(D, M, F, T);
}
//Convert julian back to date
DateTime date = JulianConversions.GetDate_FromJulian(JDE).Value;
//Obtain distance
Distance dist = GetMoonDistance(date);
PerigeeApogee ap = new PerigeeApogee(date, termsB, dist);
return ap;
}
public static Perigee GetPerigeeEvents(DateTime d)
{
//Iterate in 15 day increments due to formula variations.
//Determine closest events to date.
//per1 is last date
//per2 is next date
//integrity for new date.
if (d.Year <= 0001) { return new Perigee(new PerigeeApogee(new DateTime(), 0, new Distance(0)), new PerigeeApogee(new DateTime(), 0, new Distance(0))); }
//Start at lowest increment
PerigeeApogee per1 = MoonPerigeeOrApogee(d.AddDays(-45), MoonDistanceType.Perigee);
PerigeeApogee per2 = MoonPerigeeOrApogee(d.AddDays(-45), MoonDistanceType.Perigee);
for (int x = -30; x <= 45; x+=15)
{
//used for comparison
PerigeeApogee t = MoonPerigeeOrApogee(d.AddDays(x), MoonDistanceType.Perigee);
//Find the next pergiee after specified date
if (t.Date > per2.Date && t.Date >= d)
{
per2 = t;
break;
}
//Find last perigee before specified date
if (t.Date > per1.Date && t.Date < d)
{
per1 = t;
per2 = t;
}
}
return new Perigee(per1, per2);
}
public static Apogee GetApogeeEvents(DateTime d)
{
//Iterate in 5 month increments due to formula variations.
//Determine closest events to date.
//apo1 is last date
//apo2 is next date
//integrity for new date.
if (d.Year <= 0001) { return new Apogee(new PerigeeApogee(new DateTime(), 0, new Distance(0)), new PerigeeApogee(new DateTime(), 0, new Distance(0))); }
PerigeeApogee apo1 = MoonPerigeeOrApogee(d.AddDays(-45), MoonDistanceType.Apogee);
PerigeeApogee apo2 = MoonPerigeeOrApogee(d.AddDays(-45), MoonDistanceType.Apogee);
for (int x = -30; x <= 45; x+=15)
{
PerigeeApogee t = MoonPerigeeOrApogee(d.AddDays(x), MoonDistanceType.Apogee);
//Find next apogee after specified date
if (t.Date > apo2.Date && t.Date >= d)
{
apo2 = t;
break;
}
//Find last apogee before specified date
if (t.Date > apo1.Date && t.Date < d)
{
apo1 = t;
apo2 = t;
}
}
return new Apogee(apo1, apo2);
}
/// <summary>
/// Gets moon distance (Ch 47).
/// </summary>
/// <param name="d">DateTime</param>
/// <returns>Distance</returns>
public static Distance GetMoonDistance(DateTime d)
{
//Ch 47
double JDE = JulianConversions.GetJulian(d);//Get julian
double T = (JDE - 2451545) / 36525; //Get dynamic time.
double[] values = Get_Moon_LDMNF(T);
double D = values[1];
double M = values[2];
double N = values[3];
double F = values[4];
//Ch 47 distance formula
double dist = 385000.56 + (MeeusTables.Moon_Periodic_Er(D, M, N, F, T) / 1000);
return new Distance(dist);
}
private static Distance GetMoonDistance(DateTime d, double[] values)
{
//Ch 47
double JDE = JulianConversions.GetJulian(d);//Get julian
double T = (JDE - 2451545) / 36525; //Get dynamic time.
double D = values[1];
double M = values[2];
double N = values[3];
double F = values[4];
double dist = 385000.56 + (MeeusTables.Moon_Periodic_Er(D, M, N, F, T) / 1000);
return new Distance(dist);
}
/// <summary>
/// Gets Moon L, D, M, N, F values
/// Ch. 47
/// </summary>
/// <param name="T">Dynamic Time</param>
/// <returns>double[] containing L,D,M,N,F</returns>
static double[] Get_Moon_LDMNF(double T)
{
//T = dynamic time
//Moon's mean longitude
double L = 218.316447 + 481267.88123421 * T -
.0015786 * Math.Pow(T, 2) + Math.Pow(T, 3) / 538841 -
Math.Pow(T, 4) / 65194000;
//Moon's mean elongation
double D = 297.8501921 + 445267.1114034 * T -
0.0018819 * Math.Pow(T, 2) + Math.Pow(T, 3) / 545868 - Math.Pow(T, 4) / 113065000;
//Sun's mean anomaly
double M = 357.5291092 + 35999.0502909 * T -
.0001536 * Math.Pow(T, 2) + Math.Pow(T, 3) / 24490000;
//Moon's mean anomaly
double N = 134.9633964 + 477198.8675055 * T + .0087414 * Math.Pow(T, 2) +
Math.Pow(T, 3) / 69699 - Math.Pow(T, 4) / 14712000;
//Moon's argument of latitude
double F = 93.2720950 + 483202.0175233 * T - .0036539 * Math.Pow(T, 2) - Math.Pow(T, 3) /
3526000 + Math.Pow(T, 4) / 863310000;
//Normalize DMF to a 0-360 degree number
D %= 360;
if (D < 0) { D += 360; }
M %= 360;
if (M < 0) { M += 360; }
N %= 360;
if (N < 0) { N += 360; }
F %= 360;
if (F < 0) { F += 360; }
//Convert DMF to radians
D = D * Math.PI / 180;
M = M * Math.PI / 180;
N = N * Math.PI / 180;
F = F * Math.PI / 180;
return new double[] { L, D, M, N, F };
}
/// <summary>
/// Get moons lat/long in radians (Ch 47).
/// </summary>
/// <param name="LDMNF">L,D,M,N,F</param>
/// <param name="T">Dynamic Time</param>
/// <returns>Lat[0], Long[1]</returns>
private static double[] Get_Moon_Coordinates(double[] LDMNF,double T)
{
//Refence Ch 47.
double lat = LDMNF[0] + (MeeusTables.Moon_Periodic_El(LDMNF[0], LDMNF[1], LDMNF[2], LDMNF[3], LDMNF[4],T)/1000000);
double longi = MeeusTables.Moon_Periodic_Eb(LDMNF[0], LDMNF[1], LDMNF[2], LDMNF[3], LDMNF[4], T) / 1000000;
lat %= 360;
if (lat < 0) { lat += 360; }
//Convert to radians
double l = rad * lat; // longitude
double b = rad * longi; // latitude
return new double[] { l, b };
}
/// <summary>
/// Gets right Ascension of celestial object (Ch 13 Fig 13.3)
/// </summary>
/// <param name="l">latitude in radians</param>
/// <param name="b">longitude in radian</param>
/// <returns>Right Ascension</returns>
private static double rightAscension(double l, double b)
{
//Ch 13 Fig 13.3
//tan a = ( sin(l) * cos(e) - tan(b)-sin(e) ) / cons(l)
//Converts to the following using Atan2 for 4 quadriatic regions
return Math.Atan2(Math.Sin(l) * Math.Cos(e) - Math.Tan(b) * Math.Sin(e), Math.Cos(l));
}
/// <summary>
/// Gets declination of celestial object (Ch 13 Fig 13.4)
/// </summary>
/// <param name="l">latitude in radians</param>
/// <param name="b">longitude in radian</param>
/// <returns>Declination</returns>
private static double declination(double l, double b)
{
//Ch 13 Fig 13.4
//sin o = sin(b) * cos(e) + cos(b)*sin(e) * sin(l)
//Converts to the following using Asin
return Math.Asin(Math.Sin(b) * Math.Cos(e) + Math.Cos(b) * Math.Sin(e) * Math.Sin(l));
}
static double Parallax_Dec(double distance, double H, double pCosE, double pSinE, double dec, double cRA)
{
//Ch 40 (Correction for parallax
//H - geocentric hour angle of the body (sidereal) IAW Ch 12
double pi = Math.Asin((Math.Sin(8.794 / distance))) * Math.PI / 180; // 40.1 in radians
H = H * Math.PI / 180;
//Directly to topocencric dec
double tDEC = Math.Atan2((Math.Sin(dec) - pSinE * Math.Sin(pi)) * Math.Cos(cRA), Math.Cos(dec) - pCosE * Math.Sin(pi) * Math.Cos(H));
return tDEC;
}
static double Parallax_RA(double distance, double H, double pCosE, double dec, double ra)
{
//ENSURE RADIANS
//Ch 40 (Correction for parallax
//H - geocentric hour angle of the body (sidereal) IAW Ch 12
double pi = Math.Asin((Math.Sin(8.794 / distance))) * Math.PI / 180; // 40.1
//Convert to Radian
double t = -pCosE * Math.Sin(pi) * Math.Sin(H);
double b = Math.Cos(dec) - pCosE * Math.Sin(pi) * Math.Cos(H);
double cRA = Math.Atan2(t, b);
return cRA;
//Topocencric RA = RA - cRA
}
static double Get_pSinE(double dec, double H)
{
//ASSUME WGS 84 FOR NOW
double a = 6378.14;
double f = 1 / 298.257;
double b = a * (1 - f);
double ba = .99664719; // or 1-f
double u = (ba * dec) * Math.PI / 180;
double ps = ba * Math.Sin(u) + (H / 6378140) * Math.Sin(dec);
return ps;
}
static double Get_pCosE(double dec, double H)
{
//ASSUME WGS 84 FOR NOW
double a = 6378.14;
double f = 1 / 298.257;
double b = a * (1 - f);
double ba = .99664719; // or 1-f
double u = (ba * dec) * Math.PI / 180;
double ps = Math.Cos(u) + (H / 6378140) * Math.Cos(dec);
return ps;
}
static double azimuth(double H, double phi, double dec) { return Math.Atan2(Math.Sin(H), Math.Cos(H) * Math.Sin(phi) - Math.Tan(dec) * Math.Cos(phi)); }
static double altitude(double H, double phi, double dec)
{
return Math.Asin(Math.Sin(phi) * Math.Sin(dec) + Math.Cos(phi) * Math.Cos(dec) * Math.Cos(H));
}
static double astroRefraction(double h)
{
//CH 16
double P = 1013.25; //Average pressure of earth
double T = 16; //Average temp of earth
double alt = h / Math.PI * 180;
double Ref = P * (.1594 + .0196 * alt + .00002 * Math.Pow(alt, 2)) / ((273 + T) * (1 + .505 * alt + .0845 * Math.Pow(alt, 2)));
return Ref / 60;
}
}
}

View File

@ -0,0 +1,971 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace CoordinateSharp
{
//CURRENT ALTITUDE IS SET CONSTANT AT 100M. POSSIBLY NEED TO ADJUST TO ALLOW USER PASS.
//Altitude adjustments appear to have minimal effect on eclipse timing. These were mainly used
//to signify eclipses that had already started during rise and set times on the NASA calculator
//SOME TIMES AND ALTS WERE RETURNED WITH COLOR AND STYLING. DETERMINE WHY AND ADJUST VALUE AS REQUIRED. SEARCH "WAS ITALIC".
//ELLIPSOID ADJUSTMENT
//6378140.0 Ellipsoid is used in the NASA Calculator
//WGS84 Ellipsoid is 6378137.0. Adjustments to the ellipsoid appear to effect eclipse seconds in fractions.
//This can be modified if need to allow users to pass custom number with the Coordinate SetDatum() functions.
//CURRENT RANGE 1601-2600.
internal class SolarEclipseCalc
{
public static List<List<string>> CalculateSolarEclipse(DateTime d, double latRad, double longRad)
{
return Calculate(d, latRad, longRad, null);
}
public static List<SolarEclipseDetails> CalculateSolarEclipse(DateTime d, double latRad, double longRad, double[] events)
{
List<List<string>> evs = Calculate(d, latRad, longRad, events);
List<SolarEclipseDetails> deetsList = new List<SolarEclipseDetails>();
foreach(List<string> ls in evs)
{
SolarEclipseDetails deets = new SolarEclipseDetails(ls);
deetsList.Add(deets);
}
return deetsList;
}
public static List<List<string>> CalculateSolarEclipse(DateTime d, Coordinate coord)
{
return Calculate(d, coord.Latitude.ToRadians(), coord.Longitude.ToRadians(), null);
}
private static List<List<string>> Calculate(DateTime d, double latRad, double longRad, double[] ev)
{
//Declare storage arrays
double[] obsvconst = new double[7];
double[] mid = new double[41];//Check index to see if array needs to be this size
double[] c1 = new double[41];
double[] c2 = new double[41];
double[] c3 = new double[41];
double[] c4 = new double[41];
List<List<string>> events = new List<List<string>>();
double[] el;
if (ev == null)
{
el = Eclipse.SolarData.SolarDateData(d);//Get 100 year solar data;
}
else
{
el = ev;
}
events = new List<List<string>>();
ReadData(latRad, longRad, obsvconst);
for (int i = 0; i < el.Length; i += 28)
{
obsvconst[6] = i;
GetAll(el, obsvconst, mid,c1,c2,c3,c4);
// Is there an event...
if (mid[39] > 0)
{
List<string> values = new List<string>();
values.Add(GetDate(el, mid, obsvconst));
if (mid[39] == 1)
{
values.Add("P");
}
else if (mid[39] == 2)
{
values.Add("A");
}
else
{
values.Add("T");
}
// Partial eclipse start
if (c1[40] == 4)
{
values.Add("-");
values.Add(" ");
}
else
{
// Partial eclipse start time
values.Add(GetTime(el, c1, obsvconst));
values.Add(GetAlt(c1));
}
// Central eclipse time
if ((mid[39] > 1) && (c2[40] != 4))
{
values.Add(GetTime(el, c2, obsvconst));
}
else
{
values.Add("-");
}
//Mid Time
values.Add(GetTime(el, mid, obsvconst));
// Maximum eclipse alt
values.Add(GetAlt(mid));
// Maximum eclipse azi
values.Add(GetAzi(mid));
// Central eclipse ends
if ((mid[39] > 1) && (c3[40] != 4))
{
values.Add(GetTime(el, c3, obsvconst));
}
else
{
values.Add("-");
}
// Partial eclipse ends
if (c4[40] == 4)
{
values.Add("-");
values.Add(" ");
}
else
{
// Partial eclipse ends
values.Add(GetTime(el, c4, obsvconst));
// ... sun alt
values.Add(GetAlt(c4));
}
// Eclipse magnitude
values.Add(GetMagnitude(mid));
// Coverage
values.Add(GetCoverage(mid));
// Central duration
if (mid[39] > 1)
{
values.Add(GetDuration(mid,c2,c3));
}
else
{
values.Add("-");
}
events.Add(values);
}
}
return events;
}
//Populates the obsvcont array
private static void ReadData(double latRad, double longRad, double[] obsvconst)
{
// Get the latitude
obsvconst[0] = latRad;
//// Get the longitude
obsvconst[1] = -1 * longRad; //PASS REVERSE RADIAN.
// Get the altitude
obsvconst[2] = 100; //CHANGE TO ALLOW USER TO PASS.
// Get the time zone
obsvconst[3] = 0; //ALWAYS GMT
// Get the observer's geocentric position
double tmp = Math.Atan(0.99664719 * Math.Tan(obsvconst[0]));
obsvconst[4] = 0.99664719 * Math.Sin(tmp) + (obsvconst[2] / 6378140.0) * Math.Sin(obsvconst[0]);
obsvconst[5] = Math.Cos(tmp) + (obsvconst[2] / 6378140.0 * Math.Cos(obsvconst[0]));
}
// Populate the c1, c2, mid, c3 and c4 arrays
private static void GetAll(double[] elements, double[] obsvconst, double[] mid, double[] c1, double[] c2,double[] c3, double[] c4)
{
GetMid(elements, obsvconst, mid);
MidObservational(obsvconst, mid);
if (mid[37] > 0.0)
{
Getc1c4(elements, obsvconst, mid,c1,c2,c3,c4);
if ((mid[36] < mid[29]) || (mid[36] < -mid[29]))
{
Getc2c3(elements, obsvconst, mid,c2,c3);
if (mid[29] < 0.0)
{
mid[39] = 3; // Total eclipse
}
else
{
mid[39] = 2; // Annular eclipse
}
Observational(c1, obsvconst, mid);
Observational(c2, obsvconst, mid);
Observational(c3, obsvconst, mid);
Observational(c4, obsvconst, mid);
c2[36] = 999.9;
c3[36] = 999.9;
// Calculate how much of the eclipse is above the horizon
double pattern = 0;
if (c1[40] == 0) { pattern += 10000; }
if (c2[40] == 0) { pattern += 1000; }
if (mid[40] == 0) { pattern += 100; }
if (c3[40] == 0) { pattern += 10; }
if (c4[40] == 0) { pattern += 1; }
// Now, time to make sure that all my Observational[39] and Observational[40] are OK
if (pattern == 11110)
{
GetSunset(elements, c4, obsvconst);
Observational(c4, obsvconst, mid);
c4[40] = 3;
}
else if (pattern == 11100)
{
GetSunset(elements, c3, obsvconst);
Observational(c3, obsvconst, mid);
c3[40] = 3;
CopyCircumstances(c3, c4);
}
else if (pattern == 11000)
{
c3[40] = 4;
GetSunset(elements, mid, obsvconst);
MidObservational(obsvconst, mid);
mid[40] = 3;
CopyCircumstances(mid, c4);
}
else if (pattern == 10000)
{
mid[39] = 1;
GetSunset(elements, mid, obsvconst);
MidObservational(obsvconst, mid);
mid[40] = 3;
CopyCircumstances(mid, c4);
}
else if (pattern == 1111)
{
GetSunrise(elements, c1, obsvconst);
Observational(c1, obsvconst, mid);
c1[40] = 2;
}
else if (pattern == 111)
{
GetSunrise(elements, c2, obsvconst);
Observational(c2, obsvconst, mid);
c2[40] = 2;
CopyCircumstances(c2, c1);
}
else if (pattern == 11)
{
c2[40] = 4;
GetSunrise(elements, mid, obsvconst);
MidObservational(obsvconst, mid);
mid[40] = 2;
CopyCircumstances(mid, c1);
}
else if (pattern == 1)
{
mid[39] = 1;
GetSunrise(elements, mid, obsvconst);
MidObservational(obsvconst, mid);
mid[40] = 2;
CopyCircumstances(mid, c1);
}
else if (pattern == 0)
{
mid[39] = 0;
}
// There are other patterns, but those are the only ones we're covering!
}
else
{
mid[39] = 1; // Partial eclipse
double pattern = 0;
Observational(c1, obsvconst, mid);
Observational(c4, obsvconst, mid);
if (c1[40] == 0) { pattern += 100; }
if (mid[40] == 0) { pattern += 10; }
if (c4[40] == 0) { pattern += 1; }
if (pattern == 110)
{
GetSunset(elements, c4, obsvconst);
Observational(c4, obsvconst, mid);
c4[40] = 3;
}
else if (pattern == 100)
{
GetSunset(elements, mid, obsvconst);
MidObservational(obsvconst, mid);
mid[40] = 3;
CopyCircumstances(mid, c4);
}
else if (pattern == 11)
{
GetSunrise(elements, c1, obsvconst);
Observational(c1, obsvconst, mid);
c1[40] = 2;
}
else if (pattern == 1)
{
GetSunrise(elements, mid, obsvconst);
MidObservational(obsvconst, mid);
mid[40] = 2;
CopyCircumstances(mid, c1);
}
else if (pattern == 0)
{
mid[39] = 0;
}
// There are other patterns, but those are the only ones we're covering!
}
}
else
{
mid[39] = 0; // No eclipse
}
// Magnitude for total and annular eclipse is moon/sun ratio
if ((mid[39] == 2) || (mid[39] == 3))
{
mid[37] = mid[38];
}
}
// Calculate mid eclipse
private static void GetMid(double[] elements, double[] obsvconst, double[] mid)
{
double iter, tmp;
mid[0] = 0;
mid[1] = 0.0;
iter = 0;
tmp = 1.0;
TimeLocDependent(elements, mid, obsvconst);
while (((tmp > 0.000001) || (tmp < -0.000001)) && (iter < 50))
{
tmp = (mid[24] * mid[26] + mid[25] * mid[27]) / mid[30];
mid[1] = mid[1] - tmp;
iter++;
TimeLocDependent(elements, mid, obsvconst);
}
}
// Populate the circumstances array with the time and location dependent circumstances
private static double[] TimeLocDependent(double[] elements, double[] circumstances, double[] obsvconst)
{
double index, type;
TimeDependent(elements, circumstances, obsvconst);
index = obsvconst[6];
// Calculate h, sin h, cos h
circumstances[16] = circumstances[7] - obsvconst[1] - (elements[(int)index + 5] / 13713.44);
circumstances[17] = Math.Sin(circumstances[16]);
circumstances[18] = Math.Cos(circumstances[16]);
// Calculate xi
circumstances[19] = obsvconst[5] * circumstances[17];
// Calculate eta
circumstances[20] = obsvconst[4] * circumstances[6] - obsvconst[5] * circumstances[18] * circumstances[5];
// Calculate zeta
circumstances[21] = obsvconst[4] * circumstances[5] + obsvconst[5] * circumstances[18] * circumstances[6];
// Calculate dxi
circumstances[22] = circumstances[13] * obsvconst[5] * circumstances[18];
// Calculate deta
circumstances[23] = circumstances[13] * circumstances[19] * circumstances[5] - circumstances[21] * circumstances[12];
// Calculate u
circumstances[24] = circumstances[2] - circumstances[19];
// Calculate v
circumstances[25] = circumstances[3] - circumstances[20];
// Calculate a
circumstances[26] = circumstances[10] - circumstances[22];
// Calculate b
circumstances[27] = circumstances[11] - circumstances[23];
// Calculate l1'
type = circumstances[0];
if ((type == -2) || (type == 0) || (type == 2))
{
circumstances[28] = circumstances[8] - circumstances[21] * elements[26 + (int)index];
}
// Calculate l2'
if ((type == -1) || (type == 0) || (type == 1))
{
circumstances[29] = circumstances[9] - circumstances[21] * elements[27 + (int)index];
}
// Calculate n^2
circumstances[30] = circumstances[26] * circumstances[26] + circumstances[27] * circumstances[27];
return circumstances;
}
// Populate the circumstances array with the time-only dependent circumstances (x, y, d, m, ...)
private static double[] TimeDependent(double[] elements, double[] circumstances, double[] obsvconst)
{
double type, t, ans;
t = circumstances[1];
int index = (int)obsvconst[6];
// Calculate x
ans = elements[9 + index] * t + elements[8 + index];
ans = ans * t + elements[7 + index];
ans = ans * t + elements[6 + index];
circumstances[2] = ans;
// Calculate dx
ans = 3.0 * elements[9 + index] * t + 2.0 * elements[8 + index];
ans = ans * t + elements[7 + index];
circumstances[10] = ans;
// Calculate y
ans = elements[13 + index] * t + elements[12 + index];
ans = ans * t + elements[11 + index];
ans = ans * t + elements[10 + index];
circumstances[3] = ans;
// Calculate dy
ans = 3.0 * elements[13 + index] * t + 2.0 * elements[12 + index];
ans = ans * t + elements[11 + index];
circumstances[11] = ans;
// Calculate d
ans = elements[16 + index] * t + elements[15 + index];
ans = ans * t + elements[14 + index];
ans = ans * Math.PI / 180.0;
circumstances[4] = ans;
// sin d and cos d
circumstances[5] = Math.Sin(ans);
circumstances[6] = Math.Cos(ans);
// Calculate dd
ans = 2.0 * elements[16 + index] * t + elements[15 + index];
ans = ans * Math.PI / 180.0;
circumstances[12] = ans;
// Calculate m
ans = elements[19 + index] * t + elements[18 + index];
ans = ans * t + elements[17 + index];
if (ans >= 360.0)
{
ans = ans - 360.0;
}
ans = ans * Math.PI / 180.0;
circumstances[7] = ans;
// Calculate dm
ans = 2.0 * elements[19 + index] * t + elements[18 + index];
ans = ans * Math.PI / 180.0;
circumstances[13] = ans;
// Calculate l1 and dl1
type = circumstances[0];
if ((type == -2) || (type == 0) || (type == 2))
{
ans = elements[22 + index] * t + elements[21 + index];
ans = ans * t + elements[20 + index];
circumstances[8] = ans;
circumstances[14] = 2.0 * elements[22 + index] * t + elements[21 + index];
}
// Calculate l2 and dl2
if ((type == -1) || (type == 0) || (type == 1))
{
ans = elements[25 + index] * t + elements[24 + index];
ans = ans * t + elements[23 + index];
circumstances[9] = ans;
circumstances[15] = 2.0 * elements[25 + index] * t + elements[24 + index];
}
return circumstances;
}
// Get the observational circumstances for mid eclipse
private static void MidObservational(double[] obsvconst, double[] mid)
{
Observational(mid, obsvconst, mid);
// Calculate m, magnitude and moon/sun
mid[36] = Math.Sqrt(mid[24] * mid[24] + mid[25] * mid[25]);
mid[37] = (mid[28] - mid[36]) / (mid[28] + mid[29]);
mid[38] = (mid[28] - mid[29]) / (mid[28] + mid[29]);
}
// Get the observational circumstances
private static void Observational(double[] circumstances, double[] obsvconst, double[] mid)
{
double contacttype, coslat, sinlat;
// We are looking at an "external" contact UNLESS this is a total eclipse AND we are looking at
// c2 or c3, in which case it is an INTERNAL contact! Note that if we are looking at mid eclipse,
// then we may not have determined the type of eclipse (mid[39]) just yet!
if (circumstances[0] == 0)
{
contacttype = 1.0;
}
else
{
if ((mid[39] == 3) && ((circumstances[0] == -1) || (circumstances[0] == 1)))
{
contacttype = -1.0;
}
else
{
contacttype = 1.0;
}
}
// Calculate p
circumstances[31] = Math.Atan2(contacttype * circumstances[24], contacttype * circumstances[25]);
// Calculate alt
sinlat = Math.Sin(obsvconst[0]);
coslat = Math.Cos(obsvconst[0]);
circumstances[32] = Math.Asin(circumstances[5] * sinlat + circumstances[6] * coslat * circumstances[18]);
// Calculate q
circumstances[33] = Math.Asin(coslat * circumstances[17] / Math.Cos(circumstances[32]));
if (circumstances[20] < 0.0)
{
circumstances[33] = Math.PI - circumstances[33];
}
// Calculate v
circumstances[34] = circumstances[31] - circumstances[33];
// Calculate azi
circumstances[35] = Math.Atan2(-1.0 * circumstances[17] * circumstances[6], circumstances[5] * coslat - circumstances[18] * sinlat * circumstances[6]);
// Calculate visibility
if (circumstances[32] > -0.00524)
{
circumstances[40] = 0;
}
else
{
circumstances[40] = 1;
}
}
// Get C1 and C4 data
// Entry conditions -
// 1. The mid array must be populated
// 2. The magnitude at mid eclipse must be > 0.0
private static void Getc1c4(double[] elements, double[] obsvconst, double[] mid, double[] c1, double[] c2, double[] c3, double[] c4)
{
double tmp, n;
n = Math.Sqrt(mid[30]);
tmp = mid[26] * mid[25] - mid[24] * mid[27];
tmp = tmp / n / mid[28];
tmp = Math.Sqrt(1.0 - tmp * tmp) * mid[28] / n;
c1[0] = -2;
c4[0] = 2;
c1[1] = mid[1] - tmp;
c4[1] = mid[1] + tmp;
c1c4iterate(elements, c1, obsvconst);
c1c4iterate(elements, c4, obsvconst);
}
// Iterate on C1 or C4
private static double[] c1c4iterate(double[] elements, double[] circumstances, double[] obsvconst)
{
double sign, iter, tmp, n;
TimeLocDependent(elements, circumstances, obsvconst);
if (circumstances[0] < 0)
{
sign = -1.0;
}
else
{
sign = 1.0;
}
tmp = 1.0;
iter = 0;
while (((tmp > 0.000001) || (tmp < -0.000001)) && (iter < 50))
{
n = Math.Sqrt(circumstances[30]);
tmp = circumstances[26] * circumstances[25] - circumstances[24] * circumstances[27];
tmp = tmp / n / circumstances[28];
tmp = sign * Math.Sqrt(1.0 - tmp * tmp) * circumstances[28] / n;
tmp = (circumstances[24] * circumstances[26] + circumstances[25] * circumstances[27]) / circumstances[30] - tmp;
circumstances[1] = circumstances[1] - tmp;
TimeLocDependent(elements, circumstances, obsvconst);
iter++;
}
return circumstances;
}
// Get C2 and C3 data
// Entry conditions -
// 1. The mid array must be populated
// 2. There must be either a total or annular eclipse at the location!
private static void Getc2c3(double[] elements, double[] obsvconst, double[] mid, double[] c2, double[] c3)
{
double tmp, n;
n = Math.Sqrt(mid[30]);
tmp = mid[26] * mid[25] - mid[24] * mid[27];
tmp = tmp / n / mid[29];
tmp = Math.Sqrt(1.0 - tmp * tmp) * mid[29] / n;
c2[0] = -1;
c3[0] = 1;
if (mid[29] < 0.0)
{
c2[1] = mid[1] + tmp;
c3[1] = mid[1] - tmp;
}
else
{
c2[1] = mid[1] - tmp;
c3[1] = mid[1] + tmp;
}
c2c3iterate(elements, c2, obsvconst, mid);
c2c3iterate(elements, c3, obsvconst, mid);
}
// Iterate on C2 or C3
private static double[] c2c3iterate(double[] elements, double[] circumstances, double[] obsvconst, double[] mid)
{
double sign, iter, tmp, n;
TimeLocDependent(elements, circumstances, obsvconst);
if (circumstances[0] < 0)
{
sign = -1.0;
}
else
{
sign = 1.0;
}
if (mid[29] < 0.0)
{
sign = -sign;
}
tmp = 1.0;
iter = 0;
while (((tmp > 0.000001) || (tmp < -0.000001)) && (iter < 50))
{
n = Math.Sqrt(circumstances[30]);
tmp = circumstances[26] * circumstances[25] - circumstances[24] * circumstances[27];
tmp = tmp / n / circumstances[29];
tmp = sign * Math.Sqrt(1.0 - tmp * tmp) * circumstances[29] / n;
tmp = (circumstances[24] * circumstances[26] + circumstances[25] * circumstances[27]) / circumstances[30] - tmp;
circumstances[1] = circumstances[1] - tmp;
TimeLocDependent(elements, circumstances, obsvconst);
iter++;
}
return circumstances;
}
// Get the date of an event
private static string GetDate(double[] elements, double[] circumstances, double[] obsvconst)
{
string[] month = new string[] { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" };
double t, jd, a, b, c, d, e, index;
string ans = "";
index = obsvconst[6];
// Calculate the JD for noon (TDT) the day before the day that contains T0
jd = Math.Floor(elements[(int)index] - (elements[1 + (int)index] / 24.0));
// Calculate the local time (ie the offset in hours since midnight TDT on the day containing T0).
t = circumstances[1] + elements[1 + (int)index] - obsvconst[3] - (elements[4 + (int)index] - 0.5) / 3600.0;
if (t < 0.0)
{
jd--;
}
if (t >= 24.0)
{
jd++;
}
if (jd >= 2299160.0)
{
a = Math.Floor((jd - 1867216.25) / 36524.25);
a = jd + 1 + a - Math.Floor(a / 4.0);
}
else
{
a = jd;
}
b = a + 1525.0;
c = Math.Floor((b - 122.1) / 365.25);
d = Math.Floor(365.25 * c);
e = Math.Floor((b - d) / 30.6001);
d = b - d - Math.Floor(30.6001 * e);
if (e < 13.5)
{
e = e - 1;
}
else
{
e = e - 13;
}
double year;
if (e > 2.5)
{
ans = c - 4716 + "-";
year = c - 4716;
}
else
{
ans = c - 4715 + "-";
year = c - 4715;
}
string m = month[(int)e - 1];
ans += m + "-";
if (d < 10)
{
ans = ans + "0";
}
ans = ans + d;
//Leap Year Integrity Check
if(m =="Feb" && d ==29 && !DateTime.IsLeapYear((int)year))
{
ans = year.ToString() + "-Mar-01";
}
return ans;
}
// Calculate the time of sunset
private static void GetSunset(double[] elements, double[] circumstances, double[] obsvconst)
{
GetSunriset(elements, circumstances, 1.0, obsvconst);
}
// Calculate the time of sunrise
private static void GetSunrise(double[] elements, double[] circumstances, double[] obsvconst)
{
GetSunriset(elements, circumstances, -1.0, obsvconst);
}
// Calculate the time of sunrise or sunset
private static void GetSunriset(double[] elements, double[] circumstances, double riset, double[] obsvconst)
{
double h0, diff, iter;
diff = 1.0;
iter = 0;
while ((diff > 0.00001) || (diff < -0.00001))
{
iter++;
if (iter == 4) { return; }
h0 = Math.Acos((Math.Sin(-0.00524) - Math.Sin(obsvconst[0]) * circumstances[5]) / Math.Cos(obsvconst[0]) / circumstances[6]);
diff = (riset * h0 - circumstances[16]) / circumstances[13];
while (diff >= 12.0) { diff -= 24.0; }
while (diff <= -12.0) { diff += 24.0; }
circumstances[1] += diff;
TimeLocDependent(elements, circumstances, obsvconst);
}
}
// Copy a set of circumstances
private static void CopyCircumstances(double[] circumstancesfrom, double[] circumstancesto)
{
for (int i = 1; i < 41; i++)
{
circumstancesto[i] = circumstancesfrom[i];
}
}
// Get the local time of an event
private static string GetTime(double[] elements, double[] circumstances, double[] obsvconst)
{
string ans = "";
int index = (int)obsvconst[6];
double t = circumstances[1] + elements[1 + index] - obsvconst[3] - (elements[4 + index] - 0.5) / 3600.0;
if (t < 0.0)
{
t = t + 24.0;
}
if (t >= 24.0)
{
t = t - 24.0;
}
if (t < 10.0)
{
ans = ans + "0";
}
ans = ans + Math.Floor(t) + ":";
t = (t * 60.0) - 60.0 * Math.Floor(t);
if (t < 10.0)
{
ans = ans + "0";
}
ans = ans + Math.Floor(t);
if (circumstances[40] <= 1)
{ // not sunrise or sunset
ans = ans + ":";
t = (t * 60.0) - 60.0 * Math.Floor(t);
if (t < 10.0)
{
ans = ans + "0";
}
ans = ans + Math.Floor(t);
}
if (circumstances[40] == 1)
{
//WAS ITALIC
return ans;
}
else if (circumstances[40] == 2)
{
//Rise (CHANGED FROM NASA CALC THE INDICATES (r) WITH STRING, INVESTIGATE REMOVAL)
return ans;
}
else if (circumstances[40] == 3)
{
//Set (CHANGED FROM NASA CALC THE INDICATES (s) WITH STRING, INVESTIGATE REMOVAL)
return ans;
}
else
{
return ans;
}
}
// Get the altitude
private static string GetAlt(double[] circumstances)
{
double t;
string ans = "";
if (circumstances[40] == 2)
{
return "0(r)";
}
if (circumstances[40] == 3)
{
return "0(s)";
}
if ((circumstances[32] < 0.0) && (circumstances[32] >= -0.00524))
{
// Crude correction for refraction (and for consistency's sake)
t = 0.0;
}
else
{
t = circumstances[32] * 180.0 / Math.PI;
}
if (t < 0.0)
{
ans = "-";
t = -t;
}
else
{
ans = "";
}
t = Math.Floor(t + 0.5);
if (t < 10.0)
{
ans = ans + "0";
}
ans = ans + t;
if (circumstances[40] == 1)
{
//WAS ITALIC
return ans;
}
else
{
return ans;
}
}
// Get the azimuth
private static string GetAzi(double[] circumstances)
{
string ans = "";
double t = circumstances[35] * 180.0 / Math.PI;
if (t < 0.0)
{
t = t + 360.0;
}
if (t >= 360.0)
{
t = t - 360.0;
}
t = Math.Floor(t + 0.5);
if (t < 100.0)
{
ans = ans + "0";
}
if (t < 10.0)
{
ans = ans + "0";
}
ans = ans + t;
if (circumstances[40] == 1)
{
//WAS ITALIC
return ans;
}
else
{
return ans;
}
}
// Get the magnitude
private static string GetMagnitude(double[] mid)
{
double a = Math.Floor(1000.0 * mid[37] + 0.5) / 1000.0;
string ans = a.ToString();
if (mid[40] == 1)
{
return ans;
}
if (mid[40] == 2)
{
ans = a.ToString() + "(r)";
}
if (mid[40] == 3)
{
ans = a.ToString() + "(s)";
}
return ans;
}
// Get the coverage
private static string GetCoverage(double[] mid)
{
double a=0, b, c;
string ans = "";
if (mid[37] <= 0.0)
{
ans = "0.0";
}
else if (mid[37] >= 1.0)
{
ans = "1.000";
}
else
{
if (mid[39] == 2)
{
c = mid[38] * mid[38];
}
else
{
c = Math.Acos((mid[28] * mid[28] + mid[29] * mid[29] - 2.0 * mid[36] * mid[36]) / (mid[28] * mid[28] - mid[29] * mid[29]));
b = Math.Acos((mid[28] * mid[29] + mid[36] * mid[36]) / mid[36] / (mid[28] + mid[29]));
a = Math.PI - b - c;
c = ((mid[38] * mid[38] * a + b) - mid[38] * Math.Sin(c)) / Math.PI;
}
a = Math.Floor(1000.0 * c + 0.5) / 1000.0;
ans = a.ToString();
}
if (mid[40] == 1)
{
//WAS ITALIC
return ans;
}
if (mid[40] == 2)
{
ans = a.ToString() + "(r)";
}
if (mid[40] == 3)
{
ans = a + "(s)";
}
return ans;
}
// Get the duration in mm:ss.s format
// Adapted from code written by Stephen McCann - 27/04/2001
private static string GetDuration(double[] mid, double[] c2, double[] c3)
{
double tmp;
string ans;
if (c3[40] == 4)
{
tmp = mid[1] - c2[1];
}
else if (c2[40] == 4)
{
tmp = c3[1] - mid[1];
}
else
{
tmp = c3[1] - c2[1];
}
if (tmp < 0.0)
{
tmp = tmp + 24.0;
}
else if (tmp >= 24.0)
{
tmp = tmp - 24.0;
}
tmp = (tmp * 60.0) - 60.0 * Math.Floor(tmp) + 0.05 / 60.0;
ans = Math.Floor(tmp) + "m";
tmp = (tmp * 60.0) - 60.0 * Math.Floor(tmp);
if (tmp < 10.0)
{
ans = ans + "0";
}
ans += Math.Floor(tmp) + "s";
return ans;
}
}
}

View File

@ -0,0 +1,374 @@
using System;
using System.Collections.Generic;
namespace CoordinateSharp
{
internal class SunCalc
{
public static void CalculateSunTime(double lat, double longi, DateTime date, Celestial c,double offset = 0)
{
if (date.Year == 0001) { return; } //Return if date vaue hasn't been established.
DateTime actualDate = new DateTime(date.Year,date.Month,date.Day,0, 0, 0, DateTimeKind.Utc);
////Sun Time Calculations
//Get Julian
double lw = rad * -longi;
double phi = rad * lat;
//Rise Set
DateTime?[] evDate = Get_Event_Time(lw, phi, -.8333, actualDate);
c.sunRise = evDate[0];
c.sunSet = evDate[1];
c.sunCondition = CelestialStatus.RiseAndSet;
//Azimuth and Altitude
CalculateSunAngle(date, longi, lat, c);
// neither sunrise nor sunset
if ((!c.SunRise.HasValue) && (!c.SunSet.HasValue))
{
if (c.SunAltitude < 0)
{
c.sunCondition = CelestialStatus.DownAllDay;
}
else
{
c.sunCondition = CelestialStatus.UpAllDay;
}
}
// sunrise or sunset
else
{
if (!c.SunRise.HasValue)
{
// No sunrise this date
c.sunCondition = CelestialStatus.NoRise;
}
else if (!c.SunSet.HasValue)
{
// No sunset this date
c.sunCondition = CelestialStatus.NoSet;
}
}
//Additional Times
c.additionalSolarTimes = new AdditionalSolarTimes();
//Dusk and Dawn
//Civil
evDate = Get_Event_Time(lw, phi, -6, actualDate);
c.AdditionalSolarTimes.CivilDawn = evDate[0];
c.AdditionalSolarTimes.CivilDusk = evDate[1];
//Nautical
evDate = Get_Event_Time(lw, phi, -12, actualDate);
c.AdditionalSolarTimes.NauticalDawn = evDate[0];
c.AdditionalSolarTimes.NauticalDusk = evDate[1];
//Astronomical
evDate = Get_Event_Time(lw, phi, -18, actualDate);
c.AdditionalSolarTimes.AstronomicalDawn = evDate[0];
c.AdditionalSolarTimes.AstronomicalDusk = evDate[1];
//BottomDisc
evDate = Get_Event_Time(lw, phi, -.2998, actualDate);
c.AdditionalSolarTimes.SunriseBottomDisc = evDate[0];
c.AdditionalSolarTimes.SunsetBottomDisc = evDate[1];
CalculateSolarEclipse(date, lat, longi, c);
}
/// <summary>
/// Gets time of event based on specified degree below horizon
/// </summary>
/// <param name="lw">Observer Longitude in radians</param>
/// <param name="phi">Observer Latitude in radians</param>
/// <param name="h">Angle in Degrees</param>
/// <param name="date">Date of Event</param>
/// <returns>DateTime?[]{rise, set}</returns>
private static DateTime?[] Get_Event_Time(double lw, double phi, double h,DateTime date)
{
//Create arrays. Index 0 = Day -1, 1 = Day, 2 = Day + 1;
//These will be used to find exact day event occurs for comparison
DateTime?[] sets = new DateTime?[] { null, null, null, null, null };
DateTime?[] rises = new DateTime?[] { null, null, null,null, null };
//Iterate starting with day -1;
for (int x = 0; x < 5; x++)
{
double d = JulianConversions.GetJulian(date.AddDays(x-2)) - j2000 + .5; //LESS PRECISE JULIAN NEEDED
double n = julianCycle(d, lw);
double ds = approxTransit(0, lw, n);
double M = solarMeanAnomaly(ds);
double L = eclipticLongitude(M);
double dec = declination(L, 0);
double Jnoon = solarTransitJ(ds, M, L);
double Jset;
double Jrise;
DateTime? solarNoon = JulianConversions.GetDate_FromJulian(Jnoon);
DateTime? nadir = JulianConversions.GetDate_FromJulian(Jnoon - 0.5);
//Rise Set
Jset = GetTime(h * rad, lw, phi, dec, n, M, L);
Jrise = Jnoon - (Jset - Jnoon);
DateTime? rise = JulianConversions.GetDate_FromJulian(Jrise);
DateTime? set = JulianConversions.GetDate_FromJulian(Jset);
rises[x] = rise;
sets[x] = set;
}
//Compare and send
DateTime? tRise = null;
for(int x=0;x<5;x++)
{
if(rises[x].HasValue)
{
if(rises[x].Value.Day == date.Day)
{
tRise = rises[x];
break;
}
}
}
DateTime? tSet = null;
for (int x = 0; x < 5; x++)
{
if (sets[x].HasValue)
{
if (sets[x].Value.Day == date.Day)
{
tSet = sets[x];
break;
}
}
}
return new DateTime?[] { tRise, tSet };
}
public static void CalculateZodiacSign(DateTime date, Celestial c)
{
//Aquarius (January 20 to February 18)
//Pisces (February 19 to March 20)
//Aries (March 21-April 19)
//Taurus (April 20-May 20)
//Gemini (May 21-June 20)
//Cancer (June 21-July 22)
//Leo (July 23-August 22)
//Virgo (August 23-September 22)
//Libra (September 23-October 22)
//Scorpio (October 23-November 21)
//Sagittarius (November 22-December 21)
//Capricorn (December 22-January 19)
if (date >= new DateTime(date.Year, 1, 1) && date <= new DateTime(date.Year, 1, 19, 23, 59, 59))
{
c.AstrologicalSigns.ZodiacSign = "Capricorn";
return;
}
if (date >= new DateTime(date.Year, 1, 20) && date <= new DateTime(date.Year, 2, 18, 23, 59, 59))
{
c.AstrologicalSigns.ZodiacSign = "Aquarius";
return;
}
if (date >= new DateTime(date.Year, 2, 19) && date <= new DateTime(date.Year, 3, 20, 23, 59, 59))
{
c.AstrologicalSigns.ZodiacSign = "Pisces";
return;
}
if (date >= new DateTime(date.Year, 3, 21) && date <= new DateTime(date.Year, 4, 19, 23, 59, 59))
{
c.AstrologicalSigns.ZodiacSign = "Aries";
return;
}
if (date >= new DateTime(date.Year, 4, 20) && date <= new DateTime(date.Year, 5, 20, 23, 59, 59))
{
c.AstrologicalSigns.ZodiacSign = "Taurus";
return;
}
if (date >= new DateTime(date.Year, 5, 21) && date <= new DateTime(date.Year, 6, 20,23,59,59))
{
c.AstrologicalSigns.ZodiacSign = "Gemini";
return;
}
if (date >= new DateTime(date.Year, 6, 21) && date <= new DateTime(date.Year, 7, 22, 23, 59, 59))
{
c.AstrologicalSigns.ZodiacSign = "Cancer";
return;
}
if (date >= new DateTime(date.Year, 7, 23) && date <= new DateTime(date.Year, 8, 22, 23, 59, 59))
{
c.AstrologicalSigns.ZodiacSign = "Leo";
return;
}
if (date >= new DateTime(date.Year, 8, 23) && date <= new DateTime(date.Year, 9, 22, 23, 59, 59))
{
c.AstrologicalSigns.ZodiacSign = "Virgo";
return;
}
if (date >= new DateTime(date.Year, 9, 23) && date <= new DateTime(date.Year, 10, 22, 23, 59, 59))
{
c.AstrologicalSigns.ZodiacSign = "Libra";
return;
}
if (date >= new DateTime(date.Year, 9, 23) && date <= new DateTime(date.Year, 11, 21, 23, 59, 59))
{
c.AstrologicalSigns.ZodiacSign = "Scorpio";
return;
}
if (date >= new DateTime(date.Year, 11, 21) && date <= new DateTime(date.Year, 12, 21, 23, 59, 59))
{
c.AstrologicalSigns.ZodiacSign = "Sagittarius";
return;
}
if (date >= new DateTime(date.Year, 12, 22) && date <= new DateTime(date.Year, 12, 31, 23, 59, 59))
{
c.AstrologicalSigns.ZodiacSign = "Capricorn";
return;
}
}
public static void CalculateSolarEclipse(DateTime date, double lat, double longi, Celestial c)
{
//Convert to Radian
double latR = lat * Math.PI / 180;
double longR = longi * Math.PI / 180;
List<List<string>> se = SolarEclipseCalc.CalculateSolarEclipse(date, latR, longR);
//RETURN FIRST AND LAST
if (se.Count == 0) { return; }
//FIND LAST AND NEXT ECLIPSE
int lastE = -1;
int nextE = -1;
int currentE = 0;
DateTime lastDate = new DateTime();
DateTime nextDate = new DateTime(3300, 1, 1);
//Iterate to get last and next eclipse
foreach(List<string> values in se)
{
DateTime ld = DateTime.ParseExact(values[0], "yyyy-MMM-dd", System.Globalization.CultureInfo.InvariantCulture);
if (ld < date && ld>lastDate) { lastDate = ld;lastE = currentE; }
if(ld>= date && ld < nextDate) { nextDate = ld;nextE = currentE; }
currentE++;
}
//SET ECLIPSE DATA
if (lastE >= 0)
{
c.SolarEclipse.LastEclipse = new SolarEclipseDetails(se[lastE]);
}
if (nextE >= 0)
{
c.SolarEclipse.NextEclipse = new SolarEclipseDetails(se[nextE]);
}
}
#region Private Suntime Members
private static readonly double dayMS = 1000 * 60 * 60 * 24, j1970 = 2440588, j2000 = 2451545;
private static readonly double rad = Math.PI / 180;
private static double LocalSiderealTimeForTimeZone(double lon, double jd, double z)
{
double s = 24110.5 + 8640184.812999999 * jd / 36525 + 86636.6 * z + 86400 * lon;
s = s / 86400;
s = s - Math.Truncate(s);
double lst = s * 360 *rad;
return lst;
}
private static double SideRealTime(double d, double lw)
{
double s = rad * (280.16 + 360.9856235 * d) - lw;
return s;
}
private static double solarTransitJ(double ds, double M, double L)
{
return j2000 + ds + 0.0053 * Math.Sin(M) - 0.0069 * Math.Sin(2 * L);
}
//CH15
//Formula 15.1
//Returns Approximate Time
private static double hourAngle(double h, double phi, double d)
{
//NUMBER RETURNING > and < 1 NaN;
double d1 = Math.Sin(h) - Math.Sin(phi) * Math.Sin(d);
double d2 = Math.Cos(phi) * Math.Cos(d);
double d3 = (d1 / d2);
return Math.Acos(d3);
}
private static double approxTransit(double Ht, double lw, double n)
{
return 0.0009 + (Ht + lw) / (2 * Math.PI) + n;
}
private static double julianCycle(double d, double lw) { return Math.Round(d - 0.0009 - lw / (2 * Math.PI)); }
//Returns Time of specified event based on suns angle
private static double GetTime(double h, double lw, double phi, double dec, double n,double M, double L)
{
double approxTime = hourAngle(h, phi, dec); //Ch15 Formula 15.1
double a = approxTransit(approxTime, lw, n);
double st = solarTransitJ(a, M, L);
return st;
}
private static double declination(double l, double b)
{
double e = (Math.PI/180) * 23.4392911; // obliquity of the Earth
return Math.Asin(Math.Sin(b) * Math.Cos(e) + Math.Cos(b) * Math.Sin(e) * Math.Sin(l));
}
private static void CalculateSunAngle(DateTime date, double longi, double lat, Celestial c)
{
TimeSpan ts = date - new DateTime(1970, 1, 1,0,0,0, DateTimeKind.Utc);
double dms = (ts.TotalMilliseconds / dayMS -.5 + j1970)-j2000;
double lw = rad * -longi;
double phi = rad * lat;
double e = rad * 23.4397;
double[] sc = sunCoords(dms);
double H = SideRealTime(dms, lw) - sc[1];
c.sunAzimuth = Math.Atan2(Math.Sin(H), Math.Cos(H) * Math.Sin(phi) - Math.Tan(sc[0]) * Math.Cos(phi)) * 180 / Math.PI + 180;
c.sunAltitude = Math.Asin(Math.Sin(phi) * Math.Sin(sc[0]) + Math.Cos(phi) * Math.Cos(sc[0]) * Math.Cos(H)) * 180 / Math.PI;
}
private static double solarMeanAnomaly(double d)
{
return rad * (357.5291 + 0.98560028 * d);
}
private static double eclipticLongitude(double m)
{
double c = rad * (1.9148 * Math.Sin(m) + 0.02 * Math.Sin(2 * m) + 0.0003 * Math.Sin(3 * m)); // equation of center
double p = rad * 102.9372; // perihelion of the Earth
return m + c + p + Math.PI;
}
private static double[] sunCoords(double d)
{
double m = solarMeanAnomaly(d);
double l = eclipticLongitude(m);
double[] sc = new double[2];
double b = 0;
double e = rad * 23.4397; // obliquity of the Earth
sc[0] = Math.Asin(Math.Sin(b) * Math.Cos(e) + Math.Cos(b) * Math.Sin(e) * Math.Sin(l)); //declination
sc[1] = Math.Atan2(Math.Sin(l) * Math.Cos(e) - Math.Tan(b) * Math.Sin(e), Math.Cos(l)); //rightAscension
return sc;
}
#endregion
}
}

View File

@ -0,0 +1,620 @@
using System;
using System.ComponentModel;
using System.Collections.Generic;
namespace CoordinateSharp
{
/// <summary>
/// The main class for handling location based celestial information.
/// </summary>
/// <remarks>
/// This class can calculate various pieces of solar and lunar data, based on location and date
/// </remarks>
[Serializable]
public class Celestial
{
//When a rise or a set does not occur, the DateTime will return null
/// <summary>
/// Creates an empty Celestial.
/// </summary>
public Celestial()
{
astrologicalSigns = new AstrologicalSigns();
lunarEclipse = new LunarEclipse();
solarEclipse = new SolarEclipse();
CalculateCelestialTime(0, 0, new DateTime(1900, 1, 1, 0, 0, 0, DateTimeKind.Utc));
}
private Celestial(bool hasCalcs)
{
astrologicalSigns = new AstrologicalSigns();
lunarEclipse = new LunarEclipse();
solarEclipse = new SolarEclipse();
if (hasCalcs) { CalculateCelestialTime(0, 0, new DateTime(1900, 1, 1, 0, 0, 0, DateTimeKind.Utc)); }
}
/// <summary>
/// Creates a Celestial based on a location and specified date
/// </summary>
/// <param name="lat">latitude</param>
/// <param name="longi">longitude</param>
/// <param name="geoDate">DateTime (UTC)</param>
public Celestial(double lat, double longi, DateTime geoDate)
{
DateTime d = new DateTime(geoDate.Year, geoDate.Month, geoDate.Day, geoDate.Hour, geoDate.Minute, geoDate.Second, DateTimeKind.Utc);
astrologicalSigns = new AstrologicalSigns();
lunarEclipse = new LunarEclipse();
solarEclipse = new SolarEclipse();
CalculateCelestialTime(lat, longi, d);
}
/// <summary>
/// Creates a Celestial based on a location and date in the provided Coordinate.
/// </summary>
/// <param name="c">Coordinate</param>
/// <returns>Celestial</returns>
public static Celestial LoadCelestial(Coordinate c)
{
DateTime geoDate = c.GeoDate;
DateTime d = new DateTime(geoDate.Year, geoDate.Month, geoDate.Day, geoDate.Hour, geoDate.Minute, geoDate.Second, DateTimeKind.Utc);
Celestial cel = new Celestial(c.Latitude.ToDouble(), c.Longitude.ToDouble(), c.GeoDate);
return cel;
}
/// <summary>
/// Converts Celestial values to local times.
/// </summary>
/// <param name="c">Coordinate</param>
/// <param name="offset">UTC offset</param>
/// <returns></returns>
public static Celestial Celestial_LocalTime(Coordinate c, double offset)
{
if(offset < -12 || offset > 12) { throw new ArgumentOutOfRangeException("Time offsets cannot be greater 12 or less than -12."); }
//Probably need to offset initial UTC date so user can op in local
//Determine best way to do this.
DateTime d = c.GeoDate.AddHours(offset);
//Get 3 objects for comparison
Celestial cel = new Celestial(c.Latitude.ToDouble(), c.Longitude.ToDouble(), c.GeoDate);
Celestial celPre = new Celestial(c.Latitude.ToDouble(), c.Longitude.ToDouble(), c.GeoDate.AddDays(-1));
Celestial celPost = new Celestial(c.Latitude.ToDouble(), c.Longitude.ToDouble(), c.GeoDate.AddDays(1));
//Slip objects for comparison. Compare with slipped date.
celPre.Local_Convert(c, offset);
cel.Local_Convert(c, offset);
celPost.Local_Convert(c, offset);
//Get SunSet
int i = Determine_Slipped_Event_Index(cel.SunSet, celPre.SunSet, celPost.SunSet, d);
cel.sunSet = Get_Correct_Slipped_Date(cel.SunSet, celPre.SunSet, celPost.SunSet, i);
cel.AdditionalSolarTimes.CivilDusk = Get_Correct_Slipped_Date(cel.AdditionalSolarTimes.CivilDusk,
celPre.AdditionalSolarTimes.CivilDusk, celPost.AdditionalSolarTimes.CivilDusk, i);
cel.AdditionalSolarTimes.NauticalDusk = Get_Correct_Slipped_Date(cel.AdditionalSolarTimes.NauticalDusk,
celPre.AdditionalSolarTimes.NauticalDusk, celPost.AdditionalSolarTimes.NauticalDusk, i);
//Get SunRise
i = Determine_Slipped_Event_Index(cel.SunRise, celPre.SunRise, celPost.SunRise, d);
cel.sunRise = Get_Correct_Slipped_Date(cel.SunRise, celPre.SunRise, celPost.SunRise, i);
cel.AdditionalSolarTimes.CivilDawn = Get_Correct_Slipped_Date(cel.AdditionalSolarTimes.CivilDawn,
celPre.AdditionalSolarTimes.CivilDawn, celPost.AdditionalSolarTimes.CivilDawn, i);
cel.AdditionalSolarTimes.NauticalDawn = Get_Correct_Slipped_Date(cel.AdditionalSolarTimes.NauticalDawn,
celPre.AdditionalSolarTimes.NauticalDawn, celPost.AdditionalSolarTimes.NauticalDawn, i);
//MoonRise
i = Determine_Slipped_Event_Index(cel.MoonRise, celPre.MoonRise, celPost.MoonRise, d);
cel.moonRise = Get_Correct_Slipped_Date(cel.MoonRise, celPre.MoonRise, celPost.MoonRise, i);
//MoonSet
i = Determine_Slipped_Event_Index(cel.MoonSet, celPre.MoonSet, celPost.MoonSet, d);
cel.moonSet = Get_Correct_Slipped_Date(cel.MoonSet, celPre.MoonSet, celPost.MoonSet, i);
//Local Conditions
CelestialStatus[] cels = new CelestialStatus[]
{
celPre.MoonCondition,cel.MoonCondition,celPost.MoonCondition
};
cel.moonCondition = Celestial.GetStatus(cel.MoonRise, cel.MoonSet, cels);
cels = new CelestialStatus[]
{
celPre.SunCondition, cel.SunCondition, celPost.SunCondition
};
cel.sunCondition = Celestial.GetStatus(cel.SunRise, cel.SunSet, cels);
//Load IsUp values based on local time with populated Celestial
Celestial.Calculate_Celestial_IsUp_Booleans(d, cel);
return cel;
}
private static CelestialStatus GetStatus(DateTime? rise, DateTime? set, CelestialStatus[] cels)
{
if (set.HasValue && rise.HasValue) { return CelestialStatus.RiseAndSet; }
if (set.HasValue && !rise.HasValue) { return CelestialStatus.NoRise; }
if (!set.HasValue && rise.HasValue) { return CelestialStatus.NoSet; }
for (int x=0; x < 3;x++)
{
if(cels[x] == CelestialStatus.DownAllDay || cels[x] == CelestialStatus.UpAllDay)
{
return cels[x];
}
}
return cels[1];
}
/// <summary>
/// In place time slip
/// </summary>
/// <param name="c">Coordinate</param>
/// <param name="offset">hour offset</param>
private void Local_Convert(Coordinate c, double offset)
{
//Find new lunar set rise times
if (MoonSet.HasValue) { moonSet = moonSet.Value.AddHours(offset); }
if (MoonRise.HasValue) { moonRise = moonRise.Value.AddHours(offset); }
//Perigee
Perigee.ConvertTo_Local_Time(offset);
//Apogee
Apogee.ConvertTo_Local_Time(offset);
//Eclipse
LunarEclipse.ConvertTo_LocalTime(offset);
////Solar
if (sunSet.HasValue) { sunSet = sunSet.Value.AddHours(offset); }
if (SunRise.HasValue) { sunRise = SunRise.Value.AddHours(offset); }
AdditionalSolarTimes.Convert_To_Local_Time(offset);
//Eclipse
SolarEclipse.ConvertTo_LocalTime(offset);
SunCalc.CalculateZodiacSign(c.GeoDate.AddHours(offset), this);
MoonCalc.GetMoonSign(c.GeoDate.AddHours(offset), this);
}
private PerigeeApogee Get_Correct_Slipped_Date(PerigeeApogee actual, PerigeeApogee pre, PerigeeApogee post, int i)
{
switch (i)
{
case 0:
return pre;
case 1:
return actual;
case 2:
return post;
default:
return actual;
}
}
private static DateTime? Get_Correct_Slipped_Date(DateTime? actual, DateTime? pre, DateTime? post, int i)
{
switch(i)
{
case 0:
return pre;
case 1:
return actual;
case 2:
return post;
default:
return null;
}
}
private static int Determine_Slipped_Event_Index(DateTime? actual, DateTime? pre, DateTime? post, DateTime d)
{
if (actual.HasValue)
{
if (actual.Value.Day != d.Day)
{
if (pre.HasValue)
{
if (pre.Value.Day == d.Day) { return 0; }
}
if (post.HasValue)
{
if (post.Value.Day == d.Day) { return 2; }
}
return 3;
}
}
else
{
if (pre.HasValue)
{
if (pre.Value.Day == d.Day) { return 0; }
}
if (post.HasValue)
{
if (post.Value.Day == d.Day) { return 2; }
}
}
return 1;
}
internal DateTime? sunSet;
internal DateTime? sunRise;
internal DateTime? moonSet;
internal DateTime? moonRise;
internal double sunAltitude;
internal double sunAzimuth;
internal double moonAltitude;
internal double moonAzimuth;
internal Distance moonDistance;
internal CelestialStatus sunCondition;
internal CelestialStatus moonCondition;
internal bool isSunUp;
internal bool isMoonUp;
internal MoonIllum moonIllum;
internal Perigee perigee;
internal Apogee apogee;
internal AdditionalSolarTimes additionalSolarTimes;
internal AstrologicalSigns astrologicalSigns;
internal SolarEclipse solarEclipse;
internal LunarEclipse lunarEclipse;
/// <summary>
/// Sunset time.
/// </summary>
public DateTime? SunSet { get { return sunSet; } }
/// <summary>
/// Sunrise time.
/// </summary>
public DateTime? SunRise { get { return sunRise; } }
/// <summary>
/// Moonset time.
/// </summary>
public DateTime? MoonSet { get { return moonSet; } }
/// <summary>
/// Moonrise time.
/// </summary>
public DateTime? MoonRise { get { return moonRise; } }
/// <summary>
/// Sun altitude in degrees (E of N).
/// </summary>
public double SunAltitude { get { return sunAltitude; } }
/// <summary>
/// Sun azimuth in degrees (E of N).
/// </summary>
public double SunAzimuth { get { return sunAzimuth; } }
/// <summary>
/// Moon altitude in degrees (corrected for parallax and refraction).
/// </summary>
public double MoonAltitude { get { return moonAltitude; } }
/// <summary>
/// Moon azimuth in degrees (E of N).
/// </summary>
public double MoonAzimuth { get { return moonAzimuth; } }
/// <summary>
/// Estimated moon distance from the earth.
/// </summary>
public Distance MoonDistance { get { return moonDistance; } }
/// <summary>
/// Sun's Condition based on the provided date.
/// </summary>
public CelestialStatus SunCondition { get { return sunCondition; } }
/// <summary>
/// Moon's condition based on the provided date.
/// </summary>
public CelestialStatus MoonCondition { get { return moonCondition; } }
/// <summary>
/// Determine if the sun is currently up, based on sunset and sunrise time at the provided location and date.
/// </summary>
public bool IsSunUp{ get { return isSunUp; } }
/// <summary>
/// Determine if the moon is currently up, based on moonset and moonrise time at the provided location and date.
/// </summary>
public bool IsMoonUp { get { return isMoonUp; } }
/// <summary>
/// Moon ilumination details based on the provided date.
/// </summary>
/// <remarks>
/// Contains phase, phase name, fraction and angle
/// </remarks>
public MoonIllum MoonIllum { get { return moonIllum; } }
/// <summary>
/// Moons perigee details based on the provided date.
/// </summary>
public Perigee Perigee { get { return perigee; } }
/// <summary>
/// Moons apogee details based on the provided date.
/// </summary>
public Apogee Apogee { get { return apogee; } }
/// <summary>
/// Additional solar event times based on the provided date and location.
/// </summary>
/// <remarks>Contains civil and nautical dawn and dusk times.</remarks>
public AdditionalSolarTimes AdditionalSolarTimes { get { return additionalSolarTimes; } }
/// <summary>
/// Astrological signs based on the provided date.
/// </summary>
/// <remarks>
/// Contains zodiac, moon sign and moon name during full moon events
/// </remarks>
public AstrologicalSigns AstrologicalSigns { get { return astrologicalSigns; } }
/// <summary>
/// Returns a SolarEclipse.
/// </summary>
public SolarEclipse SolarEclipse { get { return solarEclipse; } }
/// <summary>
/// Returns a LunarEclipse.
/// </summary>
public LunarEclipse LunarEclipse { get { return lunarEclipse; } }
/// <summary>
/// Calculates all celestial data. Coordinates will notify as changes occur
/// </summary>
/// <param name="lat">Decimal format latitude</param>
/// <param name="longi">Decimal format longitude</param>
/// <param name="date">Geographic DateTime</param>
internal void CalculateCelestialTime(double lat, double longi, DateTime date)
{
date = new DateTime(date.Year, date.Month, date.Day, date.Hour, date.Minute, date.Second, DateTimeKind.Utc);
SunCalc.CalculateSunTime(lat, longi, date, this);
MoonCalc.GetMoonTimes(date, lat, longi, this);
MoonCalc.GetMoonDistance(date, this);
SunCalc.CalculateZodiacSign(date, this);
MoonCalc.GetMoonSign(date, this);
MoonCalc.GetMoonIllumination(date, this,lat,longi);
perigee = MoonCalc.GetPerigeeEvents(date);
apogee = MoonCalc.GetApogeeEvents(date);
Calculate_Celestial_IsUp_Booleans(date, this);
}
/// <summary>
/// Calculate celestial data based on lat/long and date.
/// </summary>
/// <param name="lat">Decimal format latitude</param>
/// <param name="longi">Decimal format longitude</param>
/// <param name="date">Geographic DateTime</param>
/// <returns>Fully populated Celestial object</returns>
public static Celestial CalculateCelestialTimes(double lat, double longi, DateTime date)
{
date = new DateTime(date.Year, date.Month, date.Day, date.Hour, date.Minute, date.Second, DateTimeKind.Utc);
Celestial c = new Celestial(false);
SunCalc.CalculateSunTime(lat, longi, date, c);
MoonCalc.GetMoonTimes(date, lat, longi, c);
MoonCalc.GetMoonDistance(date, c);
SunCalc.CalculateZodiacSign(date, c);
MoonCalc.GetMoonSign(date, c);
MoonCalc.GetMoonIllumination(date, c,lat,longi);
c.perigee = MoonCalc.GetPerigeeEvents(date);
c.apogee = MoonCalc.GetApogeeEvents(date);
Calculate_Celestial_IsUp_Booleans(date, c);
return c;
}
/// <summary>
/// Calculate sun data based on lat/long and date.
/// </summary>
/// <param name="lat">latitude</param>
/// <param name="longi">longitude</param>
/// <param name="date">DateTime</param>
/// <returns>Celestial (Partially Populated)</returns>
public static Celestial CalculateSunData(double lat, double longi, DateTime date)
{
date = new DateTime(date.Year, date.Month, date.Day, date.Hour, date.Minute, date.Second, DateTimeKind.Utc);
Celestial c = new Celestial(false);
SunCalc.CalculateSunTime(lat, longi, date, c);
SunCalc.CalculateZodiacSign(date, c);
return c;
}
/// <summary>
/// Calculate moon data based on lat/long and date.
/// </summary>
/// <param name="lat">latitude</param>
/// <param name="longi">longitude</param>
/// <param name="date">DateTime</param>
/// <returns>Celestial (Partially Populated)</returns>
public static Celestial CalculateMoonData(double lat, double longi, DateTime date)
{
date = new DateTime(date.Year, date.Month, date.Day, date.Hour, date.Minute, date.Second, DateTimeKind.Utc);
Celestial c = new Celestial(false);
MoonCalc.GetMoonTimes(date, lat, longi, c);
MoonCalc.GetMoonDistance(date, c);
MoonCalc.GetMoonSign(date, c);
MoonCalc.GetMoonIllumination(date, c,lat,longi);
c.perigee = MoonCalc.GetPerigeeEvents(date);
c.apogee = MoonCalc.GetApogeeEvents(date);
return c;
}
/// <summary>
/// Returns a List containing solar eclipse data for the century.
/// Century return is based on the date passed.
/// </summary>
/// <param name="lat">latitude</param>
/// <param name="longi">longitude</param>
/// <param name="date">DateTime</param>
/// <returns></returns>
public static List<SolarEclipseDetails> Get_Solar_Eclipse_Table(double lat, double longi, DateTime date)
{
//Convert to Radians
double latR = lat * Math.PI / 180;
double longR = longi * Math.PI / 180;
//Get solar data based on date
double[] events = Eclipse.SolarData.SolarDateData_100Year(date);
//Return list of solar data.
return SolarEclipseCalc.CalculateSolarEclipse(date, latR, longR, events);
}
/// <summary>
/// Returns a List containing solar eclipse data for the century.
/// Century return is based on the date passed.
/// </summary>
/// <param name="lat">latitude</param>
/// <param name="longi">longitude</param>
/// <param name="date">DateTime</param>
/// <returns></returns>
public static List<LunarEclipseDetails> Get_Lunar_Eclipse_Table(double lat, double longi, DateTime date)
{
//Convert to Radians
double latR = lat * Math.PI / 180;
double longR = longi * Math.PI / 180;
//Get solar data based on date
double[] events = Eclipse.LunarData.LunarDateData_100Year(date);
//Return list of solar data.
return LunarEclipseCalc.CalculateLunarEclipse(date, latR, longR, events);
}
/// <summary>
/// Set bool SunIsUp and MoonIsUp values
/// </summary>
/// <param name="date">Coordinate GeoDate</param>
/// <param name="cel">Celestial Object</param>
private static void Calculate_Celestial_IsUp_Booleans(DateTime date, Celestial cel)
{
//SUN
switch (cel.SunCondition)
{
case CelestialStatus.DownAllDay:
cel.isSunUp = false;
break;
case CelestialStatus.UpAllDay:
cel.isSunUp = true;
break;
case CelestialStatus.NoRise:
if(date<cel.SunSet)
{
cel.isSunUp = true;
}
else { cel.isSunUp = false; }
break;
case CelestialStatus.NoSet:
if (date > cel.SunRise)
{
cel.isSunUp = true;
}
else { cel.isSunUp = false; }
break;
case CelestialStatus.RiseAndSet:
if (cel.SunRise < cel.SunSet)
{
if (date > cel.SunRise && date < cel.SunSet)
{
cel.isSunUp = true;
}
else
{
cel.isSunUp = false;
}
}
else
{
if (date > cel.SunRise || date < cel.SunSet)
{
cel.isSunUp = true;
}
else
{
cel.isSunUp = false;
}
}
break;
default:
//Should never be reached. If reached, previous calculations failed somewhere.
break;
}
//MOON
switch (cel.MoonCondition)
{
case CelestialStatus.DownAllDay:
cel.isMoonUp = false;
break;
case CelestialStatus.UpAllDay:
cel.isMoonUp = true;
break;
case CelestialStatus.NoRise:
if (date < cel.MoonSet)
{
cel.isMoonUp = true;
}
else { cel.isMoonUp = false; }
break;
case CelestialStatus.NoSet:
if (date > cel.MoonRise)
{
cel.isMoonUp = true;
}
else { cel.isMoonUp = false; }
break;
case CelestialStatus.RiseAndSet:
if (cel.MoonRise < cel.MoonSet)
{
if (date > cel.MoonRise && date < cel.MoonSet)
{
cel.isMoonUp = true;
}
else
{
cel.isMoonUp = false;
}
}
else
{
if (date > cel.MoonRise || date < cel.MoonSet)
{
cel.isMoonUp = true;
}
else
{
cel.isMoonUp = false;
}
}
break;
default:
//Should never be reached. If reached, previous calculations failed somewhere.
break;
}
}
/// <summary>
/// Returns Apogee object containing last and next apogee based on the specified date.
/// </summary>
/// <param name="d">DateTime</param>
/// <returns>Apogee</returns>
public static Apogee GetApogees(DateTime d)
{
return MoonCalc.GetApogeeEvents(d);
}
/// <summary>
/// Returns Perigee object containing last and next perigee based on the specified date.
/// </summary>
/// <param name="d">DateTime</param>
/// <returns>Perigee</returns>
public static Perigee GetPerigees(DateTime d)
{
return MoonCalc.GetPerigeeEvents(d);
}
}
}

View File

@ -0,0 +1,269 @@
using System;
using System.Collections.Generic;
using System.Linq;
namespace CoordinateSharp
{
/// <summary>
/// Used for UTM/MGRS Conversions
/// </summary>
[Serializable]
internal class LatZones
{
public static List<string> longZongLetters = new List<string>(new string[]{"C", "D", "E", "F", "G", "H", "J", "K", "L", "M", "N", "P", "Q", "R", "S", "T",
"U", "V", "W", "X"});
}
/// <summary>
/// Used for handling diagraph determination
/// </summary>
[Serializable]
internal class Digraphs
{
private List<Digraph> digraph1;
private List<Digraph> digraph2;
private String[] digraph1Array = { "A", "B", "C", "D", "E", "F", "G", "H",
"J", "K", "L", "M", "N", "P", "Q", "R", "S", "T", "U", "V", "W", "X",
"Y", "Z" };
private String[] digraph2Array = { "V", "A", "B", "C", "D", "E", "F", "G",
"H", "J", "K", "L", "M", "N", "P", "Q", "R", "S", "T", "U", "V" };
public Digraphs()
{
digraph1 = new List<Digraph>();
digraph2 = new List<Digraph>();
digraph1.Add(new Digraph() { Zone = 1, Letter = "A" });
digraph1.Add(new Digraph() { Zone = 2, Letter = "B" });
digraph1.Add(new Digraph() { Zone = 3, Letter = "C" });
digraph1.Add(new Digraph() { Zone = 4, Letter = "D" });
digraph1.Add(new Digraph() { Zone = 5, Letter = "E" });
digraph1.Add(new Digraph() { Zone = 6, Letter = "F" });
digraph1.Add(new Digraph() { Zone = 7, Letter = "G" });
digraph1.Add(new Digraph() { Zone = 8, Letter = "H" });
digraph1.Add(new Digraph() { Zone = 9, Letter = "J" });
digraph1.Add(new Digraph() { Zone = 10, Letter = "K" });
digraph1.Add(new Digraph() { Zone = 11, Letter = "L" });
digraph1.Add(new Digraph() { Zone = 12, Letter = "M" });
digraph1.Add(new Digraph() { Zone = 13, Letter = "N" });
digraph1.Add(new Digraph() { Zone = 14, Letter = "P" });
digraph1.Add(new Digraph() { Zone = 15, Letter = "Q" });
digraph1.Add(new Digraph() { Zone = 16, Letter = "R" });
digraph1.Add(new Digraph() { Zone = 17, Letter = "S" });
digraph1.Add(new Digraph() { Zone = 18, Letter = "T" });
digraph1.Add(new Digraph() { Zone = 19, Letter = "U" });
digraph1.Add(new Digraph() { Zone = 20, Letter = "V" });
digraph1.Add(new Digraph() { Zone = 21, Letter = "W" });
digraph1.Add(new Digraph() { Zone = 22, Letter = "X" });
digraph1.Add(new Digraph() { Zone = 23, Letter = "Y" });
digraph1.Add(new Digraph() { Zone = 24, Letter = "Z" });
digraph1.Add(new Digraph() { Zone = 1, Letter = "A" });
digraph2.Add(new Digraph() { Zone = 0, Letter = "V"});
digraph2.Add(new Digraph() { Zone = 1, Letter = "A" });
digraph2.Add(new Digraph() { Zone = 2, Letter = "B" });
digraph2.Add(new Digraph() { Zone = 3, Letter = "C" });
digraph2.Add(new Digraph() { Zone = 4, Letter = "D" });
digraph2.Add(new Digraph() { Zone = 5, Letter = "E" });
digraph2.Add(new Digraph() { Zone = 6, Letter = "F" });
digraph2.Add(new Digraph() { Zone = 7, Letter = "G" });
digraph2.Add(new Digraph() { Zone = 8, Letter = "H" });
digraph2.Add(new Digraph() { Zone = 9, Letter = "J" });
digraph2.Add(new Digraph() { Zone = 10, Letter = "K" });
digraph2.Add(new Digraph() { Zone = 11, Letter = "L" });
digraph2.Add(new Digraph() { Zone = 12, Letter = "M" });
digraph2.Add(new Digraph() { Zone = 13, Letter = "N" });
digraph2.Add(new Digraph() { Zone = 14, Letter = "P" });
digraph2.Add(new Digraph() { Zone = 15, Letter = "Q" });
digraph2.Add(new Digraph() { Zone = 16, Letter = "R" });
digraph2.Add(new Digraph() { Zone = 17, Letter = "S" });
digraph2.Add(new Digraph() { Zone = 18, Letter = "T" });
digraph2.Add(new Digraph() { Zone = 19, Letter = "U" });
digraph2.Add(new Digraph() { Zone = 20, Letter = "V" });
}
internal int getDigraph1Index(String letter)
{
for (int i = 0; i < digraph1Array.Length; i++)
{
if (digraph1Array[i].Equals(letter))
{
return i + 1;
}
}
return -1;
}
internal int getDigraph2Index(String letter)
{
for (int i = 0; i < digraph2Array.Length; i++)
{
if (digraph2Array[i].Equals(letter))
{
return i;
}
}
return -1;
}
internal String getDigraph1(int longZone, double easting)
{
int a1 = longZone;
double a2 = 8 * ((a1 - 1) % 3) + 1;
double a3 = easting;
double a4 = a2 + ((int)(a3 / 100000)) - 1;
return digraph1.Where(x=>x.Zone == Math.Floor(a4)).FirstOrDefault().Letter;
}
internal String getDigraph2(int longZone, double northing)
{
int a1 = longZone;
double a2 = 1 + 5 * ((a1 - 1) % 2);
double a3 = northing;
double a4 = (a2 + ((int)(a3 / 100000)));
a4 = (a2 + ((int)(a3 / 100000.0))) % 20;
a4 = Math.Floor(a4);
if (a4 < 0)
{
a4 = a4 + 19;
}
return digraph2.Where(x => x.Zone == Math.Floor(a4)).FirstOrDefault().Letter;
}
}
/// <summary>
/// Diagraph model
/// </summary>
[Serializable]
internal class Digraph
{
public int Zone { get; set; }
public string Letter { get; set; }
}
/// <summary>
/// Used for setting whether a coordinate part is latitudinal or longitudinal.
/// </summary>
[Serializable]
public enum CoordinateType
{
/// <summary>
/// Latitude
/// </summary>
Lat,
/// <summary>
/// Longitude
/// </summary>
Long
}
/// <summary>
/// Used to set a coordinate part position.
/// </summary>
[Serializable]
public enum CoordinatesPosition :int
{
/// <summary>
/// North
/// </summary>
N,
/// <summary>
/// East
/// </summary>
E,
/// <summary>
/// South
/// </summary>
S,
/// <summary>
/// West
/// </summary>
W
}
/// <summary>
/// Coordinate type datum specification
/// </summary>
[Serializable]
[Flags]
public enum Coordinate_Datum
{
/// <summary>
/// Lat Long GeoDetic
/// </summary>
LAT_LONG = 1,
/// <summary>
/// UTM and MGRS
/// </summary>
UTM_MGRS = 2,
/// <summary>
/// ECEF
/// </summary>
ECEF = 4,
}
/// <summary>
/// Cartesian Coordinate Type
/// </summary>
public enum CartesianType
{
/// <summary>
/// Spherical Cartesian
/// </summary>
Cartesian,
/// <summary>
/// Earth Centered Earth Fixed
/// </summary>
ECEF,
}
/// <summary>
/// Used for easy read math functions
/// </summary>
[Serializable]
internal static class ModM
{
public static double Mod(double x, double y)
{
return x - y * Math.Floor(x / y);
}
public static double ModLon(double x)
{
return Mod(x + Math.PI, 2 * Math.PI) - Math.PI;
}
public static double ModCrs(double x)
{
return Mod(x, 2 * Math.PI);
}
public static double ModLat(double x)
{
return Mod(x + Math.PI / 2, 2 * Math.PI) - Math.PI / 2;
}
}
/// <summary>
/// Earth Shape for Calculations.
/// </summary>
[Serializable]
public enum Shape
{
/// <summary>
/// Calculate as sphere (less accurate, more efficient).
/// </summary>
Sphere,
/// <summary>
/// Calculate as ellipsoid (more accurate, less efficient).
/// </summary>
Ellipsoid
}
}

View File

@ -0,0 +1,155 @@
using System;
using System.ComponentModel;
namespace CoordinateSharp
{
/// <summary>
/// Cartesian (X, Y, Z) Coordinate
/// </summary>
[Serializable]
public class Cartesian : INotifyPropertyChanged
{
/// <summary>
/// Create a Cartesian Object
/// </summary>
/// <param name="c"></param>
public Cartesian(Coordinate c)
{
//formulas:
x = Math.Cos(c.Latitude.ToRadians()) * Math.Cos(c.Longitude.ToRadians());
y = Math.Cos(c.Latitude.ToRadians()) * Math.Sin(c.Longitude.ToRadians());
z = Math.Sin(c.Latitude.ToRadians());
}
/// <summary>
/// Create a Cartesian Object
/// </summary>
/// <param name="xc">X</param>
/// <param name="yc">Y</param>
/// <param name="zc">Z</param>
public Cartesian(double xc, double yc, double zc)
{
//formulas:
x = xc;
y = yc;
z = zc;
}
/// <summary>
/// Updates Cartesian Values
/// </summary>
/// <param name="c"></param>
public void ToCartesian(Coordinate c)
{
x = Math.Cos(c.Latitude.ToRadians()) * Math.Cos(c.Longitude.ToRadians());
y = Math.Cos(c.Latitude.ToRadians()) * Math.Sin(c.Longitude.ToRadians());
z = Math.Sin(c.Latitude.ToRadians());
}
private double x;
private double y;
private double z;
/// <summary>
/// X Coordinate
/// </summary>
public double X
{
get { return x; }
set
{
if(x != value)
{
x = value;
NotifyPropertyChanged("X");
}
}
}
/// <summary>
/// y Coordinate
/// </summary>
public double Y
{
get { return y; }
set
{
if (y != value)
{
y = value;
NotifyPropertyChanged("Y");
}
}
}
/// <summary>
/// Z Coordinate
/// </summary>
public double Z
{
get { return z; }
set
{
if (z != value)
{
z = value;
NotifyPropertyChanged("Z");
}
}
}
/// <summary>
/// Returns a Lat Long Coordinate object based on the provided Cartesian Coordinate
/// </summary>
/// <param name="x">X</param>
/// <param name="y">Y</param>
/// <param name="z">Z</param>
/// <returns></returns>
public static Coordinate CartesianToLatLong(double x, double y, double z)
{
double lon = Math.Atan2(y, x);
double hyp = Math.Sqrt(x * x + y * y);
double lat = Math.Atan2(z, hyp);
double Lat = lat * (180 / Math.PI);
double Lon = lon * (180 / Math.PI);
return new Coordinate(Lat, Lon);
}
/// <summary>
/// Returns a Lat Long Coordinate object based on the provided Cartesian Coordinate
/// </summary>
/// <param name="cart">Cartesian Coordinate</param>
/// <returns></returns>
public static Coordinate CartesianToLatLong(Cartesian cart)
{
double x = cart.X;
double y = cart.Y;
double z = cart.Z;
double lon = Math.Atan2(y, x);
double hyp = Math.Sqrt(x * x + y * y);
double lat = Math.Atan2(z, hyp);
double Lat = lat * (180 / Math.PI);
double Lon = lon * (180 / Math.PI);
return new Coordinate(Lat, Lon);
}
/// <summary>
/// Cartesian Default String Format
/// </summary>
/// <returns>Cartesian Formatted Coordinate String</returns>
/// <returns>Values rounded to the 8th place</returns>
public override string ToString()
{
return Math.Round(x,8).ToString() + " " + Math.Round(y, 8).ToString() + " " + Math.Round(z, 8).ToString();
}
/// <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)
{
if (this.PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propName));
}
}
}
}

View File

@ -0,0 +1,566 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel;
namespace CoordinateSharp
{
/// <summary>
/// Earth Centered - Earth Fixed (X,Y,Z) Coordinate
/// </summary>
[Serializable]
public class ECEF : INotifyPropertyChanged
{
/// <summary>
/// Create an ECEF Object
/// </summary>
/// <param name="c">Coordinate</param>
public ECEF(Coordinate c)
{
equatorial_radius = 6378137.0;
inverse_flattening = 298.257223563;
WGS84();
geodetic_height = new Distance(0);
double[] ecef = LatLong_To_ECEF(c.Latitude.DecimalDegree, c.Longitude.DecimalDegree, geodetic_height.Kilometers);
x = ecef[0];
y = ecef[1];
z = ecef[2];
}
/// <summary>
/// Create an ECEF Object
/// </summary>
/// <param name="c">Coordinate</param>
/// <param name="height">Coordinate</param>
public ECEF(Coordinate c, Distance height)
{
equatorial_radius = 6378137.0;
inverse_flattening = 298.257223563;
WGS84();
geodetic_height = height;
double[] ecef = LatLong_To_ECEF(c.Latitude.DecimalDegree, c.Longitude.DecimalDegree, geodetic_height.Kilometers);
x = ecef[0];
y = ecef[1];
z = ecef[2];
}
/// <summary>
/// Create an ECEF Object
/// </summary>
/// <param name="xc">X</param>
/// <param name="yc">Y</param>
/// <param name="zc">Z</param>
public ECEF(double xc, double yc, double zc)
{
equatorial_radius = 6378137.0;
inverse_flattening = 298.257223563;
WGS84();
geodetic_height = new Distance(0);
x = xc;
y = yc;
z = zc;
}
/// <summary>
/// Updates ECEF Values
/// </summary>
/// <param name="c">Coordinate</param>
public void ToECEF(Coordinate c)
{
equatorial_radius = 6378137.0;
inverse_flattening = 298.257223563;
WGS84();
double[] ecef = LatLong_To_ECEF(c.Latitude.DecimalDegree, c.Longitude.DecimalDegree, geodetic_height.Kilometers);
x = ecef[0];
y = ecef[1];
z = ecef[2];
}
//Globals for calucations
private double EARTH_A;
private double EARTH_B;
private double EARTH_F;
private double EARTH_Ecc;
private double EARTH_Esq;
//ECEF Values
private double x;
private double y;
private double z;
private Distance geodetic_height;
//Datum
internal double equatorial_radius;
internal double inverse_flattening;
/// <summary>
/// Datum Equatorial Radius / Semi Major Axis
/// </summary>
public double Equatorial_Radius
{
get { return equatorial_radius; }
}
/// <summary>
/// Datum Flattening
/// </summary>
public double Inverse_Flattening
{
get { return inverse_flattening; }
}
/// <summary>
/// X Coordinate
/// </summary>
public double X
{
get { return x; }
set
{
if (x != value)
{
x = value;
NotifyPropertyChanged("X");
}
}
}
/// <summary>
/// y Coordinate
/// </summary>
public double Y
{
get { return y; }
set
{
if (y != value)
{
y = value;
NotifyPropertyChanged("Y");
}
}
}
/// <summary>
/// Z Coordinate
/// </summary>
public double Z
{
get { return z; }
set
{
if (z != value)
{
z = value;
NotifyPropertyChanged("Z");
}
}
}
/// <summary>
/// GeoDetic Height from Mean Sea Level.
/// Used for converting Lat Long / ECEF.
/// Default value is 0. Adjust as needed.
/// </summary>
public Distance GeoDetic_Height
{
get { return geodetic_height; }
internal set
{
if (geodetic_height != value)
{
geodetic_height = value;
NotifyPropertyChanged("Height");
}
}
}
/// <summary>
/// Sets GeoDetic height for ECEF conversion.
/// Recalculate ECEF Coordinate
/// </summary>
/// <param name="c">Coordinate</param>
/// <param name="dist">Height</param>
public void Set_GeoDetic_Height(Coordinate c, Distance dist)
{
geodetic_height = dist;
double[] values = LatLong_To_ECEF(c.Latitude.DecimalDegree, c.Longitude.DecimalDegree, dist.Kilometers);
x = values[0];
y = values[1];
z = values[2];
}
/// <summary>
/// Returns a Geodetic Coordinate object based on the provided ECEF Coordinate
/// </summary>
/// <param name="x">X</param>
/// <param name="y">Y</param>
/// <param name="z">Z</param>
/// <returns>Coordinate</returns>
public static Coordinate ECEFToLatLong(double x, double y, double z)
{
ECEF ecef = new ECEF(x, y, z);
double[] values = ecef.ECEF_To_LatLong(x, y, z);
ecef.geodetic_height =new Distance(values[2]);
Coordinate c = new Coordinate(values[0], values[1]);
c.ECEF = ecef;
return c;
}
/// <summary>
/// Returns a Geodetic Coordinate object based on the provided ECEF Coordinate
/// </summary>
/// <param name="ecef">ECEF Coordinate</param>
/// <returns>Coordinate</returns>
public static Coordinate ECEFToLatLong(ECEF ecef)
{
double[] values = ecef.ECEF_To_LatLong(ecef.X, ecef.Y, ecef.Z);
Coordinate c = new Coordinate(values[0], values[1]);
Distance height = new Distance(values[2]);
ecef.geodetic_height = new Distance(values[2]);
c.ECEF = ecef;
return c;
}
/// <summary>
/// ECEF Default String Format
/// </summary>
/// <returns>ECEF Formatted Coordinate String</returns>
/// <returns>Values rounded to the 3rd place</returns>
public override string ToString()
{
return Math.Round(x, 3).ToString() + " km, " + Math.Round(y, 3).ToString() + " km, " + Math.Round(z, 3).ToString() + " km";
}
/// <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)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propName));
}
}
//CONVERSION LOGIC
/// <summary>
/// Initialize EARTH global variables based on the Datum
/// </summary>
private void WGS84()
{
double wgs84a = equatorial_radius / 1000;
double wgs84f = 1.0 / inverse_flattening;
double wgs84b = wgs84a * (1.0 - wgs84f);
EarthCon(wgs84a, wgs84b);
}
/// <summary>
/// Sets Earth Constants as Globals
/// </summary>
/// <param name="a">a</param>
/// <param name="b">b</param>
private void EarthCon(double a, double b)
{
double f = 1 - b / a;
double eccsq = 1 - b * b / (a * a);
double ecc = Math.Sqrt(eccsq);
EARTH_A = a;
EARTH_B = b;
EARTH_F = f;
EARTH_Ecc = ecc;
EARTH_Esq = eccsq;
}
/// <summary>
/// Compute the radii at the geodetic latitude (degrees)
/// </summary>
/// <param name="lat">Latitude in degres</param>
/// <returns>double[]</returns>
private double[] radcur(double lat)
{
double[] rrnrm = new double[3];
double dtr = Math.PI / 180.0;
double a = EARTH_A;
double b = EARTH_B;
double asq = a * a;
double bsq = b * b;
double eccsq = 1 - bsq / asq;
double ecc = Math.Sqrt(eccsq);
double clat = Math.Cos(dtr * lat);
double slat = Math.Sin(dtr * lat);
double dsq = 1.0 - eccsq * slat * slat;
double d = Math.Sqrt(dsq);
double rn = a / d;
double rm = rn * (1.0 - eccsq) / dsq;
double rho = rn * clat;
double z = (1.0 - eccsq) * rn * slat;
double rsq = rho * rho + z * z;
double r = Math.Sqrt(rsq);
rrnrm[0] = r;
rrnrm[1] = rn;
rrnrm[2] = rm;
return (rrnrm);
}
/// <summary>
/// Physical radius of the Earth
/// </summary>
/// <param name="lat">Latidude in degrees</param>
/// <returns>double</returns>
private double rearth(double lat)
{
double[] rrnrm;
rrnrm = radcur(lat);
double r = rrnrm[0];
return r;
}
/// <summary>
/// Converts geocentric latitude to geodetic latitude
/// </summary>
/// <param name="flatgc">Geocentric latitude</param>
/// <param name="altkm">Altitude in KM</param>
/// <returns>double</returns>
private double gc2gd(double flatgc, double altkm)
{
var dtr = Math.PI / 180.0;
var rtd = 1 / dtr;
double ecc = EARTH_Ecc;
double esq = ecc * ecc;
//approximation by stages
//1st use gc-lat as if is gd, then correct alt dependence
double altnow = altkm;
double[] rrnrm = radcur(flatgc);
double rn = rrnrm[1];
double ratio = 1 - esq * rn / (rn + altnow);
double tlat = Math.Tan(dtr * flatgc) / ratio;
double flatgd = rtd * Math.Atan(tlat);
//now use this approximation for gd-lat to get rn etc.
rrnrm = radcur(flatgd);
rn = rrnrm[1];
ratio = 1 - esq * rn / (rn + altnow);
tlat = Math.Tan(dtr * flatgc) / ratio;
flatgd = rtd * Math.Atan(tlat);
return flatgd;
}
/// <summary>
/// Converts geodetic latitude to geocentric latitude
/// </summary>
/// <param name="flatgd">Geodetic latitude tp geocentric latitide</param>
/// <param name="altkm">Altitude in KM</param>
/// <returns>double</returns>
private double gd2gc(double flatgd, double altkm)
{
double dtr = Math.PI / 180.0;
double rtd = 1 / dtr;
double ecc = EARTH_Ecc;
double esq = ecc * ecc;
double altnow = altkm;
double[] rrnrm = radcur(flatgd);
double rn = rrnrm[1];
double ratio = 1 - esq * rn / (rn + altnow);
double tlat = Math.Tan(dtr * flatgd) * ratio;
double flatgc = rtd * Math.Atan(tlat);
return flatgc;
}
/// <summary>
/// Converts lat / long to east, north, up vectors
/// </summary>
/// <param name="flat">Latitude</param>
/// <param name="flon">Longitude</param>
/// <returns>Array[] of double[]</returns>
private Array[] llenu(double flat, double flon)
{
double clat, slat, clon, slon;
double[] ee = new double[3];
double[] en = new double[3];
double[] eu = new double[3];
Array[] enu = new Array[3];
double dtr = Math.PI / 180.0;
clat = Math.Cos(dtr * flat);
slat = Math.Sin(dtr * flat);
clon = Math.Cos(dtr * flon);
slon = Math.Sin(dtr * flon);
ee[0] = -slon;
ee[1] = clon;
ee[2] = 0.0;
en[0] = -clon * slat;
en[1] = -slon * slat;
en[2] = clat;
eu[0] = clon * clat;
eu[1] = slon * clat;
eu[2] = slat;
enu[0] = ee;
enu[1] = en;
enu[2] = eu;
return enu;
}
/// <summary>
/// Gets ECEF vector in KM
/// </summary>
/// <param name="lat">Latitude</param>
/// <param name="longi">Longitude</param>
/// <param name="altkm">Altitude in KM</param>
/// <returns>double[]</returns>
private double[] LatLong_To_ECEF(double lat, double longi, double altkm)
{
double dtr = Math.PI / 180.0;
double clat = Math.Cos(dtr * lat);
double slat = Math.Sin(dtr * lat);
double clon = Math.Cos(dtr * longi);
double slon = Math.Sin(dtr * longi);
double[] rrnrm = radcur(lat);
double rn = rrnrm[1];
double re = rrnrm[0];
double ecc = EARTH_Ecc;
double esq = ecc * ecc;
double x = (rn + altkm) * clat * clon;
double y = (rn + altkm) * clat * slon;
double z = ((1 - esq) * rn + altkm) * slat;
double[] xvec = new double[3];
xvec[0] = x;
xvec[1] = y;
xvec[2] = z;
return xvec;
}
/// <summary>
/// Converts ECEF X, Y, Z to GeoDetic Lat / Long and Height in KM
/// </summary>
/// <param name="x"></param>
/// <param name="y"></param>
/// <param name="z"></param>
/// <returns></returns>
private double[] ECEF_To_LatLong(double x, double y, double z)
{
var dtr = Math.PI / 180.0;
double[] rrnrm = new double[3];
double[] llhvec = new double[3];
double slat, tangd, flatn, dlat, clat;
double flat;
double altkm;
double esq = EARTH_Esq;
double rp = Math.Sqrt(x * x + y * y + z * z);
double flatgc = Math.Asin(z / rp) / dtr;
double flon;
double testval = Math.Abs(x) + Math.Abs(y);
if (testval < 1.0e-10)
{ flon = 0.0; }
else
{ flon = Math.Atan2(y, x) / dtr; }
if (flon < 0.0) { flon = flon + 360.0; }
double p = Math.Sqrt(x * x + y * y);
//Pole special case
if (p < 1.0e-10)
{
flat = 90.0;
if (z < 0.0) { flat = -90.0; }
altkm = rp - rearth(flat);
llhvec[0] = flat;
llhvec[1] = flon;
llhvec[2] = altkm;
return llhvec;
}
//first iteration, use flatgc to get altitude
//and alt needed to convert gc to gd lat.
double rnow = rearth(flatgc);
altkm = rp - rnow;
flat = gc2gd(flatgc, altkm);
rrnrm = radcur(flat);
double rn = rrnrm[1];
for (int kount = 0; kount < 5; kount++)
{
slat = Math.Sin(dtr * flat);
tangd = (z + rn * esq * slat) / p;
flatn = Math.Atan(tangd) / dtr;
dlat = flatn - flat;
flat = flatn;
clat = Math.Cos(dtr * flat);
rrnrm = radcur(flat);
rn = rrnrm[1];
altkm = (p / clat) - rn;
if (Math.Abs(dlat) < 1.0e-12) { break; }
}
//CONVERTER WORKS IN E LAT ONLY, IF E LAT > 180 LAT IS WEST SO IT MUCST BE CONVERTED TO Decimal
if (flon > 180) { flon = flon - 360; }
llhvec[0] = flat;
llhvec[1] = flon;
llhvec[2] = altkm;
return llhvec;
}
}
}

View File

@ -0,0 +1,119 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace CoordinateSharp
{
/// <summary>
/// Turn on/off eager loading of certain properties.
/// </summary>
[Serializable]
public class EagerLoad
{
/// <summary>
/// Create an EagerLoad object
/// </summary>
public EagerLoad()
{
Celestial = true;
UTM_MGRS = true;
Cartesian = true;
ECEF = true;
}
/// <summary>
/// Create an EagerLoad object with all options on or off
/// </summary>
/// <param name="isOn">Turns EagerLoad on or off</param>
public EagerLoad(bool isOn)
{
Celestial = isOn;
UTM_MGRS = isOn;
Cartesian = isOn;
ECEF = isOn;
}
/// <summary>
/// Create an EagerLoad object with only the specified flag options turned on.
/// </summary>
/// <param name="et">EagerLoadType</param>
public EagerLoad(EagerLoadType et)
{
Cartesian = false;
Celestial = false;
UTM_MGRS = false;
ECEF = false;
if (et.HasFlag(EagerLoadType.Cartesian))
{
Cartesian = true;
}
if (et.HasFlag(EagerLoadType.Celestial))
{
Celestial = true;
}
if (et.HasFlag(EagerLoadType.UTM_MGRS))
{
UTM_MGRS = true;
}
if (et.HasFlag(EagerLoadType.ECEF))
{
ECEF = true;
}
}
/// <summary>
/// Creates an EagerLoad object. Only the specified flags will be set to EagerLoad.
/// </summary>
/// <param name="et">EagerLoadType</param>
/// <returns>EagerLoad</returns>
public static EagerLoad Create(EagerLoadType et)
{
EagerLoad el = new EagerLoad(et);
return el;
}
/// <summary>
/// Eager load celestial information.
/// </summary>
public bool Celestial { get; set; }
/// <summary>
/// Eager load UTM and MGRS information
/// </summary>
public bool UTM_MGRS { get; set; }
/// <summary>
/// Eager load Cartesian information
/// </summary>
public bool Cartesian { get; set; }
/// <summary>
/// Eager load ECEF information
/// </summary>
public bool ECEF { get; set; }
}
/// <summary>
/// EagerLoad Enumerator
/// </summary>
[Serializable]
[Flags]
public enum EagerLoadType
{
/// <summary>
/// UTM and MGRS
/// </summary>
UTM_MGRS = 1,
/// <summary>
/// Celestial
/// </summary>
Celestial = 2,
/// <summary>
/// Cartesian
/// </summary>
Cartesian = 4,
/// <summary>
/// ECEF
/// </summary>
ECEF = 8
}
}

View File

@ -0,0 +1,107 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace CoordinateSharp
{
/// <summary>
/// Coordinate formatting options for a Coordinate object.
/// </summary>
[Serializable]
public class CoordinateFormatOptions
{
/// <summary>
/// Set default values with the constructor.
/// </summary>
public CoordinateFormatOptions()
{
Format = CoordinateFormatType.Degree_Minutes_Seconds;
Round = 3;
Display_Leading_Zeros = false;
Display_Trailing_Zeros = false;
Display_Symbols = true;
Display_Degree_Symbol = true;
Display_Minute_Symbol = true;
Display_Seconds_Symbol = true;
Display_Hyphens = false;
Position_First = true;
}
/// <summary>
/// Coordinate format type.
/// </summary>
public CoordinateFormatType Format { get; set; }
/// <summary>
/// Rounds Coordinates to the set value.
/// </summary>
public int Round { get; set; }
/// <summary>
/// Displays leading zeros.
/// </summary>
public bool Display_Leading_Zeros { get; set; }
/// <summary>
/// Display trailing zeros.
/// </summary>
public bool Display_Trailing_Zeros { get; set; }
/// <summary>
/// Allow symbols to display.
/// </summary>
public bool Display_Symbols { get; set; }
/// <summary>
/// Display degree symbols.
/// </summary>
public bool Display_Degree_Symbol { get; set; }
/// <summary>
/// Display minute symbols.
/// </summary>
public bool Display_Minute_Symbol { get; set; }
/// <summary>
/// Display secons symbol.
/// </summary>
public bool Display_Seconds_Symbol { get; set; }
/// <summary>
/// Display hyphens between values.
/// </summary>
public bool Display_Hyphens { get; set; }
/// <summary>
/// Show coordinate position first.
/// Will show last if set 'false'.
/// </summary>
public bool Position_First { get; set; }
}
/// <summary>
/// Coordinate Format Types.
/// </summary>
[Serializable]
public enum CoordinateFormatType
{
/// <summary>
/// Decimal Degree Format
/// </summary>
/// <remarks>
/// Example: N 40.456 W 75.456
/// </remarks>
Decimal_Degree,
/// <summary>
/// Decimal Degree Minutes Format
/// </summary>
/// <remarks>
/// Example: N 40º 34.552' W 70º 45.408'
/// </remarks>
Degree_Decimal_Minutes,
/// <summary>
/// Decimal Degree Minutes Format
/// </summary>
/// <remarks>
/// Example: N 40º 34" 36.552' W 70º 45" 24.408'
/// </remarks>
Degree_Minutes_Seconds,
/// <summary>
/// Decimal Format
/// </summary>
/// <remarks>
/// Example: 40.57674 -70.46574
/// </remarks>
Decimal
}
}

View File

@ -0,0 +1,293 @@
using System;
using System.Linq;
using System.Diagnostics;
using System.ComponentModel;
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, int 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 (!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."); }
latZone = latz;
longZone = longz;
digraph = d;
easting = e;
northing = n;
//WGS84
equatorialRadius = 6378137.0;
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, int 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 (!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."); }
latZone = latz;
longZone = longz;
digraph = d;
easting = e;
northing = n;
equatorialRadius = rad;
inverseFlattening = flt;
}
private double equatorialRadius;
private double inverseFlattening;
private string latZone;
private int longZone;
private double easting;
private double northing;
private string digraph;
private bool withinCoordinateSystemBounds=true;
private bool Verify_Lat_Zone(string l)
{
if (LatZones.longZongLetters.Where(x => x == l.ToUpper()).Count() != 1)
{
return false;
}
return true;
}
/// <summary>
/// MGRS Zone Letter
/// </summary>
public string LatZone
{
get { return latZone; }
}
/// <summary>
/// MGRS Zone Number
/// </summary>
public int LongZone
{
get { return longZone; }
}
/// <summary>
/// MGRS Easting
/// </summary>
public double Easting
{
get { return easting; }
}
/// <summary>
/// MGRS Northing
/// </summary>
public double Northing
{
get { return northing; }
}
/// <summary>
/// MGRS Digraph
/// </summary>
public string Digraph
{
get { return digraph; }
}
/// <summary>
/// Is MGRS conversion within the coordinate system's accurate boundaries after conversion from Lat/Long.
/// </summary>
public bool WithinCoordinateSystemBounds
{
get { return withinCoordinateSystemBounds; }
}
internal MilitaryGridReferenceSystem(UniversalTransverseMercator utm)
{
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);
digraph = digraph1 + digraph2;
latZone = utm.LatZone;
longZone = utm.LongZone;
//String easting = String.valueOf((int)_easting);
string e = ((int)utm.Easting).ToString();
if (e.Length < 5)
{
e = "00000" + ((int)utm.Easting).ToString();
}
e = e.Substring(e.Length - 5);
easting = Convert.ToInt32(e);
string n = ((int)utm.Northing).ToString();
if (n.Length < 5)
{
n = "0000" + ((int)utm.Northing).ToString();
}
n = n.Substring(n.Length - 5);
northing = Convert.ToInt32(n);
equatorialRadius = utm.equatorial_radius;
inverseFlattening = utm.inverse_flattening;
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 (int lt = 1; lt < 25; lt++)
{
digraphLettersAll += "ABCDEFGHJKLMNPQRSTUV";
}
var eidx = digraphLettersE.IndexOf(eltr);
var nidx = digraphLettersN.IndexOf(nltr);
if (mgrs.LongZone / 2.0 == Math.Floor(mgrs.LongZone / 2.0))
{
nidx -= 5; // correction for even numbered zones
}
var ebase = 100000 * (1 + eidx - 8 * Math.Floor(Convert.ToDouble(eidx) / 8));
var latBand = digraphLettersE.IndexOf(latz);
var latBandLow = 8 * latBand - 96;
var 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;
}
var lowLetter = Math.Floor(100 + 1.11 * latBandLow);
var highLetter = Math.Round(100 + 1.11 * latBandHigh);
string latBandLetters = null;
int l = Convert.ToInt32(lowLetter);
int h = Convert.ToInt32(highLetter);
if (mgrs.LongZone / 2.0 == Math.Floor(mgrs.LongZone / 2.0))
{
latBandLetters = digraphLettersAll.Substring(l + 5, h + 5).ToString();
}
else
{
latBandLetters = digraphLettersAll.Substring(l, h).ToString();
}
var nbase = 100000 * (lowLetter + latBandLetters.IndexOf(nltr));
//latBandLetters.IndexOf(nltr) value causing incorrect Northing below -80
var x = ebase + mgrs.Easting;
var y = nbase + mgrs.Northing;
if (y > 10000000)
{
y = y - 10000000;
}
if (nbase >= 10000000)
{
y = nbase + mgrs.northing - 10000000;
}
var southern = nbase < 10000000;
UniversalTransverseMercator utm = new UniversalTransverseMercator(mgrs.LatZone, mgrs.LongZone, x, y);
utm.equatorial_radius = mgrs.equatorialRadius;
utm.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()
{
if (!withinCoordinateSystemBounds) { return ""; }//MGRS Coordinate is outside its reliable boundaries. Return empty.
return longZone.ToString() + LatZone + " " + digraph + " " + ((int)easting).ToString("00000") + " " + ((int)northing).ToString("00000");
}
/// <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)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propName));
}
}
}
}

View File

@ -0,0 +1,925 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
namespace CoordinateSharp
{
/// <summary>
/// Type of format a Coordinate parsed from.
/// </summary>
[Serializable]
public enum Parse_Format_Type
{
/// <summary>
/// Coordinate was not initialized from a parser method.
/// </summary>
None,
/// <summary>
/// Signed Degree
/// DD.dddd
/// </summary>
Signed_Degree,
/// <summary>
/// Decimal Degree
/// P DD.dddd
/// </summary>
Decimal_Degree,
/// <summary>
/// Degree Decimal Minute
/// P DD MM.sss
/// </summary>
Degree_Decimal_Minute,
/// <summary>
/// Degree Minute Second
/// P DD MM SS.sss
/// </summary>
Degree_Minute_Second,
/// <summary>
/// Universal Transverse Mercator
/// </summary>
UTM,
/// <summary>
/// Military Grid Reference System
/// </summary>
MGRS,
/// <summary>
/// Spherical Cartesian
/// </summary>
Cartesian_Spherical,
/// <summary>
/// Earth Centered Earth Fixed
/// </summary>
Cartesian_ECEF
}
internal class FormatFinder
{
//Add main to Coordinate and tunnel to Format class. Add private methods to format.
//WHEN PARSING NO EXCPETIONS FOR OUT OF RANGE ARGS WILL BE THROWN
public static bool TryParse(string coordString, CartesianType ct, out Coordinate c)
{
//Turn of eagerload for efficiency
EagerLoad eg = new EagerLoad();
eg.Cartesian = false;
eg.Celestial = false;
eg.UTM_MGRS = false;
c = new Coordinate(eg);
string s = coordString;
s = s.Trim(); //Trim all spaces before and after string
double[] d;
//Try Signed Degree
if (TrySignedDegree(s, out d))
{
try
{
c = new Coordinate(d[0], d[1], eg);
c.Parse_Format = Parse_Format_Type.Signed_Degree;
return true;
}
catch
{//Parser failed try next method
}
}
//Try Decimal Degree
if (TryDecimalDegree(s, out d))
{
try
{
c = new Coordinate(d[0], d[1], eg);
c.Parse_Format = Parse_Format_Type.Decimal_Degree;
return true;
}
catch
{//Parser failed try next method
}
}
//Try DDM
if (TryDegreeDecimalMinute(s, out d))
{
try
{
//0 Lat Degree
//1 Lat Minute
//2 Lat Direction (0 = N, 1 = S)
//3 Long Degree
//4 Long Minute
//5 Long Direction (0 = E, 1 = W)
CoordinatesPosition latP = CoordinatesPosition.N;
CoordinatesPosition lngP = CoordinatesPosition.E;
if (d[2] != 0) { latP = CoordinatesPosition.S; }
if (d[5] != 0) { lngP = CoordinatesPosition.W; }
CoordinatePart lat = new CoordinatePart((int)d[0], d[1], latP);
CoordinatePart lng = new CoordinatePart((int)d[3], d[4], lngP);
c = new Coordinate(eg);
c.Latitude = lat;
c.Longitude = lng;
c.Parse_Format = Parse_Format_Type.Degree_Decimal_Minute;
return true;
}
catch
{//Parser failed try next method
}
}
//Try DMS
if (TryDegreeMinuteSecond(s, out d))
{
try
{
//0 Lat Degree
//1 Lat Minute
//2 Lat Second
//3 Lat Direction (0 = N, 1 = S)
//4 Long Degree
//5 Long Minute
//6 Long Second
//7 Long Direction (0 = E, 1 = W)
CoordinatesPosition latP = CoordinatesPosition.N;
CoordinatesPosition lngP = CoordinatesPosition.E;
if (d[3] != 0) { latP = CoordinatesPosition.S; }
if (d[7] != 0) { lngP = CoordinatesPosition.W; }
CoordinatePart lat = new CoordinatePart((int)d[0], (int)d[1], d[2], latP);
CoordinatePart lng = new CoordinatePart((int)d[4], (int)d[5], d[6], lngP);
c = new Coordinate(eg);
c.Latitude = lat;
c.Longitude = lng;
c.Parse_Format = Parse_Format_Type.Degree_Minute_Second;
return true;
}
catch
{//Parser failed try next method
}
}
string[] um;
//Try MGRS
if (TryMGRS(s, out um))
{
try
{
double zone = Convert.ToDouble(um[0]);
double easting = Convert.ToDouble(um[3]);
double northing = Convert.ToDouble(um[4]);
MilitaryGridReferenceSystem mgrs = new MilitaryGridReferenceSystem(um[1], (int)zone, um[2], easting, northing);
c = MilitaryGridReferenceSystem.MGRStoLatLong(mgrs);
c.Parse_Format = Parse_Format_Type.MGRS;
return true;
}
catch
{//Parser failed try next method
}
}
//Try UTM
if (TryUTM(s, out um))
{
try
{
double zone = Convert.ToDouble(um[0]);
double easting = Convert.ToDouble(um[2]);
double northing = Convert.ToDouble(um[3]);
UniversalTransverseMercator utm = new UniversalTransverseMercator(um[1], (int)zone, easting, northing);
c = UniversalTransverseMercator.ConvertUTMtoLatLong(utm);
c.Parse_Format = Parse_Format_Type.UTM;
return true;
}
catch
{//Parser failed try next method
}
}
//Try Cartesian
if (TryCartesian(s.ToUpper().Replace("KM", "").Replace("X","").Replace("Y", "").Replace("Z", ""), out d))
{
if (ct == CartesianType.Cartesian)
{
try
{
Cartesian cart = new Cartesian(d[0], d[1], d[2]);
c = Cartesian.CartesianToLatLong(cart);
c.Parse_Format = Parse_Format_Type.Cartesian_Spherical;
return true;
}
catch
{//Parser failed try next method
}
}
if (ct == CartesianType.ECEF)
{
try
{
ECEF ecef = new ECEF(d[0], d[1], d[2]);
c = ECEF.ECEFToLatLong(ecef);
c.Parse_Format = Parse_Format_Type.Cartesian_ECEF;
return true;
}
catch
{//Parser failed try next method
}
}
}
c = null;
return false;
}
private static bool TrySignedDegree(string s, out double[] d)
{
d = null;
if (Regex.Matches(s, @"[a-zA-Z]").Count != 0) { return false; } //Should contain no letters
string[] sA = SpecialSplit(s,false);
double lat;
double lng;
double degLat;
double minLat; //Minutes & MinSeconds
double secLat;
int signLat = 1;
double degLng;
double minLng; //Minutes & MinSeconds
double secLng;
int signLng = 1;
switch (sA.Count())
{
case 2:
if (!double.TryParse(sA[0], out lat))
{ return false; }
if (!double.TryParse(sA[1], out lng))
{ return false; }
d = new double[] { lat, lng };
return true;
case 4:
if (!double.TryParse(sA[0], out degLat))
{ return false; }
if (!double.TryParse(sA[1], out minLat))
{ return false; }
if (!double.TryParse(sA[2], out degLng))
{ return false; }
if (!double.TryParse(sA[3], out minLng))
{ return false; }
if (degLat < 0) { signLat = -1; }
if (degLng < 0) { signLng = -1; }
if (minLat >= 60 || minLat < 0) { return false; } //Handle in parser as degree will be incorrect.
if (minLng >= 60 || minLng < 0) { return false; } //Handle in parser as degree will be incorrect.
lat = (Math.Abs(degLat) + (minLat / 60.0)) * signLat;
lng = (Math.Abs(degLng) + (minLng / 60.0)) * signLng;
d = new double[] { lat, lng };
return true;
case 6:
if (!double.TryParse(sA[0], out degLat))
{ return false; }
if (!double.TryParse(sA[1], out minLat))
{ return false; }
if (!double.TryParse(sA[2], out secLat))
{ return false; }
if (!double.TryParse(sA[3], out degLng))
{ return false; }
if (!double.TryParse(sA[4], out minLng))
{ return false; }
if (!double.TryParse(sA[5], out secLng))
{ return false; }
if (degLat < 0) { signLat = -1; }
if (degLng < 0) { signLng = -1; }
if (minLat >= 60 || minLat < 0) { return false; } //Handle in parser as degree will be incorrect.
if (minLng >= 60 || minLng < 0) { return false; } //Handle in parser as degree will be incorrect.
if (secLat >= 60 || secLat < 0) { return false; } //Handle in parser as degree will be incorrect.
if (secLng >= 60 || secLng < 0) { return false; } //Handle in parser as degree will be incorrect.
lat = (Math.Abs(degLat) + (minLat / 60.0) + (secLat / 3600)) * signLat;
lng = (Math.Abs(degLng) + (minLng / 60.0) + (secLng / 3600)) * signLng;
d = new double[] { lat, lng };
return true;
default:
return false;
}
}
private static bool TryDecimalDegree(string s, out double[] d)
{
d = null;
if (Regex.Matches(s, @"[a-zA-Z]").Count != 2) { return false; } //Should only contain 1 letter.
string[] sA = SpecialSplit(s,true);
if (sA.Count() == 2 || sA.Count() == 4)
{
double lat;
double lng;
double latR = 1; //Sets negative if South
double lngR = 1; //Sets negative if West
//Contact get brin directional indicator together with string
if (sA.Count() == 4)
{
sA[0] += sA[1];
sA[1] = sA[2] + sA[3];
}
//Find Directions
if (!sA[0].Contains("N") && !sA[0].Contains("n"))
{
if (!sA[0].Contains("S") && !sA[0].Contains("s"))
{
return false;//No Direction Found
}
latR = -1;
}
if (!sA[1].Contains("E") && !sA[1].Contains("e"))
{
if (!sA[1].Contains("W") && !sA[1].Contains("w"))
{
return false;//No Direction Found
}
lngR = -1;
}
sA[0] = Regex.Replace(sA[0], "[^0-9.]", "");
sA[1] = Regex.Replace(sA[1], "[^0-9.]", "");
if (!double.TryParse(sA[0], out lat))
{ return false; }
if (!double.TryParse(sA[1], out lng))
{ return false; }
lat *= latR;
lng *= lngR;
d = new double[] { lat, lng };
return true;
}
return false;
}
private static bool TryDegreeDecimalMinute(string s, out double[] d)
{
d = null;
if (Regex.Matches(s, @"[a-zA-Z]").Count != 2) { return false; } //Should only contain 1 letter.
string[] sA = SpecialSplit(s,true);
if (sA.Count() == 4 || sA.Count() == 6)
{
double latD;
double latMS;
double lngD;
double lngMS;
double latR = 0; //Sets 1 if South
double lngR = 0; //Sets 1 if West
//Contact get in order to combine directional indicator together with string
//Should reduce 6 items to 4
if (sA.Count() == 6)
{
if (char.IsLetter(sA[0][0])) { sA[0] += sA[1]; sA[1] = sA[2]; }
else if (char.IsLetter(sA[1][0])) { sA[0] += sA[1]; sA[1] = sA[2]; }
else if (char.IsLetter(sA[2][0])) { sA[0] += sA[2]; }
else { return false; }
if (char.IsLetter(sA[3][0])) { sA[3] += sA[4]; sA[4] = sA[5]; }
else if (char.IsLetter(sA[4][0])) { sA[3] += sA[4]; sA[4] = sA[5]; }
else if (char.IsLetter(sA[5][0])) { sA[3] += sA[5]; }
else { return false; }
//Shift values for below logic
sA[2] = sA[3];
sA[3] = sA[4];
}
//Find Directions
if (!sA[0].Contains("N") && !sA[0].Contains("n") && !sA[1].Contains("N") && !sA[1].Contains("n"))
{
if (!sA[0].Contains("S") && !sA[0].Contains("s") && !sA[1].Contains("S") && !sA[1].Contains("s"))
{
return false;//No Direction Found
}
latR = 1;
}
if (!sA[2].Contains("E") && !sA[2].Contains("e") && !sA[3].Contains("E") && !sA[3].Contains("e"))
{
if (!sA[2].Contains("W") && !sA[2].Contains("w") && !sA[3].Contains("W") && !sA[3].Contains("w"))
{
return false;//No Direction Found
}
lngR = 1;
}
sA[0] = Regex.Replace(sA[0], "[^0-9.]", "");
sA[1] = Regex.Replace(sA[1], "[^0-9.]", "");
sA[2] = Regex.Replace(sA[2], "[^0-9.]", "");
sA[3] = Regex.Replace(sA[3], "[^0-9.]", "");
if (!double.TryParse(sA[0], out latD))
{ return false; }
if (!double.TryParse(sA[1], out latMS))
{ return false; }
if (!double.TryParse(sA[2], out lngD))
{ return false; }
if (!double.TryParse(sA[3], out lngMS))
{ return false; }
d = new double[] { latD, latMS, latR, lngD, lngMS, lngR };
return true;
}
return false;
}
private static bool TryDegreeMinuteSecond(string s, out double[] d)
{
d = null;
if (Regex.Matches(s, @"[a-zA-Z]").Count != 2) { return false; } //Should only contain 1 letter.
string[] sA = SpecialSplit(s,true);
if (sA.Count() == 6 || sA.Count() == 8)
{
double latD;
double latM;
double latS;
double lngD;
double lngM;
double lngS;
double latR = 0; //Sets 1 if South
double lngR = 0; //Sets 1 if West
//Contact get in order to combine directional indicator together with string
//Should reduce 8 items to 6
if (sA.Count() == 8)
{
if (char.IsLetter(sA[0][0])) { sA[0] += sA[1]; sA[1] = sA[2]; sA[2] = sA[3]; }
else if (char.IsLetter(sA[1][0])) { sA[0] += sA[1]; sA[1] = sA[2]; sA[2] = sA[3]; }
else if (char.IsLetter(sA[3][0])) { sA[0] += sA[3]; }
else { return false; }
if (char.IsLetter(sA[4][0])) { sA[4] += sA[5]; sA[5] = sA[6]; sA[6] = sA[7]; }
else if (char.IsLetter(sA[5][0])) { sA[4] += sA[5]; sA[5] = sA[6]; sA[6] = sA[7]; }
else if (char.IsLetter(sA[7][0])) { sA[4] += sA[7]; }
else { return false; }
//Shift values for below logic
sA[3] = sA[4];
sA[4] = sA[5];
sA[5] = sA[6];
}
//Find Directions
if (!sA[0].Contains("N") && !sA[0].Contains("n") && !sA[2].Contains("N") && !sA[2].Contains("n"))
{
if (!sA[0].Contains("S") && !sA[0].Contains("s") && !sA[2].Contains("S") && !sA[2].Contains("s"))
{
return false;//No Direction Found
}
latR = 1;
}
if (!sA[3].Contains("E") && !sA[3].Contains("e") && !sA[5].Contains("E") && !sA[5].Contains("e"))
{
if (!sA[3].Contains("W") && !sA[3].Contains("w") && !sA[5].Contains("W") && !sA[5].Contains("w"))
{
return false;//No Direction Found
}
lngR = 1;
}
sA[0] = Regex.Replace(sA[0], "[^0-9.]", "");
sA[1] = Regex.Replace(sA[1], "[^0-9.]", "");
sA[2] = Regex.Replace(sA[2], "[^0-9.]", "");
sA[3] = Regex.Replace(sA[3], "[^0-9.]", "");
sA[4] = Regex.Replace(sA[4], "[^0-9.]", "");
sA[5] = Regex.Replace(sA[5], "[^0-9.]", "");
if (!double.TryParse(sA[0], out latD))
{ return false; }
if (!double.TryParse(sA[1], out latM))
{ return false; }
if (!double.TryParse(sA[2], out latS))
{ return false; }
if (!double.TryParse(sA[3], out lngD))
{ return false; }
if (!double.TryParse(sA[4], out lngM))
{ return false; }
if (!double.TryParse(sA[5], out lngS))
{ return false; }
d = new double[] { latD, latM, latS, latR, lngD, lngM, lngS, lngR };
return true;
}
return false;
}
private static bool TryUTM(string s, out string[] utm)
{
utm = null;
string[] sA = SpecialSplit(s,false);
if (sA.Count() == 3 || sA.Count() == 4)
{
double zone;
string zoneL;
double easting;
double northing;
if (sA.Count() == 4)
{
if (char.IsLetter(sA[0][0])) { sA[0] += sA[1]; sA[1] = sA[2]; sA[2] = sA[3]; }
else if (char.IsLetter(sA[1][0])) { sA[0] += sA[1]; sA[1] = sA[2]; sA[2] = sA[3]; }
else { return false; }
}
zoneL = new string(sA[0].Where(Char.IsLetter).ToArray());
if (zoneL == string.Empty) { return false; }
sA[0] = Regex.Replace(sA[0], "[^0-9.]", "");
if (!double.TryParse(sA[0], out zone))
{ return false; }
if (!double.TryParse(sA[1], out easting))
{ return false; }
if (!double.TryParse(sA[2], out northing))
{ return false; }
utm = new string[] { zone.ToString(), zoneL, easting.ToString(), northing.ToString() };
return true;
}
return false;
}
private static bool TryMGRS(string s, out string[] mgrs)
{
mgrs = null;
string[] sA = SpecialSplit(s,false);
if (sA.Count() == 4 || sA.Count() == 5)
{
double zone;
string zoneL;
string diagraph;
double easting;
double northing;
if (sA.Count() == 5)
{
if (char.IsLetter(sA[0][0])) { sA[0] += sA[1]; sA[1] = sA[2]; sA[2] = sA[3]; }
else if (char.IsLetter(sA[1][0])) { sA[0] += sA[1]; sA[1] = sA[2]; sA[2] = sA[3]; }
else { return false; }
}
zoneL = new string(sA[0].Where(Char.IsLetter).ToArray());
if (zoneL == string.Empty) { return false; }
sA[0] = Regex.Replace(sA[0], "[^0-9.]", "");
diagraph = sA[1];
if (!double.TryParse(sA[0], out zone))
{ return false; }
if (!double.TryParse(sA[2], out easting))
{ return false; }
if (!double.TryParse(sA[3], out northing))
{ return false; }
mgrs = new string[] { zone.ToString(), zoneL, diagraph, easting.ToString(), northing.ToString() };
return true;
}
return false;
}
private static bool TryCartesian(string s, out double[] d)
{
d = null;
string[] sA = SpecialSplit(s,false);
if (sA.Count() == 3)
{
double x;
double y;
double z;
if (!double.TryParse(sA[0], out x))
{ return false; }
if (!double.TryParse(sA[1], out y))
{ return false; }
if (!double.TryParse(sA[2], out z))
{ return false; }
d = new double[] { x, y, z };
return true;
}
return false;
}
//KEEP DASHES FOR SIGNED AND CARTESIAN AS THEY ARE USED FOR NEGATVE VALUES
private static string[] SpecialSplit(string s, bool removeDashes)
{
s = s.Replace("°", " ");
s = s.Replace("º", " ");
s = s.Replace("'", " ");
s = s.Replace("\"", " ");
s = s.Replace(",", " ");
s = s.Replace("mE", " ");
s = s.Replace("mN", " ");
if (removeDashes)
{
s = s.Replace("-", " ");
}
return s.Split(new char[0], StringSplitOptions.RemoveEmptyEntries);
}
}
internal class FormatFinder_CoordPart
{
//Add main to Coordinate and tunnel to Format class. Add private methods to format.
//WHEN PARSING NO EXCPETIONS FOR OUT OF RANGE ARGS WILL BE THROWN
public static bool TryParse(string coordString, out CoordinatePart cp)
{
//Turn of eagerload for efficiency
EagerLoad eg = new EagerLoad();
int type = 0; //0 = unspecifed, 1 = lat, 2 = long;
eg.Cartesian = false;
eg.Celestial = false;
eg.UTM_MGRS = false;
cp = null;
Coordinate c = new Coordinate(eg);
string s = coordString;
s = s.Trim(); //Trim all spaces before and after string
double[] d;
if (s[0] == ',')
{
type = 2;
s = s.Replace(",", "");
s = s.Trim();
}
if (s[0] == '*')
{
type = 1;
s = s.Replace("*", "");
s = s.Trim();
}
if (TrySignedDegree(s, type, out d))
{
try
{
switch (type)
{
case 0:
//Attempt Lat first (default for signed)
try
{
cp = new CoordinatePart(d[0], CoordinateType.Lat);
c.Parse_Format = Parse_Format_Type.Signed_Degree;
return true;
}
catch
{
cp = new CoordinatePart(d[0], CoordinateType.Long);
c.Parse_Format = Parse_Format_Type.Signed_Degree;
return true;
}
case 1:
//Attempt Lat
cp = new CoordinatePart(d[0], CoordinateType.Lat);
c.Parse_Format = Parse_Format_Type.Signed_Degree;
return true;
case 2:
//Attempt long
cp = new CoordinatePart(d[0], CoordinateType.Long);
c.Parse_Format = Parse_Format_Type.Signed_Degree;
return true;
}
}
catch
{
//silent fail
}
}
//SIGNED DEGREE FAILED, REMOVE DASHES FOR OTHER FORMATS
s = s.Replace("-", " ");
//All other formats should contain 1 letter.
if (Regex.Matches(s, @"[a-zA-Z]").Count != 1) { return false; } //Should only contain 1 letter.
//Get Coord Direction
int direction = Find_Position(s);
if (direction == -1)
{
return false; //No direction found
}
//If Coordinate type int specified, look for mismatch
if (type == 1 && (direction == 1 || direction == 3))
{
return false; //mismatch
}
if (type == 2 && (direction == 0 || direction == 2))
{
return false; //mismatch
}
CoordinateType t;
if (direction == 0 || direction == 2) { t = CoordinateType.Lat; }
else { t = CoordinateType.Long; }
s = Regex.Replace(s, "[^0-9. ]", ""); //Remove directional character
s = s.Trim(); //Trim all spaces before and after string
//Try Decimal Degree with Direction
if (TryDecimalDegree(s, direction, out d))
{
try
{
cp = new CoordinatePart(d[0], t);
c.Parse_Format = Parse_Format_Type.Decimal_Degree;
return true;
}
catch
{//Parser failed try next method
}
}
//Try DDM
if (TryDegreeDecimalMinute(s, out d))
{
try
{
//0 Degree
//1 Minute
//2 Direction (0 = N, 1 = E, 2 = S, 3 = W)
cp = new CoordinatePart((int)d[0], d[1], (CoordinatesPosition)direction);
c.Parse_Format = Parse_Format_Type.Degree_Decimal_Minute;
return true;
}
catch
{
//Parser failed try next method
}
}
//Try DMS
if (TryDegreeMinuteSecond(s, out d))
{
try
{
//0 Degree
//1 Minute
//2 Second
//3 Direction (0 = N, 1 = E, 2 = S, 3 = W)
cp = new CoordinatePart((int)d[0], (int)d[1], d[2], (CoordinatesPosition)direction);
c.Parse_Format = Parse_Format_Type.Degree_Minute_Second;
return true;
}
catch
{//Parser failed try next method
}
}
return false;
}
private static bool TrySignedDegree(string s, int t, out double[] d)
{
d = null;
if (Regex.Matches(s, @"[a-zA-Z]").Count != 0) { return false; } //Should contain no letters
string[] sA = SpecialSplit(s, false);
double deg;
double min; //Minutes & MinSeconds
double sec;
int sign = 1;
switch (sA.Count())
{
case 1:
if (!double.TryParse(sA[0], out deg))
{ return false; }
d = new double[] { deg };
return true;
case 2:
if (!double.TryParse(sA[0], out deg))
{ return false; }
if (!double.TryParse(sA[1], out min))
{ return false; }
if (deg < 0) { sign = -1; }
if (min >= 60 || min < 0) { return false; } //Handle in parser as degree will be incorrect.
d = new double[] { (Math.Abs(deg) + (min / 60.0)) * sign };
return true;
case 3:
if (!double.TryParse(sA[0], out deg))
{ return false; }
if (!double.TryParse(sA[1], out min))
{ return false; }
if (!double.TryParse(sA[2], out sec))
{ return false; }
if (min >= 60 || min < 0) { return false; } //Handle in parser as degree will be incorrect.
if (sec >= 60 || sec < 0) { return false; } //Handle in parser as degree will be incorrect.
if (deg < 0) { sign = -1; }
d = new double[] { (Math.Abs(deg) + (min / 60.0) + (sec / 3600.0)) * sign };
return true;
default:
return false;
}
}
private static bool TryDecimalDegree(string s, int direction, out double[] d)
{
d = null;
int sign = 1;
//S or W
if (direction == 2 || direction == 3)
{
sign = -1;
}
double coord;
string[] sA = SpecialSplit(s, true);
if (sA.Count() == 1)
{
if (!double.TryParse(s, out coord))
{ return false; }
coord *= sign;
d = new double[] { coord };
return true;
}
return false;
}
private static bool TryDegreeDecimalMinute(string s, out double[] d)
{
d = null;
double deg;
double minSec;
string[] sA = SpecialSplit(s,true);
if (sA.Count() == 2)
{
if (!double.TryParse(sA[0], out deg))
{ return false; }
if (!double.TryParse(sA[1], out minSec))
{ return false; }
d = new double[] { deg, minSec };
return true;
}
return false;
}
private static bool TryDegreeMinuteSecond(string s, out double[] d)
{
d = null;
double deg;
double min;
double sec;
string[] sA = SpecialSplit(s,true);
if (sA.Count() == 3)
{
if (!double.TryParse(sA[0], out deg))
{ return false; }
if (!double.TryParse(sA[1], out min))
{ return false; }
if (!double.TryParse(sA[2], out sec))
{ return false; }
d = new double[] { deg, min, sec };
return true;
}
return false;
}
private static int Find_Position(string s)
{
//N=0
//E=1
//S=2
//W=3
//NOPOS = -1
//Find Directions
int part = -1;
if (s.Contains("N") || s.Contains("n"))
{
part = 0;
}
if (s.Contains("E") || s.Contains("e"))
{
part = 1;
}
if (s.Contains("S") || s.Contains("s"))
{
part = 2;
}
if (s.Contains("W") || s.Contains("w"))
{
part = 3;
}
return part;
}
//KEEP DASHES FOR SIGNED AND CARTESIAN AS THEY ARE USED FOR NEGATVE VALUES
private static string[] SpecialSplit(string s, bool removeDashes)
{
s = s.Replace("°", " ");
s = s.Replace("º", " ");
s = s.Replace("'", " ");
s = s.Replace("\"", " ");
s = s.Replace(",", " ");
s = s.Replace("mE", " ");
s = s.Replace("mN", " ");
if(removeDashes)
{
s = s.Replace("-", " ");
}
return s.Split(new char[0], StringSplitOptions.RemoveEmptyEntries);
}
}
}

View File

@ -0,0 +1,592 @@
using System;
using System.Linq;
using System.Diagnostics;
using System.ComponentModel;
namespace CoordinateSharp
{
/// <summary>
/// Universal Transverse Mercator (UTM) coordinate system. Uses the WGS 84 Datum.
/// </summary>
[Serializable]
public class UniversalTransverseMercator : INotifyPropertyChanged
{
/// <summary>
/// Creates a UniversalTransverMercator object with a WGS84 Datum.
/// </summary>
/// <param name="latz">Latitude zone</param>
/// <param name="longz">Longitude zone</param>
/// <param name="est">Easting</param>
/// <param name="nrt">Northing</param>
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;
}
/// <summary>
/// Creates a UniversalTransverMercator object with a custom Datum.
/// </summary>
/// <param name="latz">Latitude zone</param>
/// <param name="longz">Longitude zone</param>
/// <param name="est">Easting</param>
/// <param name="nrt">Northing</param>
/// <param name="radius">Equatorial Radius</param>
/// <param name="flaten">Inverse Flattening</param>
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;
/// <summary>
/// UTM Zone Letter
/// </summary>
public string LatZone
{
get { return latZone; }
set
{
if (latZone != value)
{
latZone = value;
}
}
}
/// <summary>
/// UTM Zone Number
/// </summary>
public int LongZone
{
get { return longZone; }
set
{
if (longZone != value)
{
longZone = value;
}
}
}
/// <summary>
/// UTM Easting
/// </summary>
public double Easting
{
get { return easting; }
set
{
if (easting != value)
{
easting = value;
}
}
}
/// <summary>
/// UTM Northing
/// </summary>
public double Northing
{
get { return northing; }
set
{
if (northing != value)
{
northing = value;
}
}
}
/// <summary>
/// Datum Equatorial Radius / Semi Major Axis
/// </summary>
public double Equatorial_Radius
{
get { return equatorial_radius; }
}
/// <summary>
/// Datum Flattening
/// </summary>
public double Inverse_Flattening
{
get { return inverse_flattening; }
}
/// <summary>
/// Is the UTM conversion within the coordinate system's accurate boundaries after conversion from Lat/Long.
/// </summary>
public bool WithinCoordinateSystemBounds
{
get { return withinCoordinateSystemBounds; }
}
/// <summary>
/// Constructs a UTM object based off DD Lat/Long
/// </summary>
/// <param name="lat">DD Latitude</param>
/// <param name="longi">DD Longitide</param>
/// <param name="c">Parent Coordinate Object</param>
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;
}
/// <summary>
/// Constructs a UTM object based off DD Lat/Long
/// </summary>
/// <param name="lat">DD Latitude</param>
/// <param name="longi">DD Longitide</param>
/// <param name="c">Parent Coordinate Object</param>
/// <param name="rad">Equatorial Radius</param>
/// <param name="flt">Flattening</param>
internal UniversalTransverseMercator(double lat, double longi, Coordinate c,double rad,double flt)
{
equatorial_radius = rad;
inverse_flattening = flt;
ToUTM(lat, longi, this);
coordinate = c;
}
/// <summary>
/// Constructs a UTM object based off a UTM coordinate
/// Not yet implemented
/// </summary>
/// <param name="latz">Zone Letter</param>
/// <param name="longz">Zone Number</param>
/// <param name="e">Easting</param>
/// <param name="n">Northing</param>
/// <param name="c">Parent Coordinate Object</param>
/// <param name="rad">Equatorial Radius</param>
/// <param name="flt">Inverse Flattening</param>
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; }
}
/// <summary>
/// Verifies Lat zone when convert from UTM to DD Lat/Long
/// </summary>
/// <param name="l">Zone Letter</param>
/// <returns>boolean</returns>
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;
}
/// <summary>
/// Assigns UTM values based of Lat/Long
/// </summary>
/// <param name="lat">DD Latitude</param>
/// <param name="longi">DD longitude</param>
/// <param name="utm">UTM Object to modify</param>
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; }
}
/// <summary>
/// UTM Default String Format
/// </summary>
/// <returns>UTM Formatted Coordinate String</returns>
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;
}
/// <summary>
/// Converts UTM coordinate to Lat/Long
/// </summary>
/// <param name="utm">utm</param>
/// <returns>Coordinate object</returns>
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;
}
/// <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)
{
if (this.PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propName));
}
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,86 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net40; netstandard1.3; netstandard1.4; netstandard2.0</TargetFrameworks>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<Version>1.1.5.2</Version>
<Authors>Justin Gielski</Authors>
<Company />
<PackageProjectUrl>https://github.com/Tronald/CoordinateSharp</PackageProjectUrl>
<PackageLicenseUrl></PackageLicenseUrl>
<Copyright>Copyright 2019</Copyright>
<Description>A simple .NET standard library that is designed to assist with geographic coordinate conversions, formatting and location based celestial calculations.</Description>
<PackageReleaseNotes>Fixes issues with distance bearings reversing in certain regions.</PackageReleaseNotes>
<PackageTags>CoordinateSharp Latitude Longitude Coordinates Geography Sun Moon Solar Lunar Time MGRS UTM Julian ECEF</PackageTags>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<PackageIconUrl>https://raw.githubusercontent.com/Tronald/CoordinateSharp/master/ICON.png</PackageIconUrl>
<PackageId>CoordinateSharp</PackageId>
<Title>CoordinateSharp</Title>
<AssemblyVersion>1.1.5.2</AssemblyVersion>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Release|net40|AnyCPU'">
<DocumentationFile>bin\Release\net40\CoordinateSharp.xml</DocumentationFile>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Release|netstandard1.3|AnyCPU'">
<DocumentationFile>bin\Release\netstandard1.3\CoordinateSharp.xml</DocumentationFile>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Release|netstandard1.4|AnyCPU'">
<DocumentationFile>bin\Release\netstandard1.4\CoordinateSharp.xml</DocumentationFile>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Release|netstandard2.0|AnyCPU'">
<DocumentationFile>bin\Release\netstandard2.0\CoordinateSharp.xml</DocumentationFile>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Debug|net40|AnyCPU'">
<DocumentationFile>bin\Debug\net40\CoordinateSharp.xml</DocumentationFile>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Debug|netstandard1.3|AnyCPU'">
<DocumentationFile>bin\Debug\netstandard1.3\CoordinateSharp.xml</DocumentationFile>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Debug|netstandard1.4|AnyCPU'">
<DocumentationFile>bin\Debug\netstandard1.4\CoordinateSharp.xml</DocumentationFile>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Debug|netstandard2.0|AnyCPU'">
<DocumentationFile>bin\Debug\netstandard2.0\CoordinateSharp.xml</DocumentationFile>
</PropertyGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'net40'">
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="Microsoft.CSharp" />
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'netstandard2.0'">
<PackageReference Include="System.Reflection.TypeExtensions">
<Version>4.5.0</Version>
</PackageReference>
<PackageReference Include="System.Runtime.Serialization.Formatters">
<Version>4.3.0</Version>
</PackageReference>
<PackageReference Include="System.Runtime.Serialization.Primitives">
<Version>4.3.0</Version>
</PackageReference>
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'netstandard1.4'">
<PackageReference Include="System.Reflection.TypeExtensions">
<Version>4.5.0</Version>
</PackageReference>
<PackageReference Include="System.Runtime.Serialization.Formatters">
<Version>4.3.0</Version>
</PackageReference>
<PackageReference Include="System.Runtime.Serialization.Primitives">
<Version>4.3.0</Version>
</PackageReference>
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'netstandard1.3'">
<PackageReference Include="System.Reflection.TypeExtensions">
<Version>4.5.0</Version>
</PackageReference>
<PackageReference Include="System.Runtime.Serialization.Formatters">
<Version>4.3.0</Version>
</PackageReference>
<PackageReference Include="System.Runtime.Serialization.Primitives">
<Version>4.3.0</Version>
</PackageReference>
</ItemGroup>
</Project>

442
CoordinateSharp/Distance.cs Normal file
View File

@ -0,0 +1,442 @@
using System;
using System.Diagnostics;
namespace CoordinateSharp
{
/// <summary>
/// Contains distance values between two coordinates.
/// </summary>
[Serializable]
public class Distance
{
private double kilometers;
private double miles;
private double feet;
private double meters;
private double bearing;
private double nauticalMiles;
/// <summary>
/// Initializes a distance object using Haversine (Spherical Earth).
/// </summary>
/// <param name="c1">Coordinate 1</param>
/// <param name="c2">Coordinate 2</param>
public Distance(Coordinate c1, Coordinate c2)
{
Haversine(c1, c2);
}
/// <summary>
/// Initializes a distance object using Haversine (Spherical Earth) or Vincenty (Elliptical Earth).
/// </summary>
/// <param name="c1">Coordinate 1</param>
/// <param name="c2">Coordinate 2</param>
/// <param name="shape">Shape of earth</param>
public Distance(Coordinate c1, Coordinate c2, Shape shape)
{
if (shape == Shape.Sphere)
{
Haversine(c1, c2);
}
else
{
Vincenty(c1, c2);
}
}
/// <summary>
/// Initializes distance object based on distance in KM
/// </summary>
/// <param name="km">Kilometers</param>
public Distance(double km)
{
kilometers = km;
meters = km * 1000;
feet = meters * 3.28084;
miles = meters * 0.000621371;
nauticalMiles = meters * 0.0005399565;
bearing = 0;//None specified
}
/// <summary>
/// Initializaes distance object based on specified distance and measurement type
/// </summary>
/// <param name="distance">Distance</param>
/// <param name="type">Measurement type</param>
public Distance(double distance, DistanceType type)
{
bearing = 0;
switch (type)
{
case DistanceType.Feet:
feet = distance;
meters = feet * 0.3048;
kilometers = meters / 1000;
miles = meters * 0.000621371;
nauticalMiles = meters * 0.0005399565;
break;
case DistanceType.Kilometers:
kilometers = distance;
meters = kilometers * 1000;
feet = meters * 3.28084;
miles = meters * 0.000621371;
nauticalMiles = meters * 0.0005399565;
break;
case DistanceType.Meters:
meters = distance;
kilometers = meters / 1000;
feet = meters * 3.28084;
miles = meters * 0.000621371;
nauticalMiles = meters * 0.0005399565;
break;
case DistanceType.Miles:
miles = distance;
meters = miles * 1609.344;
feet = meters * 3.28084;
kilometers = meters / 1000;
nauticalMiles = meters * 0.0005399565;
break;
case DistanceType.NauticalMiles:
nauticalMiles = distance;
meters = nauticalMiles * 1852.001;
feet = meters * 3.28084;
kilometers = meters / 1000;
miles = meters * 0.000621371;
break;
default:
kilometers = distance;
meters = distance * 1000;
feet = meters * 3.28084;
miles = meters * 0.000621371;
nauticalMiles = meters * 0.0005399565;
break;
}
}
private void Vincenty(Coordinate coord1, Coordinate coord2)
{
double lat1, lat2, lon1, lon2;
double d, crs12, crs21;
lat1 = coord1.Latitude.ToRadians();
lat2 = coord2.Latitude.ToRadians();
lon1 = coord1.Longitude.ToRadians() * -1; //REVERSE FOR CALC 2.1.1.1
lon2 = coord2.Longitude.ToRadians() * -1; //REVERSE FOR CALC 2.1.1.1
//Ensure datums match between coords
if ((coord1.equatorial_radius != coord2.equatorial_radius) || (coord1.inverse_flattening != coord2.inverse_flattening))
{
throw new InvalidOperationException("The datum set does not match between Coordinate objects.");
}
double[] ellipse = new double[] { coord1.equatorial_radius, coord1.inverse_flattening };
// elliptic code
double[] cde = Distance_Assistant.Dist_Ell(lat1, -lon1, lat2, -lon2, ellipse); // ellipse uses East negative
crs12 = cde[1] * (180 / Math.PI); //Bearing
crs21 = cde[2] * (180 / Math.PI); //Reverse Bearing
d = cde[0]; //Distance
bearing = crs12;
//reverseBearing = crs21;
meters = d;
kilometers = d / 1000;
feet = d * 3.28084;
miles = d * 0.000621371;
nauticalMiles = d * 0.0005399565;
}
private void Haversine(Coordinate coord1, Coordinate coord2)
{
////RADIANS
double lat1 = coord1.Latitude.ToRadians();
double long1 = coord1.Longitude.ToRadians();
double lat2 = coord2.Latitude.ToRadians();
double long2 = coord2.Longitude.ToRadians();
//Distance Calcs
double R = 6371000; //6378137.0;//6371e3; //meters
double latRad = coord2.Latitude.ToRadians() - coord1.Latitude.ToRadians();
double longRad = coord2.Longitude.ToRadians() - coord1.Longitude.ToRadians();
double a = Math.Sin(latRad / 2.0) * Math.Sin(latRad / 2.0) +
Math.Cos(lat1) * Math.Cos(lat2) * Math.Sin(longRad / 2.0) * Math.Sin(longRad / 2.0);
double cl = 2 * Math.Atan2(Math.Sqrt(a), Math.Sqrt(1 - a));
double dist = R * cl;
//Get bearing
double dLong = long2 - long1;
double y = Math.Sin(dLong) * Math.Cos(lat2);
double x = Math.Cos(lat1) * Math.Sin(lat2) - Math.Sin(lat1) * Math.Cos(lat2) * Math.Cos(dLong);
double brng = Math.Atan2(y, x) * (180 / Math.PI); //Convert bearing back to degrees.
//if (brng < 0) { brng -= 180; brng = Math.Abs(brng); }
brng = (brng + 360) % 360; //v2.1.1.1 NORMALIZE HEADING
kilometers = dist / 1000;
meters = dist;
feet = dist * 3.28084;
miles = dist * 0.000621371;
nauticalMiles = dist * 0.0005399565;
bearing = brng;
}
/// <summary>
/// Distance in Kilometers
/// </summary>
public double Kilometers
{
get { return kilometers; }
}
/// <summary>
/// Distance in Statute Miles
/// </summary>
public double Miles
{
get { return miles; }
}
/// <summary>
/// Distance in Nautical Miles
/// </summary>
public double NauticalMiles
{
get { return nauticalMiles; }
}
/// <summary>
/// Distance in Meters
/// </summary>
public double Meters
{
get { return meters; }
}
/// <summary>
/// Distance in Feet
/// </summary>
public double Feet
{
get { return feet; }
}
/// <summary>
/// Initial Bearing from Coordinate 1 to Coordinate 2
/// </summary>
public double Bearing
{
get { return bearing; }
}
}
/// <summary>
/// Distance measurement type
/// </summary>
public enum DistanceType
{
/// <summary>
/// Distance in Meters
/// </summary>
Meters,
/// <summary>
/// Distance in Kilometers
/// </summary>
Kilometers,
/// <summary>
/// Distance in Feet
/// </summary>
Feet,
/// <summary>
/// Distance in Statute Miles
/// </summary>
Miles,
/// <summary>
/// Distance in Nautical Miles
/// </summary>
NauticalMiles
}
[Serializable]
internal class Distance_Assistant
{
/// <summary>
/// Returns new geodetic coordinate in radians
/// </summary>
/// <param name="glat1">Latitude in Radians</param>
/// <param name="glon1">Longitude in Radians</param>
/// <param name="faz">Bearing</param>
/// <param name="s">Distance</param>
/// <param name="ellipse">Earth Ellipse Values</param>
/// <returns>double[]</returns>
public static double[] Direct_Ell(double glat1, double glon1, double faz, double s, double[] ellipse)
{
glon1 *= -1; //REVERSE LONG FOR CALC 2.1.1.1
double EPS = 0.00000000005;//Used to determine if starting at pole.
double r, tu, sf, cf, b, cu, su, sa, c2a, x, c, d, y, sy = 0, cy = 0, cz = 0, e = 0;
double glat2, glon2, f;
//Determine if near pole
if ((Math.Abs(Math.Cos(glat1)) < EPS) && !(Math.Abs(Math.Sin(faz)) < EPS))
{
Debug.WriteLine("Warning: Location is at earth's pole. Only N-S courses are meaningful at this location.");
}
double a = ellipse[0];//Equitorial Radius
f = 1 / ellipse[1];//Flattening
r = 1 - f;
tu = r * Math.Tan(glat1);
sf = Math.Sin(faz);
cf = Math.Cos(faz);
if (cf == 0)
{
b = 0.0;
}
else
{
b = 2.0 * Math.Atan2(tu, cf);
}
cu = 1.0 / Math.Sqrt(1 + tu * tu);
su = tu * cu;
sa = cu * sf;
c2a = 1 - sa * sa;
x = 1.0 + Math.Sqrt(1.0 + c2a * (1.0 / (r * r) - 1.0));
x = (x - 2.0) / x;
c = 1.0 - x;
c = (x * x / 4.0 + 1.0) / c;
d = (0.375 * x * x - 1.0) * x;
tu = s / (r * a * c);
y = tu;
c = y + 1;
while (Math.Abs(y - c) > EPS)
{
sy = Math.Sin(y);
cy = Math.Cos(y);
cz = Math.Cos(b + y);
e = 2.0 * cz * cz - 1.0;
c = y;
x = e * cy;
y = e + e - 1.0;
y = (((sy * sy * 4.0 - 3.0) * y * cz * d / 6.0 + x) *
d / 4.0 - cz) * sy * d + tu;
}
b = cu * cy * cf - su * sy;
c = r * Math.Sqrt(sa * sa + b * b);
d = su * cy + cu * sy * cf;
glat2 = ModM.ModLat(Math.Atan2(d, c));
c = cu * cy - su * sy * cf;
x = Math.Atan2(sy * sf, c);
c = ((-3.0 * c2a + 4.0) * f + 4.0) * c2a * f / 16.0;
d = ((e * cy * c + cz) * sy * c + y) * sa;
glon2 = ModM.ModLon(glon1 + x - (1.0 - c) * d * f); //Adjust for IDL
//baz = ModM.ModCrs(Math.Atan2(sa, b) + Math.PI);
return new double[] { glat2, glon2 };
}
/// <summary>
/// Returns new geodetic coordinate in radians
/// </summary>
/// <param name="lat1">Latitude in radians</param>
/// <param name="lon1">Longitude in radians</param>
/// <param name="crs12">Bearing</param>
/// <param name="d12">Distance</param>
/// <returns>double[]</returns>
public static double[] Direct(double lat1, double lon1, double crs12, double d12)
{
lon1 *= -1; //REVERSE LONG FOR CALC 2.1.1.1
var EPS = 0.00000000005;//Used to determine if near pole.
double dlon, lat, lon;
d12 = d12 * 0.0005399565; //convert meter to nm
d12 = d12 / (180 * 60 / Math.PI);//Convert to Radian
//Determine if near pole
if ((Math.Abs(Math.Cos(lat1)) < EPS) && !(Math.Abs(Math.Sin(crs12)) < EPS))
{
Debug.WriteLine("Warning: Location is at earth's pole. Only N-S courses are meaningful at this location.");
}
lat = Math.Asin(Math.Sin(lat1) * Math.Cos(d12) +
Math.Cos(lat1) * Math.Sin(d12) * Math.Cos(crs12));
if (Math.Abs(Math.Cos(lat)) < EPS)
{
lon = 0.0; //endpoint a pole
}
else
{
dlon = Math.Atan2(Math.Sin(crs12) * Math.Sin(d12) * Math.Cos(lat1),
Math.Cos(d12) - Math.Sin(lat1) * Math.Sin(lat));
lon = ModM.Mod(lon1 - dlon + Math.PI, 2 * Math.PI) - Math.PI;
}
return new double[] { lat, lon };
}
public static double[] Dist_Ell(double glat1, double glon1, double glat2, double glon2, double[] ellipse)
{
double a = ellipse[0]; //Equitorial Radius
double f = 1 / ellipse[1]; //Flattening
double r, tu1, tu2, cu1, su1, cu2, s1, b1, f1;
double x = 0, sx = 0, cx = 0, sy = 0, cy = 0, y = 0, sa = 0, c2a = 0, cz = 0, e = 0, c = 0, d = 0;
double EPS = 0.00000000005;
double faz, baz, s;
double iter = 1;
double MAXITER = 100;
if ((glat1 + glat2 == 0.0) && (Math.Abs(glon1 - glon2) == Math.PI))
{
Debug.WriteLine("Warning: Course and distance between antipodal points is undefined");
glat1 = glat1 + 0.00001; // allow algorithm to complete
}
if (glat1 == glat2 && (glon1 == glon2 || Math.Abs(Math.Abs(glon1 - glon2) - 2 * Math.PI) < EPS))
{
Debug.WriteLine("Warning: Points 1 and 2 are identical- course undefined");
//D
//crs12
//crs21
return new double[] { 0, 0, Math.PI };
}
r = 1 - f;
tu1 = r * Math.Tan(glat1);
tu2 = r * Math.Tan(glat2);
cu1 = 1.0 / Math.Sqrt(1.0 + tu1 * tu1);
su1 = cu1 * tu1;
cu2 = 1.0 / Math.Sqrt(1.0 + tu2 * tu2);
s1 = cu1 * cu2;
b1 = s1 * tu2;
f1 = b1 * tu1;
x = glon2 - glon1;
d = x + 1; // force one pass
while ((Math.Abs(d - x) > EPS) && (iter < MAXITER))
{
iter = iter + 1;
sx = Math.Sin(x);
cx = Math.Cos(x);
tu1 = cu2 * sx;
tu2 = b1 - su1 * cu2 * cx;
sy = Math.Sqrt(tu1 * tu1 + tu2 * tu2);
cy = s1 * cx + f1;
y = Math.Atan2(sy, cy);
sa = s1 * sx / sy;
c2a = 1 - sa * sa;
cz = f1 + f1;
if (c2a > 0.0)
{
cz = cy - cz / c2a;
}
e = cz * cz * 2.0 - 1.0;
c = ((-3.0 * c2a + 4.0) * f + 4.0) * c2a * f / 16.0;
d = x;
x = ((e * cy * c + cz) * sy * c + y) * sa;
x = (1.0 - c) * x * f + glon2 - glon1;
}
faz = ModM.ModCrs(Math.Atan2(tu1, tu2));
baz = ModM.ModCrs(Math.Atan2(cu1 * sx, b1 * cx - su1 * cu2) + Math.PI);
x = Math.Sqrt((1 / (r * r) - 1) * c2a + 1);
x += 1;
x = (x - 2.0) / x;
c = 1.0 - x;
c = (x * x / 4.0 + 1.0) / c;
d = (0.375 * x * x - 1.0) * x;
x = e * cy;
s = ((((sy * sy * 4.0 - 3.0) * (1.0 - e - e) * cz * d / 6.0 - x) * d / 4.0 + cz) * sy * d + y) * c * a * r;
if (Math.Abs(iter - MAXITER) < EPS)
{
Debug.WriteLine("Warning: Distance algorithm did not converge");
}
return new double[] { s, faz, baz };
}
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

161
CoordinateSharp/GeoFence.cs Normal file
View File

@ -0,0 +1,161 @@
using System.Collections.Generic;
using System.Linq;
namespace CoordinateSharp
{
/// <summary>
/// Geo Fence class. It helps to check if points/coordinates are inside a polygon,
/// Next to a polyline, and counting...
/// </summary>
public class GeoFence
{
#region Fields
private List<Point> _points = new List<Point>();
#endregion
/// <summary>
/// Prepare GeoFence with a list of points
/// </summary>
/// <param name="points">List of points</param>
public GeoFence(List<Point> points)
{
_points = points;
}
/// <summary>
/// Prepare Geofence with a list of coordinates
/// </summary>
/// <param name="coordinates">List of coordinates</param>
public GeoFence(List<Coordinate> coordinates)
{
foreach (var c in coordinates)
{
_points.Add(new Point { Latitude = c.Latitude.ToDouble(), Longitude = c.Longitude.ToDouble() });
}
}
#region Utils
private Coordinate ClosestPointOnSegment(Point a, Point b, Coordinate p)
{
var d = new Point
{
Longitude = b.Longitude - a.Longitude,
Latitude = b.Latitude - a.Latitude,
};
double number = (p.Longitude.ToDouble() - a.Longitude) * d.Longitude + (p.Latitude.ToDouble() - a.Latitude) * d.Latitude;
if (number <= 0.0)
return new Coordinate(a.Latitude, a.Longitude);
double denom = d.Longitude * d.Longitude + d.Latitude * d.Latitude;
if (number >= denom)
return new Coordinate(b.Latitude, b.Longitude);
return new Coordinate(a.Latitude + (number / denom) * d.Latitude, a.Longitude + (number / denom) * d.Longitude);
}
#endregion
/// <summary>
/// The function will return true if the point x,y is inside the polygon, or
/// false if it is not. If the point is exactly on the edge of the polygon,
/// then the function may return true or false.
/// </summary>
/// <param name="point">The point to test</param>
/// <returns>bool</returns>
public bool IsPointInPolygon(Coordinate point)
{
if (point == null)
return false;
double latitude = point.Latitude.ToDouble();
double longitude = point.Longitude.ToDouble();
int sides = _points.Count;
int j = sides - 1;
bool pointStatus = false;
for (int i = 0; i < sides; i++)
{
if (_points[i].Latitude < latitude && _points[j].Latitude >= latitude || _points[j].Latitude < latitude && _points[i].Latitude >= latitude)
{
if (_points[i].Longitude + (latitude - _points[i].Latitude) / (_points[j].Latitude - _points[i].Latitude) * (_points[j].Longitude - _points[i].Longitude) < longitude)
{
pointStatus = !pointStatus;
}
}
j = i;
}
return pointStatus;
}
/// <summary>
/// The function will return true if the point x,y is next the given range of
/// the polyline, or false if it is not.
/// </summary>
/// <param name="point">The point to test</param>
/// <param name="range">The range in meters</param>
/// <returns>bool</returns>
public bool IsPointInRangeOfLine(Coordinate point, double range)
{
if (point == null)
return false;
for (int i = 0; i < _points.Count - 1; i++)
{
Coordinate c = ClosestPointOnSegment(_points[i], _points[i + 1], point);
if (c.Get_Distance_From_Coordinate(point).Meters <= range)
return true;
}
return false;
}
/// <summary>
/// The function will return true if the point x,y is next the given range of
/// the polyline, or false if it is not.
/// </summary>
/// <param name="point">The point to test</param>
/// <param name="range">The range is a distance object</param>
/// <returns>bool</returns>
public bool IsPointInRangeOfLine(Coordinate point, Distance range)
{
if (point == null || range == null)
return false;
return IsPointInRangeOfLine(point, range.Meters);
}
/// <summary>
/// This class is a help class to simplify GeoFence calculus
/// </summary>
public class Point
{
/// <summary>
/// Initialize empty point
/// </summary>
public Point()
{
}
/// <summary>
/// Initialize point with defined Latitude and Longitude
/// </summary>
/// <param name="lat">Latitude (signed)</param>
/// <param name="lng">Longitude (signed)</param>
public Point(double lat, double lng)
{
Latitude = lat;
Longitude = lng;
}
/// <summary>
/// The longitude in degrees
/// </summary>
public double Longitude;
/// <summary>
/// The latitude in degrees
/// </summary>
public double Latitude;
}
}
}

21
LICENSE Normal file
View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2017 Justin Gielski
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

86
README.md Normal file
View File

@ -0,0 +1,86 @@
<p align="center"><img src="https://s8.postimg.cc/y7wuenuzp/LOGO_COORDINATE_SHARP.jpg"></p>
<h2 align="center">v1.1.5.2</h2>
CoordinateSharp is a simple .NET library that is designed to assist with geographic coordinate conversions, formatting and location based celestial calculations. This library has the ability to convert various lat long formats, UTM, MGRS(NATO UTM) and Cartesian (Spherical and ECEF X, Y, Z).
<b>CAUTION:</b> v1.1.5.1 begins the depracation of certain methods in preparation of CoordinateSharp v2.1.1.1. Many user mutable items, have also been made user immutable in v1.1.5.1. This only effects properties that do not need to be set by users (i.e. Celestial properties). You should see no negative effects from this conversion if the library has been used in accordance with the [CoordinateSharp Developer Guide](https://coordinatesharp.com/DeveloperGuide).
<b>ANNOUNCEMENT:</b> v2.1.1.1 development has begun. This version is going to focus on simplifying constructors, better management of circular references and property change notification. It is also going to focus on proper assignment of user mutable vs user immutable properties. Tests will be expanded. Lastly, code examples are going to be provided in the library documentation in order to give developers a more standard documentation experience outside of the Developer Guide. This version should give the user an even simpler experience when using CoordinateSharp.
Please review our upcoming [licensing change issue](https://github.com/Tronald/CoordinateSharp/issues/93).
Change notes can be viewed [here](https://www.coordinatesharp.com/ChangeNotes)
### Like CoordinateSharp? Tell us about it!
This library was built to help other developers. Please make the time and effort worth while by [telling us what you are using it for](https://github.com/Tronald/CoordinateSharp/issues/79).
### Prerequisites
.NET 4.0 or .NET Standard 2.0, 1.4, 1.3 compatible runtimes.
### Installing
CoordinateSharp is available as a nuget package from [nuget.org](https://www.nuget.org/packages/CoordinateSharp/)
Alternatively, you may download the library directly [on our website](https://www.coordinatesharp.com/Download)
### Usage Example
CoordinateSharp is simple to use. In the below example we create a `Coordinate` using one of the methods below.
```csharp
//Seattle coordinates on 5 Jun 2018 @ 10:10 AM (UTC)
//Signed-Decimal Degree 47.6062, -122.3321
//Degrees Minutes Seconds N 47º 36' 22.32" W 122º 19' 55.56"
/***********************************************************/
//Initialize with signed degree (standard method)
Coordinate c = new Coordinate(47.6062, -122.3321, new DateTime(2018,6,5,10,10,0));
/***IF OTHER FORMAT IS USED SUCH AS DEGREE MINUTES SECONDS***/
//Initialize with TryParse() Method
Coordinate.TryParse("N 47º 36' 22.32\" W 122º 19' 55.56\"", new DateTime(2018,6,5,10,10,0), out c);
/****OR****/
//Initialize with Secondary Method
Coordinate c = new Coordinate();
c.Latitude = new CoordinatePart(47,36, 22.32, CoordinatePosition.N, c);
c.Longitude = new CoordinatePart(122, 19, 55.56, CoordinatePosition.W, c);
c.GeoDate = new DateTime(2018,6,5,10,10,0);
```
Once the `Coordinate` is created we have access to various formats and celestial data. Here are just a few examples.
```C#
Console.WriteLine(c); // N 47º 36' 22.32" W 122º 19' 55.56"
Console.WriteLine(c.Latitude.Seconds); // 22.32
Console.WriteLine(c.UTM); // 10T 550200mE 5272748mN
Console.WriteLine(c.CelestialInfo.SunSet); // 5-Jun-2018 4:02:00 AM
Console.WriteLine(c.CelestialInfo.MoonAltitude); // 14.4169966277874
```
### Abilities
* **Lat/Long formatting:** Quickly format how a coordinate is output.
* **Coordinate conversions:** Convert Lat/Long to UTM, MGRS, Cartesian (Spherical and ECEF) or vice versa.
* **Coordinate parsing:** Initialize a `Coordinate` with multiple format types using `TryParse()`.
* **Coordinate moving/shifting:** Shift coordinates using a distance and bearing, or a distance and target coordinate.
* **Location based celestial information:** Quickly determine sun set, moon rise, next solar eclipse or even zodiac signs at the input location.
* **Property change notification:** All properties automatically adjust as the `Coordinate` changes. For example, changing the `GeoDate` will cause all celestial times to recalculate. Adjusting a `Coordinate` latitudinal seconds, will retrigger all coordinate conversions and celestial data so your information is always up to date.
* **Geo-Fencing:** Define a perimeter and determine if your coordinate is within or near polylines.
### Guides
Check out the [CoordinateSharp Developer Guide](https://www.coordinatesharp.com/DeveloperGuide) for more detailed instructions on the usage and abilities of CoordinateSharp.
You may also view the [Documentation](https://www.coordinatesharp.com/Help/index.html) for a more in depth look at CoordinateSharp's structure.
<p align="center"><img src="https://s8.postimg.cc/wvf5cfpqt/LOGO_COORDINATE_SHARP_1.jpg"></p>