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

521 lines
20 KiB
C#

using System;
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() {
this.astrologicalSigns = new AstrologicalSigns();
this.lunarEclipse = new LunarEclipse();
this.solarEclipse = new SolarEclipse();
this.CalculateCelestialTime(0, 0, new DateTime(1900, 1, 1, 0, 0, 0, DateTimeKind.Utc));
}
private Celestial(Boolean hasCalcs) {
this.astrologicalSigns = new AstrologicalSigns();
this.lunarEclipse = new LunarEclipse();
this.solarEclipse = new SolarEclipse();
if (hasCalcs) { this.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);
this.astrologicalSigns = new AstrologicalSigns();
this.lunarEclipse = new LunarEclipse();
this.solarEclipse = new SolarEclipse();
this.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
Int32 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 (Int32 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 (this.MoonSet.HasValue) { this.moonSet = this.moonSet.Value.AddHours(offset); }
if (this.MoonRise.HasValue) { this.moonRise = this.moonRise.Value.AddHours(offset); }
//Perigee
this.Perigee.ConvertTo_Local_Time(offset);
//Apogee
this.Apogee.ConvertTo_Local_Time(offset);
//Eclipse
this.LunarEclipse.ConvertTo_LocalTime(offset);
////Solar
if (this.sunSet.HasValue) { this.sunSet = this.sunSet.Value.AddHours(offset); }
if (this.SunRise.HasValue) { this.sunRise = this.SunRise.Value.AddHours(offset); }
this.AdditionalSolarTimes.Convert_To_Local_Time(offset);
//Eclipse
this.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, Int32 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, Int32 i) => i switch
{
0 => pre,
1 => actual,
2 => post,
_ => null,
};
private static Int32 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 Boolean isSunUp;
internal Boolean 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 => this.sunSet;
/// <summary>
/// Sunrise time.
/// </summary>
public DateTime? SunRise => this.sunRise;
/// <summary>
/// Moonset time.
/// </summary>
public DateTime? MoonSet => this.moonSet;
/// <summary>
/// Moonrise time.
/// </summary>
public DateTime? MoonRise => this.moonRise;
/// <summary>
/// Sun altitude in degrees (E of N).
/// </summary>
public Double SunAltitude => this.sunAltitude;
/// <summary>
/// Sun azimuth in degrees (E of N).
/// </summary>
public Double SunAzimuth => this.sunAzimuth;
/// <summary>
/// Moon altitude in degrees (corrected for parallax and refraction).
/// </summary>
public Double MoonAltitude => this.moonAltitude;
/// <summary>
/// Moon azimuth in degrees (E of N).
/// </summary>
public Double MoonAzimuth => this.moonAzimuth;
/// <summary>
/// Estimated moon distance from the earth.
/// </summary>
public Distance MoonDistance => this.moonDistance;
/// <summary>
/// Sun's Condition based on the provided date.
/// </summary>
public CelestialStatus SunCondition => this.sunCondition;
/// <summary>
/// Moon's condition based on the provided date.
/// </summary>
public CelestialStatus MoonCondition => this.moonCondition;
/// <summary>
/// Determine if the sun is currently up, based on sunset and sunrise time at the provided location and date.
/// </summary>
public Boolean IsSunUp => this.isSunUp;
/// <summary>
/// Determine if the moon is currently up, based on moonset and moonrise time at the provided location and date.
/// </summary>
public Boolean IsMoonUp => this.isMoonUp;
/// <summary>
/// Moon ilumination details based on the provided date.
/// </summary>
/// <remarks>
/// Contains phase, phase name, fraction and angle
/// </remarks>
public MoonIllum MoonIllum => this.moonIllum;
/// <summary>
/// Moons perigee details based on the provided date.
/// </summary>
public Perigee Perigee => this.perigee;
/// <summary>
/// Moons apogee details based on the provided date.
/// </summary>
public Apogee Apogee => this.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 => this.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 => this.astrologicalSigns;
/// <summary>
/// Returns a SolarEclipse.
/// </summary>
public SolarEclipse SolarEclipse => this.solarEclipse;
/// <summary>
/// Returns a LunarEclipse.
/// </summary>
public LunarEclipse LunarEclipse => this.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);
this.perigee = MoonCalc.GetPerigeeEvents(date);
this.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:
cel.isSunUp = date < cel.SunSet;
break;
case CelestialStatus.NoSet:
cel.isSunUp = date > cel.SunRise;
break;
case CelestialStatus.RiseAndSet:
cel.isSunUp = cel.SunRise < cel.SunSet ? date > cel.SunRise && date < cel.SunSet : date > cel.SunRise || date < cel.SunSet;
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:
cel.isMoonUp = date < cel.MoonSet;
break;
case CelestialStatus.NoSet:
cel.isMoonUp = date > cel.MoonRise;
break;
case CelestialStatus.RiseAndSet:
cel.isMoonUp = cel.MoonRise < cel.MoonSet ? date > cel.MoonRise && date < cel.MoonSet : date > cel.MoonRise || date < cel.MoonSet;
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) => 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) => MoonCalc.GetPerigeeEvents(d);
}
}