using System; using System.Collections.Generic; namespace CoordinateSharp { /// /// The main class for handling location based celestial information. /// /// /// This class can calculate various pieces of solar and lunar data, based on location and date /// [Serializable] public class Celestial { //When a rise or a set does not occur, the DateTime will return null /// /// Creates an empty Celestial. /// 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)); } } /// /// Creates a Celestial based on a location and specified date /// /// latitude /// longitude /// DateTime (UTC) 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); } /// /// Creates a Celestial based on a location and date in the provided Coordinate. /// /// Coordinate /// Celestial 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; } /// /// Converts Celestial values to local times. /// /// Coordinate /// UTC offset /// 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]; } /// /// In place time slip /// /// Coordinate /// hour offset 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; /// /// Sunset time. /// public DateTime? SunSet => this.sunSet; /// /// Sunrise time. /// public DateTime? SunRise => this.sunRise; /// /// Moonset time. /// public DateTime? MoonSet => this.moonSet; /// /// Moonrise time. /// public DateTime? MoonRise => this.moonRise; /// /// Sun altitude in degrees (E of N). /// public Double SunAltitude => this.sunAltitude; /// /// Sun azimuth in degrees (E of N). /// public Double SunAzimuth => this.sunAzimuth; /// /// Moon altitude in degrees (corrected for parallax and refraction). /// public Double MoonAltitude => this.moonAltitude; /// /// Moon azimuth in degrees (E of N). /// public Double MoonAzimuth => this.moonAzimuth; /// /// Estimated moon distance from the earth. /// public Distance MoonDistance => this.moonDistance; /// /// Sun's Condition based on the provided date. /// public CelestialStatus SunCondition => this.sunCondition; /// /// Moon's condition based on the provided date. /// public CelestialStatus MoonCondition => this.moonCondition; /// /// Determine if the sun is currently up, based on sunset and sunrise time at the provided location and date. /// public Boolean IsSunUp => this.isSunUp; /// /// Determine if the moon is currently up, based on moonset and moonrise time at the provided location and date. /// public Boolean IsMoonUp => this.isMoonUp; /// /// Moon ilumination details based on the provided date. /// /// /// Contains phase, phase name, fraction and angle /// public MoonIllum MoonIllum => this.moonIllum; /// /// Moons perigee details based on the provided date. /// public Perigee Perigee => this.perigee; /// /// Moons apogee details based on the provided date. /// public Apogee Apogee => this.apogee; /// /// Additional solar event times based on the provided date and location. /// /// Contains civil and nautical dawn and dusk times. public AdditionalSolarTimes AdditionalSolarTimes => this.additionalSolarTimes; /// /// Astrological signs based on the provided date. /// /// /// Contains zodiac, moon sign and moon name during full moon events /// public AstrologicalSigns AstrologicalSigns => this.astrologicalSigns; /// /// Returns a SolarEclipse. /// public SolarEclipse SolarEclipse => this.solarEclipse; /// /// Returns a LunarEclipse. /// public LunarEclipse LunarEclipse => this.lunarEclipse; /// /// Calculates all celestial data. Coordinates will notify as changes occur /// /// Decimal format latitude /// Decimal format longitude /// Geographic DateTime 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); } /// /// Calculate celestial data based on lat/long and date. /// /// Decimal format latitude /// Decimal format longitude /// Geographic DateTime /// Fully populated Celestial object 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; } /// /// Calculate sun data based on lat/long and date. /// /// latitude /// longitude /// DateTime /// Celestial (Partially Populated) 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; } /// /// Calculate moon data based on lat/long and date. /// /// latitude /// longitude /// DateTime /// Celestial (Partially Populated) 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; } /// /// Returns a List containing solar eclipse data for the century. /// Century return is based on the date passed. /// /// latitude /// longitude /// DateTime /// public static List 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); } /// /// Returns a List containing solar eclipse data for the century. /// Century return is based on the date passed. /// /// latitude /// longitude /// DateTime /// public static List 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); } /// /// Set bool SunIsUp and MoonIsUp values /// /// Coordinate GeoDate /// Celestial Object 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; } } /// /// Returns Apogee object containing last and next apogee based on the specified date. /// /// DateTime /// Apogee public static Apogee GetApogees(DateTime d) => MoonCalc.GetApogeeEvents(d); /// /// Returns Perigee object containing last and next perigee based on the specified date. /// /// DateTime /// Perigee public static Perigee GetPerigees(DateTime d) => MoonCalc.GetPerigeeEvents(d); } }