/// <summary> /// Calculates an intermediate point at any fraction along the great circle path /// between two points with geographical coordinates /// </summary> /// <param name="p1">Geographical coordinates of the first point</param> /// <param name="p2">Geographical coordinates of the second point</param> /// <param name="fraction">Fraction along great circle route (f=0 is point 1, f=1 is point 2).</param> /// <returns> /// The intermediate point at specified fraction /// </returns> /// <remarks> /// Formula is taken from <see href="http://www.movable-type.co.uk/scripts/latlong.html"/> /// that is originally based on <see cref="http://www.edwilliams.org/avform.htm#Intermediate"/>. /// </remarks> public static CrdsGeographical Intermediate(CrdsGeographical p1, CrdsGeographical p2, double fraction) { if (fraction < 0 || fraction > 1) { throw new ArgumentException("Fraction value should be in range [0, 1]", nameof(fraction)); } double d = ToRadians(Separation(p1, p2)); double a, b; if (d <= 1e-6) { a = 1 - fraction; b = fraction; } else { a = Math.Sin((1 - fraction) * d) / Math.Sin(d); b = Math.Sin(fraction * d) / Math.Sin(d); } double alt1 = ToRadians(p1.Latitude); double alt2 = ToRadians(p2.Latitude); double az1 = ToRadians(p1.Longitude); double az2 = ToRadians(p2.Longitude); double x = a * Math.Cos(alt1) * Math.Cos(az1) + b * Math.Cos(alt2) * Math.Cos(az2); double y = a * Math.Cos(alt1) * Math.Sin(az1) + b * Math.Cos(alt2) * Math.Sin(az2); double z = a * Math.Sin(alt1) + b * Math.Sin(alt2); double lat = Math.Atan2(z, Math.Sqrt(x * x + y * y)); double lon = Math.Atan2(y, x); return(new CrdsGeographical(ToDegrees(lon), ToDegrees(lat))); }
/// <summary> /// Calculates distance in kilometers between 2 points on the Earth surface. /// </summary> /// <param name="g1">First point.</param> /// <param name="g2">Second point.</param> /// <returns>Distance in kilometers between 2 points.</returns> /// <remarks> /// The method is taken from https://www.movable-type.co.uk/scripts/latlong.html /// </remarks> public static double DistanceTo(this CrdsGeographical g1, CrdsGeographical g2) { const double R = 6371; double phi1 = Angle.ToRadians(g1.Latitude); double phi2 = Angle.ToRadians(g2.Latitude); double deltaPhi = Angle.ToRadians(g2.Latitude - g1.Latitude); double deltaLambda = Angle.ToRadians(g2.Longitude - g1.Longitude); double a = Math.Sin(deltaPhi / 2) * Math.Sin(deltaPhi / 2) + Math.Cos(phi1) * Math.Cos(phi2) * Math.Sin(deltaLambda / 2) * Math.Sin(deltaLambda / 2); double c = 2 * Math.Atan2(Math.Sqrt(a), Math.Sqrt(1 - a)); return(R * c); }
/// <summary> /// Calculates angular separation between two points with geographical coordinates /// </summary> /// <param name="p1">Geographical coordinates of the first point</param> /// <param name="p2">Geographical coordinates of the second point</param> /// <returns>Angular separation in degrees</returns> public static double Separation(CrdsGeographical p1, CrdsGeographical p2) { double a1 = ToRadians(p1.Latitude); double a2 = ToRadians(p2.Latitude); double A1 = To360(p1.Longitude); double A2 = To360(p2.Longitude); double a = Math.Acos( Math.Sin(a1) * Math.Sin(a2) + Math.Cos(a1) * Math.Cos(a2) * Math.Cos(ToRadians(A1 - A2))); return(double.IsNaN(a) ? 0 : ToDegrees(a)); }
/// <summary> /// Finds horizon circle point for a given geographical longitude /// for an instant of lunar eclipse, where Moon has zero altitude. /// </summary> /// <param name="e">Besselian elements of the Moon for the given instant.</param> /// <param name="L">Geographical longitude, positive west, negative east, from -180 to +180 degrees.</param> /// <returns> /// Geographical coordinates for the point. /// </returns> /// <remarks> /// The method core is based on formulae from the book: /// Seidelmann, P. K.: Explanatory Supplement to The Astronomical Almanac, /// University Science Book, Mill Valley (California), 1992, /// Chapter 8 "Eclipses of the Sun and Moon" /// https://archive.org/download/131123ExplanatorySupplementAstronomicalAlmanac/131123-explanatory-supplement-astronomical-almanac.pdf /// </remarks> private static CrdsGeographical Project(InstantLunarEclipseElements e, double L) { CrdsGeographical g = null; // Nutation elements var nutation = Nutation.NutationElements(e.JulianDay); // True obliquity var epsilon = Date.TrueObliquity(e.JulianDay, nutation.deltaEpsilon); // Greenwich apparent sidereal time double siderealTime = Date.ApparentSiderealTime(e.JulianDay, nutation.deltaPsi, epsilon); // Geocenric distance to the Moon, in km double dist = 358473400.0 / (e.F3 * 3600); // Horizontal parallax of the Moon double parallax = LunarEphem.Parallax(dist); // Equatorial coordinates of the Moon, initial value is geocentric CrdsEquatorial eq = new CrdsEquatorial(e.Alpha, e.Delta); // two iterations: // 1st: find geo location needed to perform topocentric correction // 2nd: correct sublunar point with topocentric position and find true geoposition for (int i = 0; i < 2; i++) { // sublunar point latitude, preserve sign! double phi0 = Sign(e.Delta) * Abs(eq.Delta); // sublunar point longitude (formula 8.426-1) double lambda0 = siderealTime - eq.Alpha; // sublunar point latitude (formula 8.426-2) double tanPhi = -1.0 / Tan(ToRadians(phi0)) * Cos(ToRadians(lambda0 - L)); double phi = ToDegrees(Atan(tanPhi)); g = new CrdsGeographical(L, phi); if (i == 0) { // correct to topocentric eq = eq.ToTopocentric(g, siderealTime, parallax); } } return(g); }
/// <summary> /// Converts local horizontal coordinates to equatorial coordinates. /// </summary> /// <param name="hor">Pair of local horizontal coordinates.</param> /// <param name="geo">Geographical of the observer</param> /// <param name="theta0">Local sidereal time.</param> /// <returns>Pair of equatorial coordinates</returns> public static CrdsEquatorial ToEquatorial(this CrdsHorizontal hor, CrdsGeographical geo, double theta0) { CrdsEquatorial eq = new CrdsEquatorial(); double A = Angle.ToRadians(hor.Azimuth); double h = Angle.ToRadians(hor.Altitude); double phi = Angle.ToRadians(geo.Latitude); double Y = Math.Sin(A); double X = Math.Cos(A) * Math.Sin(phi) + Math.Tan(h) * Math.Cos(phi); double H = Angle.ToDegrees(Math.Atan2(Y, X)); eq.Alpha = Angle.To360(theta0 - geo.Longitude - H); eq.Delta = Angle.ToDegrees(Math.Asin(Math.Sin(phi) * Math.Sin(h) - Math.Cos(phi) * Math.Cos(h) * Math.Cos(A))); return(eq); }
/// <summary> /// Converts equatorial coodinates to local horizontal /// </summary> /// <param name="eq">Pair of equatorial coodinates</param> /// <param name="geo">Geographical coordinates of the observer</param> /// <param name="theta0">Local sidereal time</param> /// <remarks> /// Implementation is taken from AA(I), formulae 12.5, 12.6. /// </remarks> public static CrdsHorizontal ToHorizontal(this CrdsEquatorial eq, CrdsGeographical geo, double theta0) { double H = Angle.ToRadians(HourAngle(theta0, geo.Longitude, eq.Alpha)); double phi = Angle.ToRadians(geo.Latitude); double delta = Angle.ToRadians(eq.Delta); CrdsHorizontal hor = new CrdsHorizontal(); double Y = Math.Sin(H); double X = Math.Cos(H) * Math.Sin(phi) - Math.Tan(delta) * Math.Cos(phi); hor.Altitude = Angle.ToDegrees(Math.Asin(Math.Sin(phi) * Math.Sin(delta) + Math.Cos(phi) * Math.Cos(delta) * Math.Cos(H))); hor.Azimuth = Angle.ToDegrees(Math.Atan2(Y, X)); hor.Azimuth = Angle.To360(hor.Azimuth); return(hor); }
/// <summary> /// Calculates topocentric equatorial coordinates of celestial body /// with taking into account correction for parallax. /// </summary> /// <param name="eq">Geocentric equatorial coordinates of the body</param> /// <param name="geo">Geographical coordinates of the body</param> /// <param name="theta0">Apparent sidereal time at Greenwich</param> /// <param name="pi">Parallax of a body</param> /// <returns>Topocentric equatorial coordinates of the celestial body</returns> /// <remarks> /// Method is taken from AA(II), formulae 40.6-40.7. /// </remarks> public static CrdsEquatorial ToTopocentric(this CrdsEquatorial eq, CrdsGeographical geo, double theta0, double pi) { double H = Angle.ToRadians(HourAngle(theta0, geo.Longitude, eq.Alpha)); double delta = Angle.ToRadians(eq.Delta); double sinPi = Math.Sin(Angle.ToRadians(pi)); double A = Math.Cos(delta) * Math.Sin(H); double B = Math.Cos(delta) * Math.Cos(H) - geo.RhoCosPhi * sinPi; double C = Math.Sin(delta) - geo.RhoSinPhi * sinPi; double q = Math.Sqrt(A * A + B * B + C * C); double H_ = Angle.ToDegrees(Math.Atan2(A, B)); double alpha_ = Angle.To360(theta0 - geo.Longitude - H_); double delta_ = Angle.ToDegrees(Math.Asin(C / q)); return(new CrdsEquatorial(alpha_, delta_)); }
private static LunarEclipseLocalCircumstancesContactPoint GetLocalCircumstancesContactPoint(double jd, LunarEclipse eclipse, PolynomialLunarEclipseElements e, CrdsGeographical g) { if (!double.IsNaN(jd)) { return(new LunarEclipseLocalCircumstancesContactPoint(e.GetInstantBesselianElements(jd), g)); } else { return(null); } }
public static LunarEclipseLocalCircumstances LocalCircumstances(LunarEclipse eclipse, PolynomialLunarEclipseElements e, CrdsGeographical g) { return(new LunarEclipseLocalCircumstances() { Location = g, PenumbralBegin = GetLocalCircumstancesContactPoint(eclipse.JulianDayFirstContactPenumbra, eclipse, e, g), PartialBegin = GetLocalCircumstancesContactPoint(eclipse.JulianDayFirstContactUmbra, eclipse, e, g), TotalBegin = GetLocalCircumstancesContactPoint(eclipse.JulianDayTotalBegin, eclipse, e, g), Maximum = GetLocalCircumstancesContactPoint(eclipse.JulianDayMaximum, eclipse, e, g), TotalEnd = GetLocalCircumstancesContactPoint(eclipse.JulianDayTotalEnd, eclipse, e, g), PartialEnd = GetLocalCircumstancesContactPoint(eclipse.JulianDayLastContactUmbra, eclipse, e, g), PenumbralEnd = GetLocalCircumstancesContactPoint(eclipse.JulianDayLastContactPenumbra, eclipse, e, g), }); }
/// <summary> /// Calculates instants of rising, transit and setting for non-stationary celestial body for the desired date. /// Non-stationary in this particular case means that body has fastly changing celestial coordinates during the day. /// </summary> /// <param name="eq">Array of three equatorial coordinates of the celestial body correspoding to local midnight, local noon, and local midnight of the following day after the desired date respectively.</param> /// <param name="location">Geographical location of the observation point.</param> /// <param name="theta0">Apparent sidereal time at Greenwich for local midnight of the desired date.</param> /// <param name="pi">Horizontal equatorial parallax of the body.</param> /// <param name="sd">Visible semidiameter of the body, expressed in degrees.</param> /// <returns>Instants of rising, transit and setting for the celestial body for the desired date.</returns> public static RTS RiseTransitSet(CrdsEquatorial[] eq, CrdsGeographical location, double theta0, double pi = 0, double sd = 0) { if (eq.Length != 3) { throw new ArgumentException("Number of equatorial coordinates in the array should be equal to 3."); } double[] alpha = new double[3]; double[] delta = new double[3]; for (int i = 0; i < 3; i++) { alpha[i] = eq[i].Alpha; delta[i] = eq[i].Delta; } Angle.Align(alpha); Angle.Align(delta); List <CrdsHorizontal> hor = new List <CrdsHorizontal>(); for (int i = 0; i <= 24; i++) { double n = i / 24.0; CrdsEquatorial eq0 = InterpolateEq(alpha, delta, n); var sidTime = InterpolateSiderialTime(theta0, n); hor.Add(eq0.ToTopocentric(location, sidTime, pi).ToHorizontal(location, sidTime)); } var result = new RTS(); for (int i = 0; i < 24; i++) { double n = (i + 0.5) / 24.0; CrdsEquatorial eq0 = InterpolateEq(alpha, delta, n); var sidTime = InterpolateSiderialTime(theta0, n); var hor0 = eq0.ToTopocentric(location, sidTime, pi).ToHorizontal(location, sidTime); if (double.IsNaN(result.Transit) && hor0.Altitude > 0) { double r = SolveParabola(Math.Sin(Angle.ToRadians(hor[i].Azimuth)), Math.Sin(Angle.ToRadians(hor0.Azimuth)), Math.Sin(Angle.ToRadians(hor[i + 1].Azimuth))); if (!double.IsNaN(r)) { double t = (i + r) / 24.0; eq0 = InterpolateEq(alpha, delta, t); sidTime = InterpolateSiderialTime(theta0, t); result.Transit = t; result.TransitAltitude = eq0.ToTopocentric(location, sidTime, pi).ToHorizontal(location, sidTime).Altitude; } } if (double.IsNaN(result.Rise) || double.IsNaN(result.Set)) { double r = SolveParabola(hor[i].Altitude + sd, hor0.Altitude + sd, hor[i + 1].Altitude + sd); if (!double.IsNaN(r)) { double t = (i + r) / 24.0; eq0 = InterpolateEq(alpha, delta, t); sidTime = InterpolateSiderialTime(theta0, t); if (double.IsNaN(result.Rise) && hor[i].Altitude + sd < 0 && hor[i + 1].Altitude + sd > 0) { result.Rise = t; result.RiseAzimuth = eq0.ToTopocentric(location, sidTime, pi).ToHorizontal(location, sidTime).Azimuth; } if (double.IsNaN(result.Set) && hor[i].Altitude + sd > 0 && hor[i + 1].Altitude + sd < 0) { result.Set = t; result.SetAzimuth = eq0.ToTopocentric(location, sidTime, pi).ToHorizontal(location, sidTime).Azimuth; } if (!double.IsNaN(result.Transit) && !double.IsNaN(result.Rise) && !double.IsNaN(result.Set)) { break; } } } } return(result); }
/// <summary> /// Calculates visibity details for the celestial body, /// </summary> /// <param name="eqBody">Mean equatorial coordinates of the body for the desired day.</param> /// <param name="eqSun">Mean equatorial coordinates of the Sun for the desired day.</param> /// <param name="minAltitude">Minimal altitude of the body, in degrees, to be considered as approproate for observations. By default it's 5 degrees for planet.</param> /// <returns><see cref="VisibilityDetails"/> instance describing details of visibility.</returns> // TODO: tests public static VisibilityDetails Details(CrdsEquatorial eqBody, CrdsEquatorial eqSun, CrdsGeographical location, double theta0, double minAltitude = 5) { var details = new VisibilityDetails(); // period when the planet is above the horizon and its altitude is larger than "minAltitude" RTS body = RiseTransitSet(eqBody, location, theta0, minAltitude); // period when the Sun is above the horizon RTS sun = RiseTransitSet(eqSun, location, theta0); // body reaches minimal altitude but Sun does not rise at all (polar night) if (body.TransitAltitude > minAltitude && sun.TransitAltitude <= 0) { details.Period = VisibilityPeriod.WholeNight; details.Duration = body.Duration * 24; } // body does not reach the minimal altitude during the day else if (body.TransitAltitude <= minAltitude) { details.Period = VisibilityPeriod.Invisible; details.Duration = 0; } // there is a day/night change during the day and body reaches minimal altitude else if (body.TransitAltitude > minAltitude) { // "Sun is below horizon" time range, expressed in degrees (0 is midnight, 180 is noon) var r1 = new AngleRange(sun.Set * 360, (1 - sun.Duration) * 360); // "body is above horizon" time range, expressed in degrees (0 is midnight, 180 is noon) var r2 = new AngleRange(body.Rise * 360, body.Duration * 360); // find the intersections of two ranges var ranges = r1.Overlaps(r2); // no intersections of time ranges if (!ranges.Any()) { details.Period = VisibilityPeriod.Invisible; details.Duration = 0; details.Begin = double.NaN; details.End = double.NaN; } // the body is observable during the day else { // duration of visibility details.Duration = ranges.Sum(i => i.Range / 360 * 24); // beginning of visibility details.Begin = ranges.First().Start / 360; // end of visibility details.End = (details.Begin + details.Duration / 24) % 1; // Evening time range, expressed in degrees // Start is a sunset time, range is a timespan from sunset to midnight. var rE = new AngleRange(sun.Set * 360, (1 - sun.Set) * 360); // Night time range, expressed in degrees // Start is a midnight time, range is a half of timespan from midnight to sunrise var rN = new AngleRange(0, sun.Rise / 2 * 360); // Morning time range, expressed in degrees // Start is a half of time from midnight to sunrise, range is a time to sunrise var rM = new AngleRange(sun.Rise / 2 * 360, sun.Rise / 2 * 360); foreach (var r in ranges) { var isEvening = r.Overlaps(rE); if (isEvening.Any()) { details.Period |= VisibilityPeriod.Evening; } var isNight = r.Overlaps(rN); if (isNight.Any()) { details.Period |= VisibilityPeriod.Night; } var isMorning = r.Overlaps(rM); if (isMorning.Any()) { details.Period |= VisibilityPeriod.Morning; } } } } return(details); }
/// <summary> /// Calculates instants of rising, transit and setting for stationary celestial body for the desired date. /// Stationary in this particular case means that body has unchanged (or slightly changing) celestial coordinates during the day. /// </summary> /// <param name="eq">Equatorial coordinates of the celestial body.</param> /// <param name="location">Geographical location of the observation point.</param> /// <param name="theta0">Apparent sidereal time at Greenwich for local midnight of the desired date.</param> /// <param name="minAltitude">Minimal altitude of the body above the horizon, in degrees, to detect rise/set. Used only for calculating visibility conditions.</param> /// <returns>Instants of rising, transit and setting for the celestial body for the desired date.</returns> public static RTS RiseTransitSet(CrdsEquatorial eq, CrdsGeographical location, double theta0, double minAltitude = 0) { List <CrdsHorizontal> hor = new List <CrdsHorizontal>(); for (int i = 0; i <= 24; i++) { double n = i / 24.0; var sidTime = InterpolateSiderialTime(theta0, n); hor.Add(eq.ToHorizontal(location, sidTime)); } var result = new RTS(); for (int i = 0; i < 24; i++) { double n = (i + 0.5) / 24.0; var sidTime = InterpolateSiderialTime(theta0, n); var hor0 = eq.ToHorizontal(location, sidTime); if (double.IsNaN(result.Transit) && hor0.Altitude > 0) { double r = SolveParabola(Math.Sin(Angle.ToRadians(hor[i].Azimuth)), Math.Sin(Angle.ToRadians(hor0.Azimuth)), Math.Sin(Angle.ToRadians(hor[i + 1].Azimuth))); if (!double.IsNaN(r)) { double t = (i + r) / 24.0; sidTime = InterpolateSiderialTime(theta0, t); result.Transit = t; result.TransitAltitude = eq.ToHorizontal(location, sidTime).Altitude; } } if (double.IsNaN(result.Rise) || double.IsNaN(result.Set)) { double r = SolveParabola(hor[i].Altitude - minAltitude, hor0.Altitude - minAltitude, hor[i + 1].Altitude - minAltitude); if (!double.IsNaN(r)) { double t = (i + r) / 24.0; sidTime = InterpolateSiderialTime(theta0, t); if (double.IsNaN(result.Rise) && hor[i].Altitude - minAltitude < 0 && hor[i + 1].Altitude - minAltitude > 0) { result.Rise = t; result.RiseAzimuth = eq.ToHorizontal(location, sidTime).Azimuth; } if (double.IsNaN(result.Set) && hor[i].Altitude - minAltitude > 0 && hor[i + 1].Altitude - minAltitude < 0) { result.Set = t; result.SetAzimuth = eq.ToHorizontal(location, sidTime).Azimuth; } if (!double.IsNaN(result.Transit) && !double.IsNaN(result.Rise) && !double.IsNaN(result.Set)) { break; } } } } return(result); }
/// <summary> /// Creates new instance /// </summary> public LunarEclipseLocalCircumstancesContactPoint(InstantLunarEclipseElements e, CrdsGeographical g) { CrdsEquatorial eq = new CrdsEquatorial(e.Alpha, e.Delta); // Nutation elements var nutation = Nutation.NutationElements(e.JulianDay); // True obliquity var epsilon = Date.TrueObliquity(e.JulianDay, nutation.deltaEpsilon); // Greenwich apparent sidereal time double siderealTime = Date.ApparentSiderealTime(e.JulianDay, nutation.deltaPsi, epsilon); // Geocenric distance to the Moon, in km double dist = 358473400.0 / (e.F3 * 3600); // Horizontal parallax of the Moon double parallax = LunarEphem.Parallax(dist); // Topocentrical equatorial coordinates var eqTopo = eq.ToTopocentric(g, siderealTime, parallax); // Horizontal coordinates var h = eqTopo.ToHorizontal(g, siderealTime); // Hour angle double H = Coordinates.HourAngle(siderealTime, g.Longitude, eqTopo.Alpha); // Position angles // Parallactic angle - AA(II), p.98, formula 14.1 double qAngle = ToDegrees(Atan2(Sin(ToRadians(H)), Tan(ToRadians(g.Latitude)) * Cos(ToRadians(eqTopo.Delta)) - Sin(ToRadians(eqTopo.Delta)) * Cos(ToRadians(H)))); // Position angle, measured from North to East (CCW) double pAngle = To360(ToDegrees(Atan2(e.X, e.Y))); // Position angle, measured from Zenith CCW double zAngle = To360(pAngle - qAngle); JulianDay = e.JulianDay; LunarAltitude = h.Altitude; QAngle = qAngle; PAngle = pAngle; ZAngle = zAngle; X = e.X; Y = e.Y; F1 = e.F1; F2 = e.F2; F3 = e.F3; }
public CrdsGeographical(CrdsGeographical other) : this(other.Longitude, other.Latitude, other.UtcOffset, other.Elevation, other.TimeZoneId, other.LocationName) { }
/// <summary> /// Creates new point with Julian Day value and coordinates /// </summary> /// <param name="jd">Julian Day value</param> /// <param name="c">Coordinates of the point</param> public SolarEclipsePoint(double jd, CrdsGeographical c) { JulianDay = jd; Coordinates = c; }