From ef121216212af8dc19ceddb38f9ffc65dbee7e2e Mon Sep 17 00:00:00 2001 From: Don Cross Date: Sat, 23 May 2020 21:29:16 -0400 Subject: [PATCH] Starting to implement C version of local solar eclipse. Defined data structure astro_local_solar_eclipse_t. Created stubs for functions to find local solar eclipses. Renamed lunar eclipse 'center' to 'peak' to be consistent. --- demo/c/lunar_eclipse.c | 14 +-- generate/ctest.c | 14 +-- generate/template/astronomy.c | 181 +++++++++++++++++++++++++++++++--- source/c/README.md | 94 +++++++++++++++++- source/c/astronomy.c | 181 +++++++++++++++++++++++++++++++--- source/c/astronomy.h | 53 +++++++++- 6 files changed, 484 insertions(+), 53 deletions(-) diff --git a/demo/c/lunar_eclipse.c b/demo/c/lunar_eclipse.c index dd676923..e8ec31d0 100644 --- a/demo/c/lunar_eclipse.c +++ b/demo/c/lunar_eclipse.c @@ -16,31 +16,31 @@ void PrintEclipse(astro_lunar_eclipse_t eclipse) { /* Calculate beginning/ending of different phases - of an eclipse by subtracting/adding the center time + of an eclipse by subtracting/adding the peak time with the number of minutes indicated by the "semi-duration" fields sd_partial and sd_total. */ const double MINUTES_PER_DAY = 24 * 60; - PrintTime(Astronomy_AddDays(eclipse.center, -eclipse.sd_partial / MINUTES_PER_DAY)); + PrintTime(Astronomy_AddDays(eclipse.peak, -eclipse.sd_partial / MINUTES_PER_DAY)); printf(" - Partial eclipse begins.\n"); if (eclipse.sd_total > 0.0) { - PrintTime(Astronomy_AddDays(eclipse.center, -eclipse.sd_total / MINUTES_PER_DAY)); + PrintTime(Astronomy_AddDays(eclipse.peak, -eclipse.sd_total / MINUTES_PER_DAY)); printf(" - Total eclipse begins.\n"); } - PrintTime(eclipse.center); + PrintTime(eclipse.peak); printf(" - Peak of %s eclipse.\n", (eclipse.kind == ECLIPSE_TOTAL) ? "total" : "partial"); if (eclipse.sd_total > 0.0) { - PrintTime(Astronomy_AddDays(eclipse.center, +eclipse.sd_total / MINUTES_PER_DAY)); + PrintTime(Astronomy_AddDays(eclipse.peak, +eclipse.sd_total / MINUTES_PER_DAY)); printf(" - Total eclipse ends.\n"); } - PrintTime(Astronomy_AddDays(eclipse.center, +eclipse.sd_partial / MINUTES_PER_DAY)); + PrintTime(Astronomy_AddDays(eclipse.peak, +eclipse.sd_partial / MINUTES_PER_DAY)); printf(" - Partial eclipse ends.\n\n"); } @@ -82,6 +82,6 @@ int main(int argc, const char *argv[]) if (++count == 10) return 0; } - eclipse = Astronomy_NextLunarEclipse(eclipse.center); + eclipse = Astronomy_NextLunarEclipse(eclipse.peak); } } diff --git a/generate/ctest.c b/generate/ctest.c index e0df47c9..3df7bcb9 100644 --- a/generate/ctest.c +++ b/generate/ctest.c @@ -2648,9 +2648,9 @@ static int LunarEclipseTest(void) filename, lnum, eclipse.kind, eclipse.sd_penum, eclipse.sd_partial, eclipse.sd_total); } - /* check eclipse center */ + /* check eclipse peak time */ - diff_days = eclipse.center.ut - peak_time.ut; + diff_days = eclipse.peak.ut - peak_time.ut; /* tolerate missing penumbral eclipses - skip to next input line without calculating next eclipse. */ if (partial_minutes == 0.0 && diff_days > 20.0) { @@ -2671,13 +2671,13 @@ static int LunarEclipseTest(void) if (diff_minutes > diff_limit) { - printf("C LunarEclipseTest expected center: "); + printf("C LunarEclipseTest expected peak: "); PrintTime(peak_time); printf("\n"); - printf("C LunarEclipseTest found center: "); - PrintTime(eclipse.center); + printf("C LunarEclipseTest found peak: "); + PrintTime(eclipse.peak); printf("\n"); - FAIL("C LunarEclipseTest(%s line %d): EXCESSIVE center time error = %lf minutes (%lf days).\n", filename, lnum, diff_minutes, diff_days); + FAIL("C LunarEclipseTest(%s line %d): EXCESSIVE peak time error = %lf minutes (%lf days).\n", filename, lnum, diff_minutes, diff_days); } if (diff_minutes > max_diff_minutes) @@ -2709,7 +2709,7 @@ static int LunarEclipseTest(void) /* calculate for next iteration */ - eclipse = Astronomy_NextLunarEclipse(eclipse.center); + eclipse = Astronomy_NextLunarEclipse(eclipse.peak); if (eclipse.status != ASTRO_SUCCESS) FAIL("C LunarEclipseTest(%s line %d): Astronomy_NextLunarEclipse returned status %d\n", filename, lnum, eclipse.status); } diff --git a/generate/template/astronomy.c b/generate/template/astronomy.c index cb80ad7f..00fc63dd 100644 --- a/generate/template/astronomy.c +++ b/generate/template/astronomy.c @@ -5485,7 +5485,7 @@ static astro_lunar_eclipse_t LunarEclipseError(astro_status_t status) astro_lunar_eclipse_t eclipse; eclipse.status = status; eclipse.kind = ECLIPSE_NONE; - eclipse.center = TimeError(); + eclipse.peak = TimeError(); eclipse.sd_penum = eclipse.sd_partial = eclipse.sd_total = NAN; return eclipse; } @@ -5736,11 +5736,7 @@ astro_lunar_eclipse_t Astronomy_SearchLunarEclipse(astro_time_t startTime) if (fullmoon.status != ASTRO_SUCCESS) return LunarEclipseError(fullmoon.status); - /* - Pruning: if the full Moon's ecliptic latitude is too large, - a lunar eclipse is not possible. Avoid needless work searching for - the minimum moon distance. - */ + /* Pruning: if the full Moon's ecliptic latitude is too large, a lunar eclipse is not possible. */ CalcMoon(fullmoon.time.tt / 36525.0, &eclip_lon, &eclip_lat, &distance); if (RAD2DEG * fabs(eclip_lat) < PruneLatitude) { @@ -5755,7 +5751,7 @@ astro_lunar_eclipse_t Astronomy_SearchLunarEclipse(astro_time_t startTime) /* This is at least a penumbral eclipse. We will return a result. */ eclipse.status = ASTRO_SUCCESS; eclipse.kind = ECLIPSE_PENUMBRAL; - eclipse.center = shadow.time; + eclipse.peak = shadow.time; eclipse.sd_total = 0.0; eclipse.sd_partial = 0.0; eclipse.sd_penum = ShadowSemiDurationMinutes(shadow.time, shadow.p + MOON_MEAN_RADIUS_KM, 200.0); @@ -5797,7 +5793,7 @@ astro_lunar_eclipse_t Astronomy_SearchLunarEclipse(astro_time_t startTime) * * After using #Astronomy_SearchLunarEclipse to find the first lunar eclipse * in a series, you can call this function to find the next consecutive lunar eclipse. - * Pass in the `center` value from the #astro_lunar_eclipse_t returned by the + * Pass in the `peak` value from the #astro_lunar_eclipse_t returned by the * previous call to `Astronomy_SearchLunarEclipse` or `Astronomy_NextLunarEclipse` * to find the next lunar eclipse. * @@ -5954,7 +5950,7 @@ static astro_global_solar_eclipse_t GeoidIntersect(shadow_t shadow) /** - * @brief Searches for a solar eclipse. + * @brief Searches for a solar eclipse visible anywhere on the Earth's surface. * * This function finds the first solar eclipse that occurs after `startTime`. * A solar eclipse found may be partial, annular, or total. @@ -5989,15 +5985,11 @@ astro_global_solar_eclipse_t Astronomy_SearchGlobalSolarEclipse(astro_time_t sta if (newmoon.status != ASTRO_SUCCESS) return GlobalSolarEclipseError(newmoon.status); - /* - Pruning: if the full Moon's ecliptic latitude is too large, - a lunar eclipse is not possible. Avoid needless work searching for - the minimum moon distance. - */ + /* Pruning: if the new moon's ecliptic latitude is too large, a solar eclipse is not possible. */ CalcMoon(newmoon.time.tt / 36525.0, &eclip_lon, &eclip_lat, &distance); if (RAD2DEG * fabs(eclip_lat) < PruneLatitude) { - /* Search near the full moon for the time when the center of the Earth */ + /* Search near the new moon for the time when the center of the Earth */ /* is closest to the line passing through the centers of the Sun and Moon. */ shadow = PeakMoonShadow(newmoon.time); if (shadow.status != ASTRO_SUCCESS) @@ -6022,7 +6014,7 @@ astro_global_solar_eclipse_t Astronomy_SearchGlobalSolarEclipse(astro_time_t sta /** - * @brief Searches for the next solar eclipse in a series. + * @brief Searches for the next global solar eclipse in a series. * * After using #Astronomy_SearchGlobalSolarEclipse to find the first solar eclipse * in a series, you can call this function to find the next consecutive solar eclipse. @@ -6044,6 +6036,163 @@ astro_global_solar_eclipse_t Astronomy_NextGlobalSolarEclipse(astro_time_t prevE return Astronomy_SearchGlobalSolarEclipse(startTime); } + +static astro_local_solar_eclipse_t LocalSolarEclipseError(astro_status_t status) +{ + astro_local_solar_eclipse_t eclipse; + + eclipse.status = status; + eclipse.kind = ECLIPSE_NONE; + eclipse.peak = eclipse.sunrise = eclipse.sunset = + eclipse.partial_begin = eclipse.total_begin = + eclipse.total_end = eclipse.partial_end = + TimeError(); + + return eclipse; +} + + +static shadow_t PeakLocalMoonShadow(astro_time_t search_center_time, astro_observer_t observer) +{ + /* FIXFIXFIX - not yet implemented. */ + (void)search_center_time; + (void)observer; + return ShadowError(ASTRO_NOT_INITIALIZED); +} + + +static astro_local_solar_eclipse_t LocalEclipse( + shadow_t shadow, + astro_observer_t observer) +{ + /* FIXFIXFIX - not yet implemented. */ + (void)shadow; + (void)observer; + return LocalSolarEclipseError(ASTRO_NOT_INITIALIZED); +} + + +static int DaytimeOverlap( + astro_local_solar_eclipse_t *eclipse, + astro_observer_t observer) +{ + /* FIXFIXFIX - not yet implemented. */ + (void)eclipse; + (void)observer; + return 0; +} + + + +/** + * @brief Searches for a solar eclipse visible at a specific location on the Earth's surface. + * + * This function finds the first solar eclipse that occurs after `startTime`. + * A solar eclipse found may be partial, annular, or total. + * See #astro_local_solar_eclipse_t for more information. + * To find a series of solar eclipses, call this function once, + * then keep calling #Astronomy_NextLocalSolarEclipse as many times as desired, + * passing in the `peak` value returned from the previous call. + * + * IMPORTANT: An eclipse reported by this function might be partly or + * completely invisible to the observer due to the time of day. + * + * @param startTime + * The date and time for starting the search for a solar eclipse. + * + * @param observer + * The geographic location of the observer. + * + * @return + * If successful, the `status` field in the returned structure will contain `ASTRO_SUCCESS` + * and the remaining structure fields are as described in #astro_local_solar_eclipse_t. + * Any other value indicates an error. + */ +astro_local_solar_eclipse_t Astronomy_SearchLocalSolarEclipse( + astro_time_t startTime, + astro_observer_t observer) +{ + const double PruneLatitude = 1.8; /* Moon's ecliptic latitude beyond which eclipse is impossible */ + astro_time_t nmtime; + astro_search_result_t newmoon; + shadow_t shadow; + int nmcount; + double eclip_lat, eclip_lon, distance; + astro_local_solar_eclipse_t eclipse; + + /* Iterate through consecutive new moons until we find a solar eclipse visible somewhere on Earth. */ + nmtime = startTime; + for (nmcount=0; nmcount < 12; ++nmcount) + { + /* Search for the next new moon. Any eclipse will be near it. */ + newmoon = Astronomy_SearchMoonPhase(0.0, nmtime, 40.0); + if (newmoon.status != ASTRO_SUCCESS) + return LocalSolarEclipseError(newmoon.status); + + /* Pruning: if the new moon's ecliptic latitude is too large, a solar eclipse is not possible. */ + CalcMoon(newmoon.time.tt / 36525.0, &eclip_lon, &eclip_lat, &distance); + if (RAD2DEG * fabs(eclip_lat) < PruneLatitude) + { + /* Search near the new moon for the time when the observer */ + /* is closest to the line passing through the centers of the Sun and Moon. */ + shadow = PeakLocalMoonShadow(newmoon.time, observer); + if (shadow.status != ASTRO_SUCCESS) + return LocalSolarEclipseError(shadow.status); + + if (shadow.r < shadow.p) + { + /* This is at least a partial solar eclipse for the observer. */ + eclipse = LocalEclipse(shadow, observer); + if (eclipse.status != ASTRO_SUCCESS) + return eclipse; + + /* Ignore any eclipse that happens completely at night. */ + /* Find sunrise/sunset pairs until either some part of the eclipse */ + /* overlaps with daytime hours, or we prove that the eclipse is entirely at night. */ + if (DaytimeOverlap(&eclipse, observer)) + return eclipse; + } + } + + /* We didn't find an eclipse on this new moon, so search for the next one. */ + nmtime = Astronomy_AddDays(newmoon.time, 10.0); + } + + /* Safety valve to prevent infinite loop. */ + /* This should never happen, because at least 2 solar eclipses happen per year. */ + return LocalSolarEclipseError(ASTRO_INTERNAL_ERROR); +} + + +/** + * @brief Searches for the next local solar eclipse in a series. + * + * After using #Astronomy_SearchLocalSolarEclipse to find the first solar eclipse + * in a series, you can call this function to find the next consecutive solar eclipse. + * Pass in the `center` value from the #astro_local_solar_eclipse_t returned by the + * previous call to `Astronomy_SearchLocalSolarEclipse` or `Astronomy_NextLocalSolarEclipse` + * to find the next solar eclipse. + * + * @param prevEclipseTime + * A date and time near a new moon. Solar eclipse search will start at the next new moon. + * + * @param observer + * The geographic location of the observer. + * + * @return + * If successful, the `status` field in the returned structure will contain `ASTRO_SUCCESS` + * and the remaining structure fields are as described in #astro_local_solar_eclipse_t. + * Any other value indicates an error. + */ +astro_local_solar_eclipse_t Astronomy_NextLocalSolarEclipse( + astro_time_t prevEclipseTime, + astro_observer_t observer) +{ + astro_time_t startTime = Astronomy_AddDays(prevEclipseTime, 10.0); + return Astronomy_SearchLocalSolarEclipse(startTime, observer); +} + + #ifdef __cplusplus } #endif diff --git a/source/c/README.md b/source/c/README.md index b689531f..6b49eb8a 100644 --- a/source/c/README.md +++ b/source/c/README.md @@ -886,7 +886,7 @@ This function determines the phase of the Moon using its apparent ecliptic longi ### Astronomy_NextGlobalSolarEclipse(prevEclipseTime) ⇒ [`astro_global_solar_eclipse_t`](#astro_global_solar_eclipse_t) -**Searches for the next solar eclipse in a series.** +**Searches for the next global solar eclipse in a series.** @@ -905,6 +905,31 @@ After using [`Astronomy_SearchGlobalSolarEclipse`](#Astronomy_SearchGlobalSolarE +--- + + +### Astronomy_NextLocalSolarEclipse(prevEclipseTime, observer) ⇒ [`astro_local_solar_eclipse_t`](#astro_local_solar_eclipse_t) + +**Searches for the next local solar eclipse in a series.** + + + +After using [`Astronomy_SearchLocalSolarEclipse`](#Astronomy_SearchLocalSolarEclipse) to find the first solar eclipse in a series, you can call this function to find the next consecutive solar eclipse. Pass in the `center` value from the [`astro_local_solar_eclipse_t`](#astro_local_solar_eclipse_t) returned by the previous call to `Astronomy_SearchLocalSolarEclipse` or `Astronomy_NextLocalSolarEclipse` to find the next solar eclipse. + + + +**Returns:** If successful, the `status` field in the returned structure will contain `ASTRO_SUCCESS` and the remaining structure fields are as described in [`astro_local_solar_eclipse_t`](#astro_local_solar_eclipse_t). Any other value indicates an error. + + + +| Type | Parameter | Description | +| --- | --- | --- | +| [`astro_time_t`](#astro_time_t) | `prevEclipseTime` | A date and time near a new moon. Solar eclipse search will start at the next new moon. | +| [`astro_observer_t`](#astro_observer_t) | `observer` | The geographic location of the observer. | + + + + --- @@ -940,7 +965,7 @@ See [`Astronomy_SearchLunarApsis`](#Astronomy_SearchLunarApsis) for more details -After using [`Astronomy_SearchLunarEclipse`](#Astronomy_SearchLunarEclipse) to find the first lunar eclipse in a series, you can call this function to find the next consecutive lunar eclipse. Pass in the `center` value from the [`astro_lunar_eclipse_t`](#astro_lunar_eclipse_t) returned by the previous call to `Astronomy_SearchLunarEclipse` or `Astronomy_NextLunarEclipse` to find the next lunar eclipse. +After using [`Astronomy_SearchLunarEclipse`](#Astronomy_SearchLunarEclipse) to find the first lunar eclipse in a series, you can call this function to find the next consecutive lunar eclipse. Pass in the `peak` value from the [`astro_lunar_eclipse_t`](#astro_lunar_eclipse_t) returned by the previous call to `Astronomy_SearchLunarEclipse` or `Astronomy_NextLunarEclipse` to find the next lunar eclipse. @@ -1383,7 +1408,7 @@ If the search does not converge within 20 iterations, it will fail with status c ### Astronomy_SearchGlobalSolarEclipse(startTime) ⇒ [`astro_global_solar_eclipse_t`](#astro_global_solar_eclipse_t) -**Searches for a solar eclipse.** +**Searches for a solar eclipse visible anywhere on the Earth's surface.** @@ -1435,6 +1460,33 @@ On success, the function reports the date and time, along with the horizontal co +--- + + +### Astronomy_SearchLocalSolarEclipse(startTime, observer) ⇒ [`astro_local_solar_eclipse_t`](#astro_local_solar_eclipse_t) + +**Searches for a solar eclipse visible at a specific location on the Earth's surface.** + + + +This function finds the first solar eclipse that occurs after `startTime`. A solar eclipse found may be partial, annular, or total. See [`astro_local_solar_eclipse_t`](#astro_local_solar_eclipse_t) for more information. To find a series of solar eclipses, call this function once, then keep calling [`Astronomy_NextLocalSolarEclipse`](#Astronomy_NextLocalSolarEclipse) as many times as desired, passing in the `peak` value returned from the previous call. + +IMPORTANT: An eclipse reported by this function might be partly or completely invisible to the observer due to the time of day. + + + +**Returns:** If successful, the `status` field in the returned structure will contain `ASTRO_SUCCESS` and the remaining structure fields are as described in [`astro_local_solar_eclipse_t`](#astro_local_solar_eclipse_t). Any other value indicates an error. + + + +| Type | Parameter | Description | +| --- | --- | --- | +| [`astro_time_t`](#astro_time_t) | `startTime` | The date and time for starting the search for a solar eclipse. | +| [`astro_observer_t`](#astro_observer_t) | `observer` | The geographic location of the observer. | + + + + --- @@ -2406,6 +2458,38 @@ Returned by the functions [`Astronomy_Illumination`](#Astronomy_Illumination) an | `double` | `ring_tilt` | For Saturn, the tilt angle in degrees of its rings as seen from Earth. For all other bodies, 0. | +--- + + +### `astro_local_solar_eclipse_t` + +**Information about a solar eclipse as seen by an observer at a given time and geographic location.** + + + +Returned by [`Astronomy_SearchLocalSolarEclipse`](#Astronomy_SearchLocalSolarEclipse) or [`Astronomy_NextLocalSolarEclipse`](#Astronomy_NextLocalSolarEclipse) to report information about a solar eclipse as seen at a given geographic location. If a solar eclipse is found, `status` holds `ASTRO_SUCCESS` and the other fields are set. If `status` holds any other value, it is an error code and the other fields are undefined. + +When a solar eclipse is found, it is classified as partial, annular, or total. The `kind` field thus holds `ECLIPSE_PARTIAL`, `ECLIPSE_ANNULAR`, or `ECLIPSE_TOTAL`. A partial solar eclipse is when the Moon does not line up directly enough with the Sun to completely block the Sun's light from reaching the observer. An annular eclipse occurs when the Moon's disc is completely visible against the Sun but the Moon is too far away to completely block the Sun's light; this leaves the Sun with a ring-like appearance. A total eclipse occurs when the Moon is close enough to the Earth and aligned with the Sun just right to completely block all sunlight from reaching the observer. + +Field `peak` holds the date and time of the center of the eclipse, when it is at its peak. + +As a convenience to the caller, the `sunrise` and `sunset` fields indicate the date and time of sunrise and sunset on the same day as the eclipse. An eclipse may not be completely visible to the observer due to starting before sunrise or ending after sunset. However, an eclipse will never be reported that is invisible to the observer due to happening completely at night. + +The fields `partial_begin` and `partial_end` are always set, and indicate when the eclipse begins/ends. If the eclipse reaches totality or becomes annular, `total_begin` and `total_end` indicate when the total/annular phase begins/ends. All four fields must be checked against `sunrise` and `sunset` to determine which portion of the eclipse could be seen by the observer. + +| Type | Member | Description | +| ---- | ------ | ----------- | +| [`astro_status_t`](#astro_status_t) | `status` | `ASTRO_SUCCESS` if this struct is valid; otherwise an error code. | +| [`astro_eclipse_kind_t`](#astro_eclipse_kind_t) | `kind` | The type of solar eclipse found. | +| [`astro_time_t`](#astro_time_t) | `peak` | The time of the eclipse at its peak. | +| [`astro_time_t`](#astro_time_t) | `sunrise` | The time of sunrise on the same day as the eclipse. | +| [`astro_time_t`](#astro_time_t) | `sunset` | The time of sunset on the same day as the eclipse. | +| [`astro_time_t`](#astro_time_t) | `partial_begin` | The time the partial eclipse begins; may happen before sunrise. | +| [`astro_time_t`](#astro_time_t) | `total_begin` | If a total eclipse, the time totality begins; otherwise invalid. | +| [`astro_time_t`](#astro_time_t) | `total_end` | If a total eclipse, the time totality ends; otherwise invalid. | +| [`astro_time_t`](#astro_time_t) | `partial_end` | The time the partial eclipse ends; may happen after sunset. | + + --- @@ -2421,7 +2505,7 @@ When a lunar eclipse is found, it is classified as penumbral, partial, or total. The `kind` field thus holds `ECLIPSE_PENUMBRAL`, `ECLIPSE_PARTIAL`, or `ECLIPSE_TOTAL`, depending on the kind of lunar eclipse found. -Field `center` holds the date and time of the center of the eclipse, when it is at its peak. +Field `peak` holds the date and time of the center of the eclipse, when it is at its peak. Fields `sd_penum`, `sd_partial`, and `sd_total` hold the semi-duration of each phase of the eclipse, which is half of the amount of time the eclipse spends in each phase (expressed in minutes), or 0 if the eclipse never reaches that phase. By converting from minutes to days, and subtracting/adding with `center`, the caller may determine the date and time of the beginning/end of each eclipse phase. @@ -2429,7 +2513,7 @@ Fields `sd_penum`, `sd_partial`, and `sd_total` hold the semi-duration of each p | ---- | ------ | ----------- | | [`astro_status_t`](#astro_status_t) | `status` | `ASTRO_SUCCESS` if this struct is valid; otherwise an error code. | | [`astro_eclipse_kind_t`](#astro_eclipse_kind_t) | `kind` | The type of lunar eclipse found. | -| [`astro_time_t`](#astro_time_t) | `center` | The time of the eclipse at its peak. | +| [`astro_time_t`](#astro_time_t) | `peak` | The time of the eclipse at its peak. | | `double` | `sd_penum` | The semi-duration of the penumbral phase in minutes. | | `double` | `sd_partial` | The semi-duration of the partial phase in minutes, or 0.0 if none. | | `double` | `sd_total` | The semi-duration of the total phase in minutes, or 0.0 if none. | diff --git a/source/c/astronomy.c b/source/c/astronomy.c index 0a314b1d..8bcb1eb1 100644 --- a/source/c/astronomy.c +++ b/source/c/astronomy.c @@ -7140,7 +7140,7 @@ static astro_lunar_eclipse_t LunarEclipseError(astro_status_t status) astro_lunar_eclipse_t eclipse; eclipse.status = status; eclipse.kind = ECLIPSE_NONE; - eclipse.center = TimeError(); + eclipse.peak = TimeError(); eclipse.sd_penum = eclipse.sd_partial = eclipse.sd_total = NAN; return eclipse; } @@ -7391,11 +7391,7 @@ astro_lunar_eclipse_t Astronomy_SearchLunarEclipse(astro_time_t startTime) if (fullmoon.status != ASTRO_SUCCESS) return LunarEclipseError(fullmoon.status); - /* - Pruning: if the full Moon's ecliptic latitude is too large, - a lunar eclipse is not possible. Avoid needless work searching for - the minimum moon distance. - */ + /* Pruning: if the full Moon's ecliptic latitude is too large, a lunar eclipse is not possible. */ CalcMoon(fullmoon.time.tt / 36525.0, &eclip_lon, &eclip_lat, &distance); if (RAD2DEG * fabs(eclip_lat) < PruneLatitude) { @@ -7410,7 +7406,7 @@ astro_lunar_eclipse_t Astronomy_SearchLunarEclipse(astro_time_t startTime) /* This is at least a penumbral eclipse. We will return a result. */ eclipse.status = ASTRO_SUCCESS; eclipse.kind = ECLIPSE_PENUMBRAL; - eclipse.center = shadow.time; + eclipse.peak = shadow.time; eclipse.sd_total = 0.0; eclipse.sd_partial = 0.0; eclipse.sd_penum = ShadowSemiDurationMinutes(shadow.time, shadow.p + MOON_MEAN_RADIUS_KM, 200.0); @@ -7452,7 +7448,7 @@ astro_lunar_eclipse_t Astronomy_SearchLunarEclipse(astro_time_t startTime) * * After using #Astronomy_SearchLunarEclipse to find the first lunar eclipse * in a series, you can call this function to find the next consecutive lunar eclipse. - * Pass in the `center` value from the #astro_lunar_eclipse_t returned by the + * Pass in the `peak` value from the #astro_lunar_eclipse_t returned by the * previous call to `Astronomy_SearchLunarEclipse` or `Astronomy_NextLunarEclipse` * to find the next lunar eclipse. * @@ -7609,7 +7605,7 @@ static astro_global_solar_eclipse_t GeoidIntersect(shadow_t shadow) /** - * @brief Searches for a solar eclipse. + * @brief Searches for a solar eclipse visible anywhere on the Earth's surface. * * This function finds the first solar eclipse that occurs after `startTime`. * A solar eclipse found may be partial, annular, or total. @@ -7644,15 +7640,11 @@ astro_global_solar_eclipse_t Astronomy_SearchGlobalSolarEclipse(astro_time_t sta if (newmoon.status != ASTRO_SUCCESS) return GlobalSolarEclipseError(newmoon.status); - /* - Pruning: if the full Moon's ecliptic latitude is too large, - a lunar eclipse is not possible. Avoid needless work searching for - the minimum moon distance. - */ + /* Pruning: if the new moon's ecliptic latitude is too large, a solar eclipse is not possible. */ CalcMoon(newmoon.time.tt / 36525.0, &eclip_lon, &eclip_lat, &distance); if (RAD2DEG * fabs(eclip_lat) < PruneLatitude) { - /* Search near the full moon for the time when the center of the Earth */ + /* Search near the new moon for the time when the center of the Earth */ /* is closest to the line passing through the centers of the Sun and Moon. */ shadow = PeakMoonShadow(newmoon.time); if (shadow.status != ASTRO_SUCCESS) @@ -7677,7 +7669,7 @@ astro_global_solar_eclipse_t Astronomy_SearchGlobalSolarEclipse(astro_time_t sta /** - * @brief Searches for the next solar eclipse in a series. + * @brief Searches for the next global solar eclipse in a series. * * After using #Astronomy_SearchGlobalSolarEclipse to find the first solar eclipse * in a series, you can call this function to find the next consecutive solar eclipse. @@ -7699,6 +7691,163 @@ astro_global_solar_eclipse_t Astronomy_NextGlobalSolarEclipse(astro_time_t prevE return Astronomy_SearchGlobalSolarEclipse(startTime); } + +static astro_local_solar_eclipse_t LocalSolarEclipseError(astro_status_t status) +{ + astro_local_solar_eclipse_t eclipse; + + eclipse.status = status; + eclipse.kind = ECLIPSE_NONE; + eclipse.peak = eclipse.sunrise = eclipse.sunset = + eclipse.partial_begin = eclipse.total_begin = + eclipse.total_end = eclipse.partial_end = + TimeError(); + + return eclipse; +} + + +static shadow_t PeakLocalMoonShadow(astro_time_t search_center_time, astro_observer_t observer) +{ + /* FIXFIXFIX - not yet implemented. */ + (void)search_center_time; + (void)observer; + return ShadowError(ASTRO_NOT_INITIALIZED); +} + + +static astro_local_solar_eclipse_t LocalEclipse( + shadow_t shadow, + astro_observer_t observer) +{ + /* FIXFIXFIX - not yet implemented. */ + (void)shadow; + (void)observer; + return LocalSolarEclipseError(ASTRO_NOT_INITIALIZED); +} + + +static int DaytimeOverlap( + astro_local_solar_eclipse_t *eclipse, + astro_observer_t observer) +{ + /* FIXFIXFIX - not yet implemented. */ + (void)eclipse; + (void)observer; + return 0; +} + + + +/** + * @brief Searches for a solar eclipse visible at a specific location on the Earth's surface. + * + * This function finds the first solar eclipse that occurs after `startTime`. + * A solar eclipse found may be partial, annular, or total. + * See #astro_local_solar_eclipse_t for more information. + * To find a series of solar eclipses, call this function once, + * then keep calling #Astronomy_NextLocalSolarEclipse as many times as desired, + * passing in the `peak` value returned from the previous call. + * + * IMPORTANT: An eclipse reported by this function might be partly or + * completely invisible to the observer due to the time of day. + * + * @param startTime + * The date and time for starting the search for a solar eclipse. + * + * @param observer + * The geographic location of the observer. + * + * @return + * If successful, the `status` field in the returned structure will contain `ASTRO_SUCCESS` + * and the remaining structure fields are as described in #astro_local_solar_eclipse_t. + * Any other value indicates an error. + */ +astro_local_solar_eclipse_t Astronomy_SearchLocalSolarEclipse( + astro_time_t startTime, + astro_observer_t observer) +{ + const double PruneLatitude = 1.8; /* Moon's ecliptic latitude beyond which eclipse is impossible */ + astro_time_t nmtime; + astro_search_result_t newmoon; + shadow_t shadow; + int nmcount; + double eclip_lat, eclip_lon, distance; + astro_local_solar_eclipse_t eclipse; + + /* Iterate through consecutive new moons until we find a solar eclipse visible somewhere on Earth. */ + nmtime = startTime; + for (nmcount=0; nmcount < 12; ++nmcount) + { + /* Search for the next new moon. Any eclipse will be near it. */ + newmoon = Astronomy_SearchMoonPhase(0.0, nmtime, 40.0); + if (newmoon.status != ASTRO_SUCCESS) + return LocalSolarEclipseError(newmoon.status); + + /* Pruning: if the new moon's ecliptic latitude is too large, a solar eclipse is not possible. */ + CalcMoon(newmoon.time.tt / 36525.0, &eclip_lon, &eclip_lat, &distance); + if (RAD2DEG * fabs(eclip_lat) < PruneLatitude) + { + /* Search near the new moon for the time when the observer */ + /* is closest to the line passing through the centers of the Sun and Moon. */ + shadow = PeakLocalMoonShadow(newmoon.time, observer); + if (shadow.status != ASTRO_SUCCESS) + return LocalSolarEclipseError(shadow.status); + + if (shadow.r < shadow.p) + { + /* This is at least a partial solar eclipse for the observer. */ + eclipse = LocalEclipse(shadow, observer); + if (eclipse.status != ASTRO_SUCCESS) + return eclipse; + + /* Ignore any eclipse that happens completely at night. */ + /* Find sunrise/sunset pairs until either some part of the eclipse */ + /* overlaps with daytime hours, or we prove that the eclipse is entirely at night. */ + if (DaytimeOverlap(&eclipse, observer)) + return eclipse; + } + } + + /* We didn't find an eclipse on this new moon, so search for the next one. */ + nmtime = Astronomy_AddDays(newmoon.time, 10.0); + } + + /* Safety valve to prevent infinite loop. */ + /* This should never happen, because at least 2 solar eclipses happen per year. */ + return LocalSolarEclipseError(ASTRO_INTERNAL_ERROR); +} + + +/** + * @brief Searches for the next local solar eclipse in a series. + * + * After using #Astronomy_SearchLocalSolarEclipse to find the first solar eclipse + * in a series, you can call this function to find the next consecutive solar eclipse. + * Pass in the `center` value from the #astro_local_solar_eclipse_t returned by the + * previous call to `Astronomy_SearchLocalSolarEclipse` or `Astronomy_NextLocalSolarEclipse` + * to find the next solar eclipse. + * + * @param prevEclipseTime + * A date and time near a new moon. Solar eclipse search will start at the next new moon. + * + * @param observer + * The geographic location of the observer. + * + * @return + * If successful, the `status` field in the returned structure will contain `ASTRO_SUCCESS` + * and the remaining structure fields are as described in #astro_local_solar_eclipse_t. + * Any other value indicates an error. + */ +astro_local_solar_eclipse_t Astronomy_NextLocalSolarEclipse( + astro_time_t prevEclipseTime, + astro_observer_t observer) +{ + astro_time_t startTime = Astronomy_AddDays(prevEclipseTime, 10.0); + return Astronomy_SearchLocalSolarEclipse(startTime, observer); +} + + #ifdef __cplusplus } #endif diff --git a/source/c/astronomy.h b/source/c/astronomy.h index 01325f84..192a8b7e 100644 --- a/source/c/astronomy.h +++ b/source/c/astronomy.h @@ -526,7 +526,7 @@ astro_eclipse_kind_t; * The `kind` field thus holds `ECLIPSE_PENUMBRAL`, `ECLIPSE_PARTIAL`, or `ECLIPSE_TOTAL`, * depending on the kind of lunar eclipse found. * - * Field `center` holds the date and time of the center of the eclipse, when it is at its peak. + * Field `peak` holds the date and time of the center of the eclipse, when it is at its peak. * * Fields `sd_penum`, `sd_partial`, and `sd_total` hold the semi-duration of each phase * of the eclipse, which is half of the amount of time the eclipse spends in each @@ -538,7 +538,7 @@ typedef struct { astro_status_t status; /**< `ASTRO_SUCCESS` if this struct is valid; otherwise an error code. */ astro_eclipse_kind_t kind; /**< The type of lunar eclipse found. */ - astro_time_t center; /**< The time of the eclipse at its peak. */ + astro_time_t peak; /**< The time of the eclipse at its peak. */ double sd_penum; /**< The semi-duration of the penumbral phase in minutes. */ double sd_partial; /**< The semi-duration of the partial phase in minutes, or 0.0 if none. */ double sd_total; /**< The semi-duration of the total phase in minutes, or 0.0 if none. */ @@ -589,6 +589,53 @@ typedef struct astro_global_solar_eclipse_t; +/** + * @brief Information about a solar eclipse as seen by an observer at a given time and geographic location. + * + * Returned by #Astronomy_SearchLocalSolarEclipse or #Astronomy_NextLocalSolarEclipse + * to report information about a solar eclipse as seen at a given geographic location. + * If a solar eclipse is found, `status` holds `ASTRO_SUCCESS` and the other fields are set. + * If `status` holds any other value, it is an error code and the other fields are undefined. + * + * When a solar eclipse is found, it is classified as partial, annular, or total. + * The `kind` field thus holds `ECLIPSE_PARTIAL`, `ECLIPSE_ANNULAR`, or `ECLIPSE_TOTAL`. + * A partial solar eclipse is when the Moon does not line up directly enough with the Sun + * to completely block the Sun's light from reaching the observer. + * An annular eclipse occurs when the Moon's disc is completely visible against the Sun + * but the Moon is too far away to completely block the Sun's light; this leaves the + * Sun with a ring-like appearance. + * A total eclipse occurs when the Moon is close enough to the Earth and aligned with the + * Sun just right to completely block all sunlight from reaching the observer. + * + * Field `peak` holds the date and time of the center of the eclipse, when it is at its peak. + * + * As a convenience to the caller, the `sunrise` and `sunset` fields indicate the + * date and time of sunrise and sunset on the same day as the eclipse. + * An eclipse may not be completely visible to the observer due to starting + * before sunrise or ending after sunset. However, an eclipse will never be + * reported that is invisible to the observer due to happening completely at night. + * + * The fields `partial_begin` and `partial_end` are always set, and indicate when + * the eclipse begins/ends. If the eclipse reaches totality or becomes annular, + * `total_begin` and `total_end` indicate when the total/annular phase begins/ends. + * All four fields must be checked against `sunrise` and `sunset` to determine which + * portion of the eclipse could be seen by the observer. + */ +typedef struct +{ + astro_status_t status; /**< `ASTRO_SUCCESS` if this struct is valid; otherwise an error code. */ + astro_eclipse_kind_t kind; /**< The type of solar eclipse found. */ + astro_time_t peak; /**< The time of the eclipse at its peak. */ + astro_time_t sunrise; /**< The time of sunrise on the same day as the eclipse. */ + astro_time_t sunset; /**< The time of sunset on the same day as the eclipse. */ + astro_time_t partial_begin; /**< The time the partial eclipse begins; may happen before sunrise. */ + astro_time_t total_begin; /**< If a total eclipse, the time totality begins; otherwise invalid. */ + astro_time_t total_end; /**< If a total eclipse, the time totality ends; otherwise invalid. */ + astro_time_t partial_end; /**< The time the partial eclipse ends; may happen after sunset. */ +} +astro_local_solar_eclipse_t; + + /** * @brief Aberration calculation options. * @@ -720,6 +767,8 @@ astro_lunar_eclipse_t Astronomy_SearchLunarEclipse(astro_time_t startTime); astro_lunar_eclipse_t Astronomy_NextLunarEclipse(astro_time_t prevEclipseTime); astro_global_solar_eclipse_t Astronomy_SearchGlobalSolarEclipse(astro_time_t startTime); astro_global_solar_eclipse_t Astronomy_NextGlobalSolarEclipse(astro_time_t prevEclipseTime); +astro_local_solar_eclipse_t Astronomy_SearchLocalSolarEclipse(astro_time_t startTime, astro_observer_t observer); +astro_local_solar_eclipse_t Astronomy_NextLocalSolarEclipse(astro_time_t prevEclipseTime, astro_observer_t observer); astro_search_result_t Astronomy_Search( astro_search_func_t func,