Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
34ff6a36ae
|
|||
|
d40c4dfcd9
|
|||
|
bec7b8a0d8
|
+126
-65
@@ -46,7 +46,7 @@ go get b612.me/astro
|
|||||||
- Lunar position, rise/set, Earth distance, phase, new/full/quarter times, apparent diameter, bright-limb angle, parallactic angle, geocentric/topocentric libration, apsides, nodes, maximum declination
|
- Lunar position, rise/set, Earth distance, phase, new/full/quarter times, apparent diameter, bright-limb angle, parallactic angle, geocentric/topocentric libration, apsides, nodes, maximum declination
|
||||||
- `lite/sun` and `lite/moon` lightweight approximation chains for watches, frontends, mini programs, and other resource-constrained environments
|
- `lite/sun` and `lite/moon` lightweight approximation chains for watches, frontends, mini programs, and other resource-constrained environments
|
||||||
- Global and local solar/lunar eclipses, solar central paths, partial footprints, visible local lunar eclipses, Saros metadata, and SVG diagrams
|
- Global and local solar/lunar eclipses, solar central paths, partial footprints, visible local lunar eclipses, Saros metadata, and SVG diagrams
|
||||||
- Seven major planets with positions, rise/set, conjunction/opposition/station events, quadratures, elongations, nodes, phase, apparent magnitude, apparent diameter, parallactic angle, and physical ephemerides
|
- Seven major planets with positions, rise/set, conjunction/opposition/station events, quadratures, elongations, Mercury/Venus geocentric transits, nodes, phase, apparent magnitude, apparent diameter, parallactic angle, and physical ephemerides
|
||||||
- 9100+ star catalog entries, constellation lookup, proper-motion propagation, rise/set, parallactic angle, and apparent altitude
|
- 9100+ star catalog entries, constellation lookup, proper-motion propagation, rise/set, parallactic angle, and apparent altitude
|
||||||
- Coordinate transforms, topocentric coordinates, sidereal time, precession, nutation, angular distance, refraction, airmass, parallactic angle, and Galactic coordinates
|
- Coordinate transforms, topocentric coordinates, sidereal time, precession, nutation, angular distance, refraction, airmass, parallactic angle, and Galactic coordinates
|
||||||
- Standalone formulas for blackbody radiation, synodic periods, photometry, telescope limiting magnitude, stellar radius/temperature/luminosity relations, and airmass models
|
- Standalone formulas for blackbody radiation, synodic periods, photometry, telescope limiting magnitude, stellar radius/temperature/luminosity relations, and airmass models
|
||||||
@@ -63,7 +63,7 @@ go get b612.me/astro
|
|||||||
| `moon` | Lunar position, rise/set, phases, new/full/quarter times, apparent altitude, parallactic angle, diameter, bright-limb angle, geocentric/topocentric libration, apsides, nodes, maximum declination |
|
| `moon` | Lunar position, rise/set, phases, new/full/quarter times, apparent altitude, parallactic angle, diameter, bright-limb angle, geocentric/topocentric libration, apsides, nodes, maximum declination |
|
||||||
| `lite/sun` / `lite/moon` | Lightweight Sun/Moon approximation chains for minute-level rise/set, lightweight sky position, and lunar-phase work |
|
| `lite/sun` / `lite/moon` | Lightweight Sun/Moon approximation chains for minute-level rise/set, lightweight sky position, and lunar-phase work |
|
||||||
| `eclipse` / `eclipse/svg` | Global/local solar and lunar eclipses, solar central paths, partial footprints, local visibility filtering, Saros metadata, SVG output |
|
| `eclipse` / `eclipse/svg` | Global/local solar and lunar eclipses, solar central paths, partial footprints, local visibility filtering, Saros metadata, SVG output |
|
||||||
| `mercury` / `venus` | Positions, rise/set, conjunctions, stations, elongations, phase, parallactic angle, magnitude, diameter, nodes, physical ephemerides |
|
| `mercury` / `venus` | Positions, rise/set, conjunctions, stations, elongations, geocentric transits, phase, parallactic angle, magnitude, diameter, nodes, physical ephemerides |
|
||||||
| `mars` / `jupiter` / `saturn` / `uranus` / `neptune` | Positions, rise/set, conjunction/opposition, stations, quadratures, phase, parallactic angle, magnitude, diameter, nodes, physical ephemerides |
|
| `mars` / `jupiter` / `saturn` / `uranus` / `neptune` | Positions, rise/set, conjunction/opposition, stations, quadratures, phase, parallactic angle, magnitude, diameter, nodes, physical ephemerides |
|
||||||
| `earth` | Earth orbital eccentricity, perihelion, aphelion |
|
| `earth` | Earth orbital eccentricity, perihelion, aphelion |
|
||||||
| `star` | Constellation lookup, star catalog, proper motion / precession / nutation correction, stellar rise/set, parallactic angle, apparent altitude |
|
| `star` | Constellation lookup, star catalog, proper motion / precession / nutation correction, stellar rise/set, parallactic angle, apparent altitude |
|
||||||
@@ -249,7 +249,6 @@ Output:
|
|||||||
|
|
||||||
```text
|
```text
|
||||||
[魏明帝 景初三年腊月二十 蜀后主 延熙二年冬月十九 吴大帝 赤乌二年冬月二十] // one Gregorian instant maps to parallel Three Kingdoms lunisolar results
|
[魏明帝 景初三年腊月二十 蜀后主 延熙二年冬月十九 吴大帝 赤乌二年冬月二十] // one Gregorian instant maps to parallel Three Kingdoms lunisolar results
|
||||||
|
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
"solarDate": "0240-01-01T08:08:08.000000008+08:00",
|
"solarDate": "0240-01-01T08:08:08.000000008+08:00",
|
||||||
@@ -309,7 +308,6 @@ Output:
|
|||||||
"chineseZodiac": "羊"
|
"chineseZodiac": "羊"
|
||||||
}
|
}
|
||||||
] // structured lunisolar records, one object per matching historical result
|
] // structured lunisolar records, one object per matching historical result
|
||||||
|
|
||||||
1083-11-24 00:00:00 +0800 CST // Gregorian date corresponding to 元丰六年十月十二日
|
1083-11-24 00:00:00 +0800 CST // Gregorian date corresponding to 元丰六年十月十二日
|
||||||
[宋神宗 元丰六年十月十二 辽道宗 大康九年十月十二] // the same day also matches a Liao calendar result
|
[宋神宗 元丰六年十月十二 辽道宗 大康九年十月十二] // the same day also matches a Liao calendar result
|
||||||
2026-02-17 00:00:00 +0800 CST // Chinese New Year in 2026
|
2026-02-17 00:00:00 +0800 CST // Chinese New Year in 2026
|
||||||
@@ -341,10 +339,10 @@ func main() {
|
|||||||
Output:
|
Output:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
2020-02-04 17:03:17.820854187 +0800 CST // Beginning of Spring
|
2020-02-04 17:03:20.471614301 +0800 CST // Beginning of Spring
|
||||||
2020-12-21 18:02:17.568823993 +0800 CST // Winter Solstice
|
2020-12-21 18:02:20.648710727 +0800 CST // Winter Solstice
|
||||||
2020-03-20 11:49:34.502393603 +0800 CST // March Equinox
|
2020-03-20 11:49:37.149532735 +0800 CST // March Equinox
|
||||||
2020-03-20 11:49:34.502393603 +0800 CST // same result from direct longitude input
|
2020-03-20 11:49:37.149532735 +0800 CST // same result from direct longitude input
|
||||||
```
|
```
|
||||||
|
|
||||||
### Sun And Moon
|
### Sun And Moon
|
||||||
@@ -400,14 +398,14 @@ func main() {
|
|||||||
Output:
|
Output:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
2020-01-01 07:22:27.964431345 +0800 CST <nil> // civil morning twilight begins
|
2020-01-01 07:22:27.960488498 +0800 CST <nil> // civil morning twilight begins
|
||||||
2020-01-01 07:50:14.534510672 +0800 CST <nil> // sunrise
|
2020-01-01 07:50:14.530648291 +0800 CST <nil> // sunrise
|
||||||
2020-01-01 12:47:35.933117866 +0800 CST // solar upper culmination
|
2020-01-01 12:47:35.933117866 +0800 CST // solar upper culmination
|
||||||
2020-01-01 17:44:47.076647579 +0800 CST <nil> // sunset
|
2020-01-01 17:44:47.070974707 +0800 CST <nil> // sunset
|
||||||
2020-01-01 18:12:33.629668056 +0800 CST <nil> // civil evening twilight ends
|
2020-01-01 18:12:33.624035418 +0800 CST <nil> // civil evening twilight ends
|
||||||
2020-01-01 11:52:44.643359184 +0800 CST <nil> // moonrise
|
2020-01-01 11:52:45.157297253 +0800 CST <nil> // moonrise
|
||||||
2020-01-01 17:38:03.879639208 +0800 CST // lunar upper culmination
|
2020-01-01 17:38:02.510787248 +0800 CST // lunar upper culmination
|
||||||
2020-01-01 23:26:52.202896177 +0800 CST <nil> // moonset
|
2020-01-01 23:26:51.580328643 +0800 CST <nil> // moonset
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Sun and Moon position
|
#### Sun and Moon position
|
||||||
@@ -461,17 +459,16 @@ func main() {
|
|||||||
Output:
|
Output:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
280.0152925179703 // apparent ecliptic longitude of the Sun, degrees
|
280.01526210031136 // apparent ecliptic longitude of the Sun, degrees
|
||||||
23.436215552851408 // true obliquity of the ecliptic, degrees
|
23.4362178391013 // true obliquity of the ecliptic, degrees
|
||||||
RA: 18h43m34.83s Dec: -23°3′30.25″ // apparent RA and Dec of the Sun
|
RA: 18h43m34.82s Dec: -23°3′30.27″ // apparent RA and Dec of the Sun
|
||||||
Sagittarius // English constellation containing the Sun
|
Sagittarius // English constellation containing the Sun
|
||||||
Azimuth: 120.19483856399326 Altitude: 2.4014324584398516 Zenith: 87.59856754156014 // solar horizontal coordinates at Xi'an
|
Azimuth: 120.19477090015224 Altitude: 2.4014437419430097 Zenith: 87.59855625805699 // solar horizontal coordinates at Xi'an
|
||||||
0.9832929365443133 // Sun-Earth distance, AU
|
0.983292937163176 // Sun-Earth distance, AU
|
||||||
|
RA: 23h17m53.15s Dec: -10°19′18.57″ // topocentric apparent RA and Dec of the Moon
|
||||||
RA: 23h17m51.93s Dec: -10°19′17.02″ // topocentric apparent RA and Dec of the Moon
|
|
||||||
Aquarius // English constellation containing the Moon
|
Aquarius // English constellation containing the Moon
|
||||||
Azimuth: 67.84449893794012 Altitude: -45.13018696439911 Zenith: 135.13018696439912 // lunar horizontal coordinates at Xi'an
|
Azimuth: 67.84050700509859 Altitude: -45.13425530765482 Zenith: 135.13425530765483 // lunar horizontal coordinates at Xi'an
|
||||||
404238.6354387698 // Earth-Moon distance, km
|
404238.6096080479 // Earth-Moon distance, km
|
||||||
```
|
```
|
||||||
|
|
||||||
`sun.Physical` / `sun.PhysicalN` return:
|
`sun.Physical` / `sun.PhysicalN` return:
|
||||||
@@ -609,12 +606,12 @@ func main() {
|
|||||||
Output:
|
Output:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
0.3000437415436273 // about 30% of the lunar disk is illuminated
|
0.300041309608744 // about 30% of the lunar disk is illuminated
|
||||||
上峨眉月 // Chinese phase description
|
上峨眉月 // Chinese phase description
|
||||||
2020-01-25 05:41:55.820311009 +0800 CST // next new moon
|
2020-01-25 05:41:58.271192908 +0800 CST // next new moon
|
||||||
2020-01-03 12:45:20.809730887 +0800 CST // next first quarter
|
2020-01-03 12:45:23.229190707 +0800 CST // next first quarter
|
||||||
2020-01-11 03:21:14.729664623 +0800 CST // next full moon
|
2020-01-11 03:21:17.159625291 +0800 CST // next full moon
|
||||||
2020-01-17 20:58:20.955985486 +0800 CST // next last quarter
|
2020-01-17 20:58:23.396406769 +0800 CST // next last quarter
|
||||||
```
|
```
|
||||||
|
|
||||||
Phase aliases:
|
Phase aliases:
|
||||||
@@ -674,6 +671,8 @@ Common entry points:
|
|||||||
- `LastSolarEclipse` / `NextSolarEclipse` / `ClosestSolarEclipse`: search global solar eclipses
|
- `LastSolarEclipse` / `NextSolarEclipse` / `ClosestSolarEclipse`: search global solar eclipses
|
||||||
- `LocalSolarEclipseOnDate`: detect whether a site can see a local solar eclipse on that date
|
- `LocalSolarEclipseOnDate`: detect whether a site can see a local solar eclipse on that date
|
||||||
- `LastLocalSolarEclipse` / `NextLocalSolarEclipse` / `ClosestLocalSolarEclipse`: search locally visible solar eclipses
|
- `LastLocalSolarEclipse` / `NextLocalSolarEclipse` / `ClosestLocalSolarEclipse`: search locally visible solar eclipses
|
||||||
|
- `LastLocalTotalSolarEclipse` / `NextLocalTotalSolarEclipse` / `ClosestLocalTotalSolarEclipse`: search locally visible total solar eclipses, returning `(info, ok)`
|
||||||
|
- `LastLocalAnnularSolarEclipse` / `NextLocalAnnularSolarEclipse` / `ClosestLocalAnnularSolarEclipse`: search locally visible annular solar eclipses, returning `(info, ok)`
|
||||||
- `SolarEclipseCentralPath`: compute central line, northern/southern limits, and greatest-eclipse point
|
- `SolarEclipseCentralPath`: compute central line, northern/southern limits, and greatest-eclipse point
|
||||||
- `SolarEclipsePartialFootprints`: compute the partial-eclipse penumbral footprint on Earth
|
- `SolarEclipsePartialFootprints`: compute the partial-eclipse penumbral footprint on Earth
|
||||||
- `eclipse/svg.LocalSolarEclipseSVG`: render a local solar-disk SVG
|
- `eclipse/svg.LocalSolarEclipseSVG`: render a local solar-disk SVG
|
||||||
@@ -925,6 +924,7 @@ Common entry points:
|
|||||||
- `LastLunarEclipse` / `NextLunarEclipse` / `ClosestLunarEclipse`: search global lunar eclipses
|
- `LastLunarEclipse` / `NextLunarEclipse` / `ClosestLunarEclipse`: search global lunar eclipses
|
||||||
- `LocalLunarEclipseOnDate`: detect whether a visible lunar eclipse is visible from a site on a local date
|
- `LocalLunarEclipseOnDate`: detect whether a visible lunar eclipse is visible from a site on a local date
|
||||||
- `LastLocalLunarEclipse` / `NextLocalLunarEclipse` / `ClosestLocalLunarEclipse`: search visible local lunar eclipses
|
- `LastLocalLunarEclipse` / `NextLocalLunarEclipse` / `ClosestLocalLunarEclipse`: search visible local lunar eclipses
|
||||||
|
- `LastLocalTotalLunarEclipse` / `NextLocalTotalLunarEclipse` / `ClosestLocalTotalLunarEclipse`: search visible local total lunar eclipses, returning `(info, ok)`
|
||||||
- `GeometricLocalLunarEclipseOnDate`: detect geometric lunar eclipse overlap without filtering by whether the Moon is above the horizon
|
- `GeometricLocalLunarEclipseOnDate`: detect geometric lunar eclipse overlap without filtering by whether the Moon is above the horizon
|
||||||
- `eclipse/svg.LunarEclipseSVG`: render a lunar-eclipse shadow-path SVG
|
- `eclipse/svg.LunarEclipseSVG`: render a lunar-eclipse shadow-path SVG
|
||||||
|
|
||||||
@@ -1002,18 +1002,18 @@ Output:
|
|||||||
```text
|
```text
|
||||||
total // eclipse type
|
total // eclipse type
|
||||||
true {125 49 72} // Lunar Saros 125, member 49/72
|
true {125 49 72} // Lunar Saros 125, member 49/72
|
||||||
2028-12-31 16:52:05.257715537 +0000 UTC // greatest eclipse
|
2028-12-31 16:52:05.566135346 +0000 UTC // greatest eclipse
|
||||||
2.273989043 1.246114288 // penumbral and umbral magnitudes
|
2.273989043382249 1.2461142882946992 // penumbral and umbral magnitudes
|
||||||
2028-12-31 14:03:54.163612125 +0000 UTC // P1
|
2028-12-31 14:03:54.219463169 +0000 UTC // P1
|
||||||
2028-12-31 15:07:42.293254197 +0000 UTC // U1
|
2028-12-31 15:07:42.115980684 +0000 UTC // U1
|
||||||
2028-12-31 16:16:27.717077732 +0000 UTC // U2
|
2028-12-31 16:16:27.24464178 +0000 UTC // U2
|
||||||
2028-12-31 17:27:46.687390804 +0000 UTC // U3
|
2028-12-31 17:27:46.214954853 +0000 UTC // U3
|
||||||
2028-12-31 18:36:32.272528112 +0000 UTC // U4
|
2028-12-31 18:36:32.251235246 +0000 UTC // U4
|
||||||
2028-12-31 19:40:11.173523784 +0000 UTC // P4
|
2028-12-31 19:40:11.52023971 +0000 UTC // P4
|
||||||
2.29960334 1.25117109 // Chauvenet penumbral and umbral magnitudes
|
2.2996033397593934 1.2511710895700923 // Chauvenet penumbral and umbral magnitudes
|
||||||
true // local date overlaps an eclipse
|
true // local date overlaps an eclipse
|
||||||
total // local eclipse type
|
total // local eclipse type
|
||||||
2029-01-01 00:52:05.257715537 +0800 CST // greatest eclipse in UTC+8
|
2029-01-01 00:52:05.566135346 +0800 CST // greatest eclipse in UTC+8
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Checks against NASA data
|
#### Checks against NASA data
|
||||||
@@ -1153,20 +1153,20 @@ func main() {
|
|||||||
Output:
|
Output:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
2019-11-11 23:21:39.702344834 +0800 CST // previous inferior conjunction of Mercury
|
2019-11-11 23:21:42.048057317 +0800 CST // previous inferior conjunction of Mercury
|
||||||
2021-03-26 14:57:38.289429545 +0800 CST // next superior conjunction of Venus
|
2021-03-26 14:57:43.01215589 +0800 CST // next superior conjunction of Venus
|
||||||
2019-11-01 04:31:47.807287573 +0800 CST // previous Mercury station from prograde to retrograde
|
2019-11-01 04:31:38.999851942 +0800 CST // previous Mercury station from prograde to retrograde
|
||||||
2021-12-18 18:59:12.762369811 +0800 CST // next Venus station from retrograde to prograde
|
2020-06-25 02:07:41.549940705 +0800 CST // next Venus station from retrograde to prograde
|
||||||
2019-10-20 11:59:33.893027007 +0800 CST // previous greatest eastern elongation of Mercury
|
2019-10-20 11:50:28.734245896 +0800 CST // previous greatest eastern elongation of Mercury
|
||||||
2020-08-13 07:56:02.326616048 +0800 CST // next greatest western elongation of Venus
|
2020-08-13 07:59:17.123789191 +0800 CST // next greatest western elongation of Venus
|
||||||
2020-01-01 10:01:10.821288228 +0800 CST <nil> // Venus rise time in Xi'an; no error
|
2020-01-01 10:02:34.172194004 +0800 CST <nil> // Venus rise time in Xi'an; no error
|
||||||
2020-01-01 20:27:00.741534233 +0800 CST <nil> // Venus set time in Xi'an; no error
|
2020-01-01 20:25:37.363712489 +0800 CST <nil> // Venus set time in Xi'an; no error
|
||||||
-4 // Venus apparent magnitude
|
-4 // Venus apparent magnitude
|
||||||
49.98145049145023 // Venus phase angle, degrees
|
49.98145049145023 // Venus phase angle, degrees
|
||||||
0.8215177914415865 // illuminated fraction of Venus
|
0.8215177914415865 // illuminated fraction of Venus
|
||||||
255.63802111818407 // bright-limb position angle of Venus, degrees
|
255.63802093000768 // bright-limb position angle of Venus, degrees
|
||||||
1.2760033106813273 // Earth-Venus distance, AU
|
1.2778819631550336 // Earth-Venus distance, AU
|
||||||
0.7262288470390035 // Sun-Venus distance, AU
|
0.7262651056423838 // Sun-Venus distance, AU
|
||||||
```
|
```
|
||||||
|
|
||||||
Inner and outer planets also expose `Diameter` / `Semidiameter` and `N` variants, returning geocentric apparent diameter/semidiameter in arcseconds.
|
Inner and outer planets also expose `Diameter` / `Semidiameter` and `N` variants, returning geocentric apparent diameter/semidiameter in arcseconds.
|
||||||
@@ -1191,6 +1191,67 @@ For `date := 2020-01-01 08:08:08 CST`, the output is:
|
|||||||
76.86008484515058 256.8600848451506 // Venus ascending-node and descending-node longitudes, degrees
|
76.86008484515058 256.8600848451506 // Venus ascending-node and descending-node longitudes, degrees
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Mercury and Venus also expose `NextTransit` / `LastTransit` / `ClosestTransit` for geocentric planetary transits. "Geocentric" means the planet disk crosses the solar disk as seen from Earth's center; it does not test whether the Sun is above the horizon at a particular observing site. For observing plans, combine this with local solar altitude and weather.
|
||||||
|
|
||||||
|
```go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"b612.me/astro/mercury"
|
||||||
|
"b612.me/astro/venus"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
// Next geocentric Mercury transit after the beginning of 2019.
|
||||||
|
mercuryTransit := mercury.NextTransit(time.Date(2019, 1, 1, 0, 0, 0, 0, time.UTC))
|
||||||
|
fmt.Println(mercuryTransit.Valid)
|
||||||
|
fmt.Println(mercuryTransit.Start)
|
||||||
|
fmt.Println(mercuryTransit.InternalStart)
|
||||||
|
fmt.Println(mercuryTransit.Greatest)
|
||||||
|
fmt.Println(mercuryTransit.InternalEnd)
|
||||||
|
fmt.Println(mercuryTransit.End)
|
||||||
|
fmt.Println(mercuryTransit.Duration)
|
||||||
|
fmt.Println(mercuryTransit.MinimumSeparationArcsec)
|
||||||
|
fmt.Println(mercuryTransit.SunSemidiameterArcsec)
|
||||||
|
fmt.Println(mercuryTransit.PlanetSemidiameterArcsec)
|
||||||
|
|
||||||
|
// Next geocentric Venus transit after the beginning of 2012.
|
||||||
|
venusTransit := venus.NextTransit(time.Date(2012, 1, 1, 0, 0, 0, 0, time.UTC))
|
||||||
|
fmt.Println(venusTransit.Valid)
|
||||||
|
fmt.Println(venusTransit.Start)
|
||||||
|
fmt.Println(venusTransit.InternalStart)
|
||||||
|
fmt.Println(venusTransit.Greatest)
|
||||||
|
fmt.Println(venusTransit.InternalEnd)
|
||||||
|
fmt.Println(venusTransit.End)
|
||||||
|
fmt.Println(venusTransit.Duration)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Output:
|
||||||
|
|
||||||
|
```text
|
||||||
|
true // a valid geocentric Mercury transit was found
|
||||||
|
2019-11-11 12:35:31.617325544 +0000 UTC // first contact: Mercury externally enters the solar disk
|
||||||
|
2019-11-11 12:37:13.078211545 +0000 UTC // second contact: Mercury is fully inside the solar disk
|
||||||
|
2019-11-11 15:19:48.410291075 +0000 UTC // greatest transit: Mercury center is closest to the Sun center
|
||||||
|
2019-11-11 18:02:29.2267102 +0000 UTC // third contact: Mercury starts leaving the solar disk
|
||||||
|
2019-11-11 18:04:10.687676668 +0000 UTC // fourth contact: Mercury externally leaves the solar disk
|
||||||
|
5h28m39.070351124s // geocentric transit duration from first to fourth contact
|
||||||
|
75.92506897631685 // minimum Mercury-Sun center separation at greatest transit, arcseconds
|
||||||
|
968.8881520858397 // solar semidiameter at greatest transit, arcseconds
|
||||||
|
4.978442860728242 // Mercury semidiameter at greatest transit, arcseconds
|
||||||
|
true // a valid geocentric Venus transit was found
|
||||||
|
2012-06-05 22:09:47.514281272 +0000 UTC // first contact: Venus externally enters the solar disk
|
||||||
|
2012-06-05 22:27:35.701768398 +0000 UTC // second contact: Venus is fully inside the solar disk
|
||||||
|
2012-06-06 01:29:35.408823788 +0000 UTC // greatest transit: Venus center is closest to the Sun center
|
||||||
|
2012-06-06 04:31:34.90493685 +0000 UTC // third contact: Venus starts leaving the solar disk
|
||||||
|
2012-06-06 04:49:23.303366303 +0000 UTC // fourth contact: Venus externally leaves the solar disk
|
||||||
|
6h39m35.789085031s // geocentric transit duration from first to fourth contact
|
||||||
|
```
|
||||||
|
|
||||||
#### Outer planets
|
#### Outer planets
|
||||||
|
|
||||||
```go
|
```go
|
||||||
@@ -1251,18 +1312,18 @@ func main() {
|
|||||||
Output:
|
Output:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
2020-10-14 07:25:47.740884125 +0800 CST // next opposition of Mars
|
2020-10-14 07:25:50.262777507 +0800 CST // next opposition of Mars
|
||||||
2021-01-29 09:39:30.916356146 +0800 CST // next conjunction of Jupiter
|
2021-01-29 09:39:33.565426468 +0800 CST // next conjunction of Jupiter
|
||||||
2019-04-30 10:28:27.453395426 +0800 CST // previous Saturn station from prograde to retrograde
|
2019-04-30 10:27:41.606289446 +0800 CST // previous Saturn station from prograde to retrograde
|
||||||
saturn B=23.577026 Bp=23.266930 P=6.629811 dU=1.171017 major=34.133852 minor=13.652911 // Saturn ring B, B', P, dU, major axis, minor axis
|
saturn B=23.577026 Bp=23.266930 P=6.629811 dU=1.171016 major=34.133852 minor=13.652911 // Saturn ring B, B', P, dU, major axis, minor axis
|
||||||
2021-01-14 21:35:01.269377768 +0800 CST // next Uranus station from retrograde to prograde
|
2020-01-11 15:23:07.378419935 +0800 CST // next Uranus station from retrograde to prograde
|
||||||
2019-12-08 17:00:13.772284984 +0800 CST // previous eastern quadrature of Neptune
|
2019-12-08 17:00:15.328663587 +0800 CST // previous eastern quadrature of Neptune
|
||||||
2020-06-07 03:10:57.179121673 +0800 CST // next western quadrature of Mars
|
2020-06-07 03:10:59.356176853 +0800 CST // next western quadrature of Mars
|
||||||
2020-01-01 04:40:05.409269034 +0800 CST <nil> // Mars rise time in Xi'an; no error
|
2020-01-01 04:41:29.622089266 +0800 CST <nil> // Mars rise time in Xi'an; no error
|
||||||
2020-01-01 14:56:57.175483703 +0800 CST <nil> // Mars set time in Xi'an; no error
|
2020-01-01 14:55:32.963870465 +0800 CST <nil> // Mars set time in Xi'an; no error
|
||||||
1.57 // Mars apparent magnitude
|
1.57 // Mars apparent magnitude
|
||||||
2.1820316323604088 // Earth-Mars distance, AU
|
2.1844284956325937 // Earth-Mars distance, AU
|
||||||
1.5894169865107062 // Sun-Mars distance, AU
|
1.5897860004265403 // Sun-Mars distance, AU
|
||||||
```
|
```
|
||||||
|
|
||||||
`saturn.Ring` returns `RingInfo`: `EarthLatitude` is ring opening angle B, `SunLatitude` is B', `PositionAngle` is the position angle of the northern semiminor axis, `DeltaU` is the Saturnicentric longitude difference between the Sun and Earth in the ring plane, and `MajorAxis` / `MinorAxis` are the apparent outer major/minor axes in arcseconds.
|
`saturn.Ring` returns `RingInfo`: `EarthLatitude` is ring opening angle B, `SunLatitude` is B', `PositionAngle` is the position angle of the northern semiminor axis, `DeltaU` is the Saturnicentric longitude difference between the Sun and Earth in the ring plane, and `MajorAxis` / `MinorAxis` are the apparent outer major/minor axes in arcseconds.
|
||||||
@@ -1312,7 +1373,7 @@ Output:
|
|||||||
|
|
||||||
```text
|
```text
|
||||||
jupiter DS=54.342153 DE=1.436485 CMI=292.712909 CMII=276.309048 CMIII=147.241811 // Jupiter DS/DE and System I/II/III central meridians, degrees
|
jupiter DS=54.342153 DE=1.436485 CMI=292.712909 CMII=276.309048 CMIII=147.241811 // Jupiter DS/DE and System I/II/III central meridians, degrees
|
||||||
saturn B=-0.608048 Bp=-2.675677 P=4.480276 major=42.709920 minor=0.453248 // Saturn ring B, B', minor-axis position angle, outer major/minor axes
|
saturn B=-0.608046 Bp=-2.675677 P=4.480276 major=42.709920 minor=0.453246 // Saturn ring B, B', minor-axis position angle, outer major/minor axes
|
||||||
```
|
```
|
||||||
|
|
||||||
If only Jupiter central meridians are needed:
|
If only Jupiter central meridians are needed:
|
||||||
@@ -1475,8 +1536,8 @@ Output:
|
|||||||
2019-12-31 19:22:56.176710426 +0800 CST // rise time of Sirius
|
2019-12-31 19:22:56.176710426 +0800 CST // rise time of Sirius
|
||||||
2020-01-01 05:30:39.834894239 +0800 CST // set time of Sirius
|
2020-01-01 05:30:39.834894239 +0800 CST // set time of Sirius
|
||||||
Canis Major // English constellation containing Sirius
|
Canis Major // English constellation containing Sirius
|
||||||
5h58m10.19s // right ascension of Vega in year 13600
|
5h58m5.71s // right ascension of Vega in year 13600
|
||||||
84°19′26.25″ // declination of Vega in year 13600
|
84°19′26.13″ // declination of Vega in year 13600
|
||||||
天狼 Sirius -1.46 // first brightest-star entry: Chinese name, common English name, apparent magnitude
|
天狼 Sirius -1.46 // first brightest-star entry: Chinese name, common English name, apparent magnitude
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -1767,7 +1828,7 @@ Notes:
|
|||||||
- `lite/sun` and `lite/moon` lightweight Sun/Moon chains for minute-level rise/set, lightweight position, and lunar-phase work
|
- `lite/sun` and `lite/moon` lightweight Sun/Moon chains for minute-level rise/set, lightweight position, and lunar-phase work
|
||||||
- Earth eccentricity, Sun-Earth distance, perihelion, aphelion
|
- Earth eccentricity, Sun-Earth distance, perihelion, aphelion
|
||||||
- Apparent/mean sidereal time, constellation lookup, common coordinate transforms, refraction, airmass, parallactic angle, Galactic coordinates
|
- Apparent/mean sidereal time, constellation lookup, common coordinate transforms, refraction, airmass, parallactic angle, Galactic coordinates
|
||||||
- Seven major-planet coordinates, Sun/body and Earth/body distances, special events, physical ephemerides, apparent diameters, phases, parallactic angles, and nodes
|
- Seven major-planet coordinates, Sun/body and Earth/body distances, special events, Mercury/Venus geocentric transits, physical ephemerides, apparent diameters, phases, parallactic angles, and nodes
|
||||||
- Chinese lunisolar calendar conversion from 104 BCE to 3000 CE
|
- Chinese lunisolar calendar conversion from 104 BCE to 3000 CE
|
||||||
- 9100+ star catalog
|
- 9100+ star catalog
|
||||||
- Generic small-body orbit propagation, H-G apparent magnitude, visual-binary position angle and separation
|
- Generic small-body orbit propagation, H-G apparent magnitude, visual-binary position angle and separation
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ go get b612.me/astro
|
|||||||
- 🌙 **月亮计算**:天球位置、月出月落、地月距离、月相、朔望时间、视直径、亮边位置角、视差角、地心/站心天平动、近远地点、交点、最大赤纬等
|
- 🌙 **月亮计算**:天球位置、月出月落、地月距离、月相、朔望时间、视直径、亮边位置角、视差角、地心/站心天平动、近远地点、交点、最大赤纬等
|
||||||
- 🪶 **轻量链路**:`lite/sun` 与 `lite/moon` 提供面向手表、前端、小程序和其它资源受限环境的轻量近似太阳/月亮算法,覆盖天球位置、升落和月相
|
- 🪶 **轻量链路**:`lite/sun` 与 `lite/moon` 提供面向手表、前端、小程序和其它资源受限环境的轻量近似太阳/月亮算法,覆盖天球位置、升落和月相
|
||||||
- 🌗 **日月食**:全局日食、站心日食、中心线/偏食足迹、月食、地方可见月食与 SVG 示意图
|
- 🌗 **日月食**:全局日食、站心日食、中心线/偏食足迹、月食、地方可见月食与 SVG 示意图
|
||||||
- 🪐 **行星计算**:七大行星天球位置、升落时间、合冲留等特殊天象时间、升交点/降交点、视直径/视半径、相位、视差角、节点、视星等与物理星历
|
- 🪐 **行星计算**:七大行星天球位置、升落时间、合冲留、大距、水星/金星地心凌日等特殊天象时间、升交点/降交点、视直径/视半径、相位、视差角、节点、视星等与物理星历
|
||||||
- ⭐ **恒星计算**:指定天球坐标所属星座;同时包含9100颗恒星数据库,可计算升降时间、视差角和视高度角,获取指定日期的恒星坐标信息
|
- ⭐ **恒星计算**:指定天球坐标所属星座;同时包含9100颗恒星数据库,可计算升降时间、视差角和视高度角,获取指定日期的恒星坐标信息
|
||||||
- 🧭 **坐标工具**:黄道/赤道/地平坐标转换、站心坐标、恒星时、岁差、章动、角距离、大气折射、大气质量、视差角、银道坐标
|
- 🧭 **坐标工具**:黄道/赤道/地平坐标转换、站心坐标、恒星时、岁差、章动、角距离、大气折射、大气质量、视差角、银道坐标
|
||||||
- 🔭 **研究公式**:黑体辐射、会合周期、星等距离换算、望远镜极限星等、恒星半径/温度/光度换算、大气质量模型
|
- 🔭 **研究公式**:黑体辐射、会合周期、星等距离换算、望远镜极限星等、恒星半径/温度/光度换算、大气质量模型
|
||||||
@@ -61,7 +61,7 @@ go get b612.me/astro
|
|||||||
| `moon` | 月亮位置、月出月落、月相、朔望弦、视高度角、视差角、视直径、亮边位置角、地心/站心天平动、近远地点、交点、最大赤纬 |
|
| `moon` | 月亮位置、月出月落、月相、朔望弦、视高度角、视差角、视直径、亮边位置角、地心/站心天平动、近远地点、交点、最大赤纬 |
|
||||||
| `lite/sun` / `lite/moon` | 轻量太阳/月亮近似链路,面向分钟级升落、轻量天球位置和月相计算 |
|
| `lite/sun` / `lite/moon` | 轻量太阳/月亮近似链路,面向分钟级升落、轻量天球位置和月相计算 |
|
||||||
| `eclipse` / `eclipse/svg` | 全局/局地日月食、日食中心线与偏食足迹、局地可见性筛选、日月食 SVG |
|
| `eclipse` / `eclipse/svg` | 全局/局地日月食、日食中心线与偏食足迹、局地可见性筛选、日月食 SVG |
|
||||||
| `mercury` / `venus` | 水星、金星位置、升落、合日、留、大距、相位、视差角、视星等、视直径、节点和物理星历 |
|
| `mercury` / `venus` | 水星、金星位置、升落、合日、留、大距、地心凌日、相位、视差角、视星等、视直径、节点和物理星历 |
|
||||||
| `mars` / `jupiter` / `saturn` / `uranus` / `neptune` | 外行星位置、升落、合冲、留、方照、相位、视差角、视星等、视直径、节点和物理星历 |
|
| `mars` / `jupiter` / `saturn` / `uranus` / `neptune` | 外行星位置、升落、合冲、留、方照、相位、视差角、视星等、视直径、节点和物理星历 |
|
||||||
| `earth` | 地球轨道偏心率、近日点、远日点 |
|
| `earth` | 地球轨道偏心率、近日点、远日点 |
|
||||||
| `star` | 星座判定、恒星数据库、恒星自行/岁差/章动修正、恒星升落、视差角、视高度角 |
|
| `star` | 星座判定、恒星数据库、恒星自行/岁差/章动修正、恒星升落、视差角、视高度角 |
|
||||||
@@ -309,7 +309,6 @@ func main() {
|
|||||||
```text
|
```text
|
||||||
// 同一公历时刻在三国并立时期会映射到多个政权各自的农历结果
|
// 同一公历时刻在三国并立时期会映射到多个政权各自的农历结果
|
||||||
[魏明帝 景初三年腊月二十 蜀后主 延熙二年冬月十九 吴大帝 赤乌二年冬月二十]
|
[魏明帝 景初三年腊月二十 蜀后主 延熙二年冬月十九 吴大帝 赤乌二年冬月二十]
|
||||||
|
|
||||||
// 结构化农历信息输出;每个对象对应一个政权口径下的结果
|
// 结构化农历信息输出;每个对象对应一个政权口径下的结果
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
@@ -370,14 +369,12 @@ func main() {
|
|||||||
"chineseZodiac": "羊"
|
"chineseZodiac": "羊"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
// “元丰六年十月十二日”对应的公历日期
|
// “元丰六年十月十二日”对应的公历日期
|
||||||
1083-11-24 00:00:00 +0800 CST
|
1083-11-24 00:00:00 +0800 CST
|
||||||
// 同一天在并行政权下还会命中辽道宗大康九年十月十二
|
// 同一天在并行政权下还会命中辽道宗大康九年十月十二
|
||||||
[宋神宗 元丰六年十月十二 辽道宗 大康九年十月十二]
|
[宋神宗 元丰六年十月十二 辽道宗 大康九年十月十二]
|
||||||
|
|
||||||
// 现代农历日期转换结果;2026 年正月初一对应 2026-02-17
|
// 现代农历日期转换结果;2026 年正月初一对应 2026-02-17
|
||||||
2026-02-17 00:00:00 +0800 CST //2026年春节
|
2026-02-17 00:00:00 +0800 CST
|
||||||
```
|
```
|
||||||
|
|
||||||
#### 节气
|
#### 节气
|
||||||
@@ -407,10 +404,10 @@ func main() {
|
|||||||
输出结果
|
输出结果
|
||||||
|
|
||||||
```
|
```
|
||||||
2020-02-04 17:03:17.820854187 +0800 CST
|
2020-02-04 17:03:20.471614301 +0800 CST
|
||||||
2020-12-21 18:02:17.568823993 +0800 CST
|
2020-12-21 18:02:20.648710727 +0800 CST
|
||||||
2020-03-20 11:49:34.502393603 +0800 CST
|
2020-03-20 11:49:37.149532735 +0800 CST
|
||||||
2020-03-20 11:49:34.502393603 +0800 CST
|
2020-03-20 11:49:37.149532735 +0800 CST
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -474,14 +471,14 @@ func main() {
|
|||||||
输出结果
|
输出结果
|
||||||
|
|
||||||
```
|
```
|
||||||
2020-01-01 07:22:27.964431345 +0800 CST <nil>
|
2020-01-01 07:22:27.960488498 +0800 CST <nil>
|
||||||
2020-01-01 07:50:14.534510672 +0800 CST <nil>
|
2020-01-01 07:50:14.530648291 +0800 CST <nil>
|
||||||
2020-01-01 12:47:35.933117866 +0800 CST
|
2020-01-01 12:47:35.933117866 +0800 CST
|
||||||
2020-01-01 17:44:47.076647579 +0800 CST <nil>
|
2020-01-01 17:44:47.070974707 +0800 CST <nil>
|
||||||
2020-01-01 18:12:33.629668056 +0800 CST <nil>
|
2020-01-01 18:12:33.624035418 +0800 CST <nil>
|
||||||
2020-01-01 11:52:44.643359184 +0800 CST <nil>
|
2020-01-01 11:52:45.157297253 +0800 CST <nil>
|
||||||
2020-01-01 17:38:03.879639208 +0800 CST
|
2020-01-01 17:38:02.510787248 +0800 CST
|
||||||
2020-01-01 23:26:52.202896177 +0800 CST <nil>
|
2020-01-01 23:26:51.580328643 +0800 CST <nil>
|
||||||
|
|
||||||
|
|
||||||
```
|
```
|
||||||
@@ -536,17 +533,16 @@ func main() {
|
|||||||
输出结果:
|
输出结果:
|
||||||
|
|
||||||
```
|
```
|
||||||
280.0152925179703
|
280.01526210031136
|
||||||
23.436215552851408
|
23.4362178391013
|
||||||
赤经: 18h43m34.83s 赤纬: -23°3′30.25″
|
赤经: 18h43m34.82s 赤纬: -23°3′30.27″
|
||||||
人马座
|
人马座
|
||||||
方位角: 120.19483856399326 高度角: 2.4014324584398516 天顶距: 87.59856754156014
|
方位角: 120.19477090015224 高度角: 2.4014437419430097 天顶距: 87.59855625805699
|
||||||
0.9832929365443133
|
0.983292937163176
|
||||||
|
赤经: 23h17m53.15s 赤纬: -10°19′18.57″
|
||||||
赤经: 23h17m51.93s 赤纬: -10°19′17.02″
|
|
||||||
宝瓶座
|
宝瓶座
|
||||||
方位角: 67.84449893794012 高度角: -45.13018696439911 天顶距: 135.13018696439912
|
方位角: 67.84050700509859 高度角: -45.13425530765482 天顶距: 135.13425530765483
|
||||||
404238.6354387698
|
404238.6096080479
|
||||||
```
|
```
|
||||||
|
|
||||||
太阳还提供 `sun.Physical` / `sun.PhysicalN`,返回:
|
太阳还提供 `sun.Physical` / `sun.PhysicalN`,返回:
|
||||||
@@ -683,12 +679,12 @@ func main() {
|
|||||||
输出结果:
|
输出结果:
|
||||||
|
|
||||||
```
|
```
|
||||||
0.3000437415436273 // 月面约有 30% 被太阳照亮
|
0.300041309608744 // 月面约有 30% 被太阳照亮
|
||||||
上峨眉月 // 当前月相描述
|
上峨眉月 // 当前月相描述
|
||||||
2020-01-25 05:41:55.820311009 +0800 CST // 下一次朔月
|
2020-01-25 05:41:58.271192908 +0800 CST // 下一次朔月
|
||||||
2020-01-03 12:45:20.809730887 +0800 CST // 下一次上弦
|
2020-01-03 12:45:23.229190707 +0800 CST // 下一次上弦
|
||||||
2020-01-11 03:21:14.729664623 +0800 CST // 下一次望月,也就是满月
|
2020-01-11 03:21:17.159625291 +0800 CST // 下一次望月,也就是满月
|
||||||
2020-01-17 20:58:20.955985486 +0800 CST // 下一次下弦
|
2020-01-17 20:58:23.396406769 +0800 CST // 下一次下弦
|
||||||
```
|
```
|
||||||
|
|
||||||
月相四个相位同时提供拼音名和英文 alias,例如:
|
月相四个相位同时提供拼音名和英文 alias,例如:
|
||||||
@@ -742,6 +738,8 @@ func main() {
|
|||||||
- `LastSolarEclipse` / `NextSolarEclipse` / `ClosestSolarEclipse`:搜索全局日食
|
- `LastSolarEclipse` / `NextSolarEclipse` / `ClosestSolarEclipse`:搜索全局日食
|
||||||
- `LocalSolarEclipseOnDate`:判断某地当天是否能看到站心日食
|
- `LocalSolarEclipseOnDate`:判断某地当天是否能看到站心日食
|
||||||
- `LastLocalSolarEclipse` / `NextLocalSolarEclipse` / `ClosestLocalSolarEclipse`:搜索某地可见的站心日食
|
- `LastLocalSolarEclipse` / `NextLocalSolarEclipse` / `ClosestLocalSolarEclipse`:搜索某地可见的站心日食
|
||||||
|
- `LastLocalTotalSolarEclipse` / `NextLocalTotalSolarEclipse` / `ClosestLocalTotalSolarEclipse`:搜索某地可见的日全食,返回 `(info, ok)`
|
||||||
|
- `LastLocalAnnularSolarEclipse` / `NextLocalAnnularSolarEclipse` / `ClosestLocalAnnularSolarEclipse`:搜索某地可见的日环食,返回 `(info, ok)`
|
||||||
- `SolarEclipseCentralPath`:计算中心线、南北界和食甚点
|
- `SolarEclipseCentralPath`:计算中心线、南北界和食甚点
|
||||||
- `SolarEclipsePartialFootprints`:计算偏食半影在地球表面的足迹
|
- `SolarEclipsePartialFootprints`:计算偏食半影在地球表面的足迹
|
||||||
- `eclipse/svg.LocalSolarEclipseSVG`:生成某地的日面视圆 SVG
|
- `eclipse/svg.LocalSolarEclipseSVG`:生成某地的日面视圆 SVG
|
||||||
@@ -995,6 +993,7 @@ true 13424 // 北京日全食 SVG 生成成功,长度 13424 字节
|
|||||||
- `LastLunarEclipse` / `NextLunarEclipse` / `ClosestLunarEclipse`:搜索全局月食
|
- `LastLunarEclipse` / `NextLunarEclipse` / `ClosestLunarEclipse`:搜索全局月食
|
||||||
- `LocalLunarEclipseOnDate`:判断某地当天是否能看到可见月食
|
- `LocalLunarEclipseOnDate`:判断某地当天是否能看到可见月食
|
||||||
- `LastLocalLunarEclipse` / `NextLocalLunarEclipse` / `ClosestLocalLunarEclipse`:搜索某地可见月食
|
- `LastLocalLunarEclipse` / `NextLocalLunarEclipse` / `ClosestLocalLunarEclipse`:搜索某地可见月食
|
||||||
|
- `LastLocalTotalLunarEclipse` / `NextLocalTotalLunarEclipse` / `ClosestLocalTotalLunarEclipse`:搜索某地可见月全食,返回 `(info, ok)`
|
||||||
- `GeometricLocalLunarEclipseOnDate`:判断某地当天是否发生几何月食,不做“月亮在地平线上方”的可见性过滤
|
- `GeometricLocalLunarEclipseOnDate`:判断某地当天是否发生几何月食,不做“月亮在地平线上方”的可见性过滤
|
||||||
- `eclipse/svg.LunarEclipseSVG`:生成月食穿影图 SVG
|
- `eclipse/svg.LunarEclipseSVG`:生成月食穿影图 SVG
|
||||||
|
|
||||||
@@ -1071,18 +1070,18 @@ func main() {
|
|||||||
```text
|
```text
|
||||||
total
|
total
|
||||||
true {125 49 72}
|
true {125 49 72}
|
||||||
2028-12-31 16:52:05.257715537 +0000 UTC
|
2028-12-31 16:52:05.566135346 +0000 UTC
|
||||||
2.273989043 1.246114288
|
2.273989043382249 1.2461142882946992
|
||||||
2028-12-31 14:03:54.163612125 +0000 UTC
|
2028-12-31 14:03:54.219463169 +0000 UTC
|
||||||
2028-12-31 15:07:42.293254197 +0000 UTC
|
2028-12-31 15:07:42.115980684 +0000 UTC
|
||||||
2028-12-31 16:16:27.717077732 +0000 UTC
|
2028-12-31 16:16:27.24464178 +0000 UTC
|
||||||
2028-12-31 17:27:46.687390804 +0000 UTC
|
2028-12-31 17:27:46.214954853 +0000 UTC
|
||||||
2028-12-31 18:36:32.272528112 +0000 UTC
|
2028-12-31 18:36:32.251235246 +0000 UTC
|
||||||
2028-12-31 19:40:11.173523784 +0000 UTC
|
2028-12-31 19:40:11.52023971 +0000 UTC
|
||||||
2.29960334 1.25117109
|
2.2996033397593934 1.2511710895700923
|
||||||
true
|
true
|
||||||
total
|
total
|
||||||
2029-01-01 00:52:05.257715537 +0800 CST
|
2029-01-01 00:52:05.566135346 +0800 CST
|
||||||
```
|
```
|
||||||
|
|
||||||
##### 与 NASA 数据对照
|
##### 与 NASA 数据对照
|
||||||
@@ -1229,20 +1228,20 @@ func main() {
|
|||||||
输出结果:
|
输出结果:
|
||||||
|
|
||||||
```
|
```
|
||||||
2019-11-11 23:21:39.702344834 +0800 CST // 水星上次下合
|
2019-11-11 23:21:42.048057317 +0800 CST // 水星上次下合
|
||||||
2021-03-26 14:57:38.289429545 +0800 CST // 金星下次上合
|
2021-03-26 14:57:43.01215589 +0800 CST // 金星下次上合
|
||||||
2019-11-01 04:31:47.807287573 +0800 CST // 水星上次由顺行转逆行的留
|
2019-11-01 04:31:38.999851942 +0800 CST // 水星上次由顺行转逆行的留
|
||||||
2021-12-18 18:59:12.762369811 +0800 CST // 金星下次由逆行转顺行的留
|
2020-06-25 02:07:41.549940705 +0800 CST // 金星下次由逆行转顺行的留
|
||||||
2019-10-20 11:59:33.893027007 +0800 CST // 水星上次东大距
|
2019-10-20 11:50:28.734245896 +0800 CST // 水星上次东大距
|
||||||
2020-08-13 07:56:02.326616048 +0800 CST // 金星下次西大距
|
2020-08-13 07:59:17.123789191 +0800 CST // 金星下次西大距
|
||||||
2020-01-01 10:01:10.821288228 +0800 CST <nil> // 西安当天金星升起时刻;无错误
|
2020-01-01 10:02:34.172194004 +0800 CST <nil> // 西安当天金星升起时刻;无错误
|
||||||
2020-01-01 20:27:00.741534233 +0800 CST <nil> // 西安当天金星落下时刻;无错误
|
2020-01-01 20:25:37.363712489 +0800 CST <nil> // 西安当天金星落下时刻;无错误
|
||||||
-4 // 金星视星等
|
-4 // 金星视星等
|
||||||
49.98145049145023 // 金星相位角,单位度
|
49.98145049145023 // 金星相位角,单位度
|
||||||
0.8215177914415865 // 金星被照亮比例
|
0.8215177914415865 // 金星被照亮比例
|
||||||
255.63802111818407 // 金星亮面中心位置角,单位度
|
255.63802093000768 // 金星亮面中心位置角,单位度
|
||||||
1.2760033106813273 // 金地距离,单位 AU
|
1.2778819631550336 // 金地距离,单位 AU
|
||||||
0.7262288470390035 // 金日距离,单位 AU
|
0.7262651056423838 // 金日距离,单位 AU
|
||||||
```
|
```
|
||||||
|
|
||||||
内外行星同样提供 `Diameter` / `Semidiameter`(以及 `N` 版),返回地心视直径/视半径,单位为角秒。
|
内外行星同样提供 `Diameter` / `Semidiameter`(以及 `N` 版),返回地心视直径/视半径,单位为角秒。
|
||||||
@@ -1267,6 +1266,67 @@ fmt.Println(venus.AscendingNode(date), venus.DescendingNode(date))
|
|||||||
76.86008484515058 256.8600848451506 // 金星升交点、降交点黄经,单位度
|
76.86008484515058 256.8600848451506 // 金星升交点、降交点黄经,单位度
|
||||||
```
|
```
|
||||||
|
|
||||||
|
水星和金星还提供 `NextTransit` / `LastTransit` / `ClosestTransit` 地心凌日查询。这里的“地心”指从地球中心看到的行星圆面经过太阳圆面,不判断某个地点当时太阳是否在地平线上;如果要做观测计划,还需要结合本地太阳高度角和天气条件。
|
||||||
|
|
||||||
|
```go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"b612.me/astro/mercury"
|
||||||
|
"b612.me/astro/venus"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
// 查询 2019 年之后下一次地心水星凌日。
|
||||||
|
mercuryTransit := mercury.NextTransit(time.Date(2019, 1, 1, 0, 0, 0, 0, time.UTC))
|
||||||
|
fmt.Println(mercuryTransit.Valid)
|
||||||
|
fmt.Println(mercuryTransit.Start)
|
||||||
|
fmt.Println(mercuryTransit.InternalStart)
|
||||||
|
fmt.Println(mercuryTransit.Greatest)
|
||||||
|
fmt.Println(mercuryTransit.InternalEnd)
|
||||||
|
fmt.Println(mercuryTransit.End)
|
||||||
|
fmt.Println(mercuryTransit.Duration)
|
||||||
|
fmt.Println(mercuryTransit.MinimumSeparationArcsec)
|
||||||
|
fmt.Println(mercuryTransit.SunSemidiameterArcsec)
|
||||||
|
fmt.Println(mercuryTransit.PlanetSemidiameterArcsec)
|
||||||
|
|
||||||
|
// 查询 2012 年之后下一次地心金星凌日。
|
||||||
|
venusTransit := venus.NextTransit(time.Date(2012, 1, 1, 0, 0, 0, 0, time.UTC))
|
||||||
|
fmt.Println(venusTransit.Valid)
|
||||||
|
fmt.Println(venusTransit.Start)
|
||||||
|
fmt.Println(venusTransit.InternalStart)
|
||||||
|
fmt.Println(venusTransit.Greatest)
|
||||||
|
fmt.Println(venusTransit.InternalEnd)
|
||||||
|
fmt.Println(venusTransit.End)
|
||||||
|
fmt.Println(venusTransit.Duration)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
输出结果:
|
||||||
|
|
||||||
|
```text
|
||||||
|
true // 找到一次有效的地心水星凌日
|
||||||
|
2019-11-11 12:35:31.617325544 +0000 UTC // 一触:水星外切进入太阳圆面
|
||||||
|
2019-11-11 12:37:13.078211545 +0000 UTC // 二触:水星完全进入太阳圆面
|
||||||
|
2019-11-11 15:19:48.410291075 +0000 UTC // 凌甚:水星中心最接近太阳中心
|
||||||
|
2019-11-11 18:02:29.2267102 +0000 UTC // 三触:水星开始离开太阳圆面
|
||||||
|
2019-11-11 18:04:10.687676668 +0000 UTC // 四触:水星外切离开太阳圆面
|
||||||
|
5h28m39.070351124s // 一触到四触的地心凌日持续时间
|
||||||
|
75.92506897631685 // 凌甚时水星中心与太阳中心的最小角距离,单位角秒
|
||||||
|
968.8881520858397 // 凌甚时太阳视半径,单位角秒
|
||||||
|
4.978442860728242 // 凌甚时水星视半径,单位角秒
|
||||||
|
true // 找到一次有效的地心金星凌日
|
||||||
|
2012-06-05 22:09:47.514281272 +0000 UTC // 一触:金星外切进入太阳圆面
|
||||||
|
2012-06-05 22:27:35.701768398 +0000 UTC // 二触:金星完全进入太阳圆面
|
||||||
|
2012-06-06 01:29:35.408823788 +0000 UTC // 凌甚:金星中心最接近太阳中心
|
||||||
|
2012-06-06 04:31:34.90493685 +0000 UTC // 三触:金星开始离开太阳圆面
|
||||||
|
2012-06-06 04:49:23.303366303 +0000 UTC // 四触:金星外切离开太阳圆面
|
||||||
|
6h39m35.789085031s // 一触到四触的地心凌日持续时间
|
||||||
|
```
|
||||||
|
|
||||||
#### 外行星
|
#### 外行星
|
||||||
|
|
||||||
```go
|
```go
|
||||||
@@ -1326,18 +1386,18 @@ func main() {
|
|||||||
输出结果:
|
输出结果:
|
||||||
|
|
||||||
```
|
```
|
||||||
2020-10-14 07:25:47.740884125 +0800 CST // 火星下次冲日
|
2020-10-14 07:25:50.262777507 +0800 CST // 火星下次冲日
|
||||||
2021-01-29 09:39:30.916356146 +0800 CST // 木星下次合日
|
2021-01-29 09:39:33.565426468 +0800 CST // 木星下次合日
|
||||||
2019-04-30 10:28:27.453395426 +0800 CST // 土星上次由顺行转逆行的留
|
2019-04-30 10:27:41.606289446 +0800 CST // 土星上次由顺行转逆行的留
|
||||||
saturn B=23.577026 Bp=23.266930 P=6.629811 dU=1.171017 major=34.133852 minor=13.652911 // 土星环 B、B'、P、dU、长轴、短轴
|
saturn B=23.577026 Bp=23.266930 P=6.629811 dU=1.171016 major=34.133852 minor=13.652911 // 土星环 B、B'、P、dU、长轴、短轴
|
||||||
2021-01-14 21:35:01.269377768 +0800 CST // 天王星下次由逆行转顺行的留
|
2020-01-11 15:23:07.378419935 +0800 CST // 天王星下次由逆行转顺行的留
|
||||||
2019-12-08 17:00:13.772284984 +0800 CST // 海王星上次东方照
|
2019-12-08 17:00:15.328663587 +0800 CST // 海王星上次东方照
|
||||||
2020-06-07 03:10:57.179121673 +0800 CST // 火星下次西方照
|
2020-06-07 03:10:59.356176853 +0800 CST // 火星下次西方照
|
||||||
2020-01-01 04:40:05.409269034 +0800 CST <nil> // 西安当天火星升起时刻;无错误
|
2020-01-01 04:41:29.622089266 +0800 CST <nil> // 西安当天火星升起时刻;无错误
|
||||||
2020-01-01 14:56:57.175483703 +0800 CST <nil> // 西安当天火星落下时刻;无错误
|
2020-01-01 14:55:32.963870465 +0800 CST <nil> // 西安当天火星落下时刻;无错误
|
||||||
1.57 // 火星视星等
|
1.57 // 火星视星等
|
||||||
2.1820316323604088 // 地火距离,单位 AU
|
2.1844284956325937 // 地火距离,单位 AU
|
||||||
1.5894169865107062 // 日火距离,单位 AU
|
1.5897860004265403 // 日火距离,单位 AU
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -1388,7 +1448,7 @@ func main() {
|
|||||||
|
|
||||||
```text
|
```text
|
||||||
jupiter DS=54.342153 DE=1.436485 CMI=292.712909 CMII=276.309048 CMIII=147.241811 // 木星子日/子地赤纬,System I/II/III 中央经线,单位度
|
jupiter DS=54.342153 DE=1.436485 CMI=292.712909 CMII=276.309048 CMIII=147.241811 // 木星子日/子地赤纬,System I/II/III 中央经线,单位度
|
||||||
saturn B=-0.608048 Bp=-2.675677 P=4.480276 major=42.709920 minor=0.453248 // 土星环 B、B'、短轴位置角、外缘长短轴,角度单位度,长短轴单位角秒
|
saturn B=-0.608046 Bp=-2.675677 P=4.480276 major=42.709920 minor=0.453246 // 土星环 B、B'、短轴位置角、外缘长短轴,角度单位度,长短轴单位角秒
|
||||||
```
|
```
|
||||||
|
|
||||||
只需要中央经线时,可以单独调用 `CentralMeridians`:
|
只需要中央经线时,可以单独调用 `CentralMeridians`:
|
||||||
@@ -1555,8 +1615,8 @@ func main() {
|
|||||||
2019-12-31 19:22:56.176710426 +0800 CST // 天狼星升起时刻
|
2019-12-31 19:22:56.176710426 +0800 CST // 天狼星升起时刻
|
||||||
2020-01-01 05:30:39.834894239 +0800 CST // 天狼星落下时刻
|
2020-01-01 05:30:39.834894239 +0800 CST // 天狼星落下时刻
|
||||||
大犬座 // 天狼星所在星座
|
大犬座 // 天狼星所在星座
|
||||||
5h58m10.19s // 织女一在公元 13600 年的赤经
|
5h58m5.71s // 织女一在公元 13600 年的赤经
|
||||||
84°19′26.25″ // 织女一在公元 13600 年的赤纬
|
84°19′26.13″ // 织女一在公元 13600 年的赤纬
|
||||||
天狼 Sirius -1.46 // 最亮恒星表第一项:中文名、英文常用名、视星等
|
天狼 Sirius -1.46 // 最亮恒星表第一项:中文名、英文常用名、视星等
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -1849,7 +1909,7 @@ func main() {
|
|||||||
- ✅ `lite/sun`、`lite/moon` 轻量太阳/月亮链路:面向分钟级升落、轻量位置和月相计算
|
- ✅ `lite/sun`、`lite/moon` 轻量太阳/月亮链路:面向分钟级升落、轻量位置和月相计算
|
||||||
- ✅ 地球偏心率、日地距离、近日点、远日点
|
- ✅ 地球偏心率、日地距离、近日点、远日点
|
||||||
- ✅ 真平恒星时、星座计算、常用坐标转换、大气折射、大气质量、视差角、银道坐标
|
- ✅ 真平恒星时、星座计算、常用坐标转换、大气折射、大气质量、视差角、银道坐标
|
||||||
- ✅ 七大行星坐标、距日距地距离、特殊天象、物理星历、视直径、相位、视差角与节点
|
- ✅ 七大行星坐标、距日距地距离、特殊天象、水星/金星地心凌日、物理星历、视直径、相位、视差角与节点
|
||||||
- ✅ 公农历转换(公元前104年-公元3000年)
|
- ✅ 公农历转换(公元前104年-公元3000年)
|
||||||
- ✅ 9100+恒星数据库
|
- ✅ 9100+恒星数据库
|
||||||
- ✅ 通用小天体轨道传播、H-G 视星等、视双星位置角/角距
|
- ✅ 通用小天体轨道传播、H-G 视星等、视双星位置角/角距
|
||||||
|
|||||||
@@ -0,0 +1,76 @@
|
|||||||
|
package basic
|
||||||
|
|
||||||
|
import "math"
|
||||||
|
|
||||||
|
const exactEventTolerance = 2.0 / 86400.0
|
||||||
|
|
||||||
|
func sameEventJD(a, b float64) bool {
|
||||||
|
return math.Abs(a-b) <= exactEventTolerance
|
||||||
|
}
|
||||||
|
|
||||||
|
func sameEventUTQueryTT(eventUT, queryTT float64) bool {
|
||||||
|
return math.Abs(eventUTQueryTTDelta(eventUT, queryTT)) <= exactEventTolerance
|
||||||
|
}
|
||||||
|
|
||||||
|
func closestEventUTToQueryTT(queryTT, best float64, candidates ...float64) float64 {
|
||||||
|
bestAbs := math.Abs(eventUTQueryTTDelta(best, queryTT))
|
||||||
|
for _, candidate := range candidates {
|
||||||
|
candidateAbs := math.Abs(eventUTQueryTTDelta(candidate, queryTT))
|
||||||
|
if candidateAbs < bestAbs {
|
||||||
|
best = candidate
|
||||||
|
bestAbs = candidateAbs
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return best
|
||||||
|
}
|
||||||
|
|
||||||
|
type phaseEventSearchFunc func(jde, degree float64, next uint8) float64
|
||||||
|
type simpleEventSearchFunc func(jde float64) float64
|
||||||
|
|
||||||
|
func inclusiveLastPhaseEvent(jde, degree float64, fn phaseEventSearchFunc) float64 {
|
||||||
|
last := fn(jde, degree, 0)
|
||||||
|
next := fn(jde, degree, 1)
|
||||||
|
if eventUTQueryBeforeOrEqual(next, jde) && eventUTQueryAfterOrEqual(next, jde) {
|
||||||
|
return next
|
||||||
|
}
|
||||||
|
if eventUTQueryBeforeOrEqual(last, jde) {
|
||||||
|
return last
|
||||||
|
}
|
||||||
|
return last
|
||||||
|
}
|
||||||
|
|
||||||
|
func inclusiveNextPhaseEvent(jde, degree float64, fn phaseEventSearchFunc) float64 {
|
||||||
|
last := fn(jde, degree, 0)
|
||||||
|
if eventUTQueryBeforeOrEqual(last, jde) && eventUTQueryAfterOrEqual(last, jde) {
|
||||||
|
return last
|
||||||
|
}
|
||||||
|
next := fn(jde, degree, 1)
|
||||||
|
if eventUTQueryAfterOrEqual(next, jde) {
|
||||||
|
return next
|
||||||
|
}
|
||||||
|
return next
|
||||||
|
}
|
||||||
|
|
||||||
|
func inclusiveLastSimpleEvent(jde float64, lastFn, nextFn simpleEventSearchFunc) float64 {
|
||||||
|
last := lastFn(jde)
|
||||||
|
next := nextFn(jde)
|
||||||
|
if eventUTQueryBeforeOrEqual(next, jde) && eventUTQueryAfterOrEqual(next, jde) {
|
||||||
|
return next
|
||||||
|
}
|
||||||
|
if eventUTQueryBeforeOrEqual(last, jde) {
|
||||||
|
return last
|
||||||
|
}
|
||||||
|
return last
|
||||||
|
}
|
||||||
|
|
||||||
|
func inclusiveNextSimpleEvent(jde float64, lastFn, nextFn simpleEventSearchFunc) float64 {
|
||||||
|
last := lastFn(jde)
|
||||||
|
if eventUTQueryBeforeOrEqual(last, jde) && eventUTQueryAfterOrEqual(last, jde) {
|
||||||
|
return last
|
||||||
|
}
|
||||||
|
next := nextFn(jde)
|
||||||
|
if eventUTQueryAfterOrEqual(next, jde) {
|
||||||
|
return next
|
||||||
|
}
|
||||||
|
return next
|
||||||
|
}
|
||||||
@@ -40,9 +40,7 @@ func eventZeroBracket(leftJD, leftVal, centerJD, centerVal, rightJD, rightVal fl
|
|||||||
return 0, 0, 0, 0, false
|
return 0, 0, 0, 0, false
|
||||||
}
|
}
|
||||||
|
|
||||||
// eventZeroRefine 细化 seed 附近的零点;若找不到可用括号区间,则退回旧的固定步长扫描。
|
// eventZeroRefine 细化 seed 附近的零点;无可用括号区间时退回固定步长扫描。
|
||||||
// eventZeroRefine refines a nearby zero crossing and falls back to the legacy
|
|
||||||
// fixed-step scan when no usable bracket is found.
|
|
||||||
func eventZeroRefine(seed, halfWindow, step float64, fn func(float64) float64) float64 {
|
func eventZeroRefine(seed, halfWindow, step float64, fn func(float64) float64) float64 {
|
||||||
leftJD := seed - halfWindow
|
leftJD := seed - halfWindow
|
||||||
centerJD := seed
|
centerJD := seed
|
||||||
|
|||||||
@@ -0,0 +1,183 @@
|
|||||||
|
package basic
|
||||||
|
|
||||||
|
import "math"
|
||||||
|
|
||||||
|
const innerEventEpsilon = 4.0 / 86400.0
|
||||||
|
|
||||||
|
func eventQueryTTAsUT(queryTT float64) float64 {
|
||||||
|
return TD2UT(queryTT, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
func eventUTQueryTTDelta(eventUT, queryTT float64) float64 {
|
||||||
|
return eventUT - eventQueryTTAsUT(queryTT)
|
||||||
|
}
|
||||||
|
|
||||||
|
func eventUTQueryBeforeOrEqual(eventUT, queryTT float64) bool {
|
||||||
|
return eventUTQueryTTDelta(eventUT, queryTT) <= innerEventEpsilon
|
||||||
|
}
|
||||||
|
|
||||||
|
func eventUTQueryAfterOrEqual(eventUT, queryTT float64) bool {
|
||||||
|
return eventUTQueryTTDelta(eventUT, queryTT) >= -innerEventEpsilon
|
||||||
|
}
|
||||||
|
|
||||||
|
func eventUTNextQueryTT(eventUT float64) float64 {
|
||||||
|
return TD2UT(eventUT, true) + 1.0
|
||||||
|
}
|
||||||
|
|
||||||
|
func eventUTLastQueryTT(eventUT float64) float64 {
|
||||||
|
return TD2UT(eventUT, true) - 1.0
|
||||||
|
}
|
||||||
|
|
||||||
|
func innerNextCycleOffset(delta, period float64) float64 {
|
||||||
|
if delta <= 0 {
|
||||||
|
return -delta * period / 360.0
|
||||||
|
}
|
||||||
|
return (360.0 - delta) * period / 360.0
|
||||||
|
}
|
||||||
|
|
||||||
|
func innerLastCycleOffset(delta, period float64) float64 {
|
||||||
|
if delta >= 0 {
|
||||||
|
return delta * period / 360.0
|
||||||
|
}
|
||||||
|
return (360.0 + delta) * period / 360.0
|
||||||
|
}
|
||||||
|
|
||||||
|
func clampFloat64(v, min, max float64) float64 {
|
||||||
|
if v < min {
|
||||||
|
return min
|
||||||
|
}
|
||||||
|
if v > max {
|
||||||
|
return max
|
||||||
|
}
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
func scanWindowForMinAbs(start, end, step float64, fn func(float64) float64) float64 {
|
||||||
|
if end < start {
|
||||||
|
start, end = end, start
|
||||||
|
}
|
||||||
|
if step <= 0 || end == start {
|
||||||
|
return start
|
||||||
|
}
|
||||||
|
bestJD := start
|
||||||
|
bestAbs := math.Abs(fn(start))
|
||||||
|
for jd := start + step; jd < end; jd += step {
|
||||||
|
candidateAbs := math.Abs(fn(jd))
|
||||||
|
if candidateAbs < bestAbs {
|
||||||
|
bestAbs = candidateAbs
|
||||||
|
bestJD = jd
|
||||||
|
}
|
||||||
|
}
|
||||||
|
endAbs := math.Abs(fn(end))
|
||||||
|
if endAbs < bestAbs {
|
||||||
|
return end
|
||||||
|
}
|
||||||
|
return bestJD
|
||||||
|
}
|
||||||
|
|
||||||
|
func scanWindowForMax(start, end, step float64, fn func(float64) float64) float64 {
|
||||||
|
if end < start {
|
||||||
|
start, end = end, start
|
||||||
|
}
|
||||||
|
if step <= 0 || end == start {
|
||||||
|
return start
|
||||||
|
}
|
||||||
|
bestJD := start
|
||||||
|
bestVal := fn(start)
|
||||||
|
for jd := start + step; jd < end; jd += step {
|
||||||
|
candidateVal := fn(jd)
|
||||||
|
if candidateVal > bestVal {
|
||||||
|
bestVal = candidateVal
|
||||||
|
bestJD = jd
|
||||||
|
}
|
||||||
|
}
|
||||||
|
endVal := fn(end)
|
||||||
|
if endVal > bestVal {
|
||||||
|
return end
|
||||||
|
}
|
||||||
|
return bestJD
|
||||||
|
}
|
||||||
|
|
||||||
|
func boundedEventZeroRefine(seed, start, end, halfWindow, step float64, fn func(float64) float64) float64 {
|
||||||
|
if end < start {
|
||||||
|
start, end = end, start
|
||||||
|
}
|
||||||
|
if end <= start {
|
||||||
|
return start
|
||||||
|
}
|
||||||
|
maxHalfWindow := (end - start) / 2
|
||||||
|
if halfWindow > maxHalfWindow {
|
||||||
|
halfWindow = maxHalfWindow
|
||||||
|
}
|
||||||
|
if halfWindow <= 0 {
|
||||||
|
return clampFloat64(seed, start, end)
|
||||||
|
}
|
||||||
|
seed = clampFloat64(seed, start+halfWindow, end-halfWindow)
|
||||||
|
return eventZeroRefine(seed, halfWindow, step, fn)
|
||||||
|
}
|
||||||
|
|
||||||
|
func zeroEventInWindow(start, end, coarseStep, halfWindow, refineStep float64, coarseFn, exactFn func(float64) float64) float64 {
|
||||||
|
if end < start {
|
||||||
|
start, end = end, start
|
||||||
|
}
|
||||||
|
if end <= start {
|
||||||
|
return start
|
||||||
|
}
|
||||||
|
rangeDays := end - start
|
||||||
|
if coarseStep <= 0 || coarseStep > rangeDays {
|
||||||
|
coarseStep = rangeDays / 6.0
|
||||||
|
}
|
||||||
|
if coarseStep < 0.5 {
|
||||||
|
coarseStep = 0.5
|
||||||
|
}
|
||||||
|
if refineStep <= 0 {
|
||||||
|
refineStep = 0.5 / 86400.0
|
||||||
|
}
|
||||||
|
if halfWindow <= 0 {
|
||||||
|
halfWindow = coarseStep
|
||||||
|
}
|
||||||
|
guess := scanWindowForMinAbs(start, end, coarseStep, coarseFn)
|
||||||
|
return boundedEventZeroRefine(guess, start, end, halfWindow, refineStep, exactFn)
|
||||||
|
}
|
||||||
|
|
||||||
|
func maximizeInWindow(start, end, coarseStep float64, coarseFn, exactFn func(float64) float64) float64 {
|
||||||
|
if end < start {
|
||||||
|
start, end = end, start
|
||||||
|
}
|
||||||
|
if end <= start {
|
||||||
|
return start
|
||||||
|
}
|
||||||
|
rangeDays := end - start
|
||||||
|
if coarseStep <= 0 || coarseStep > rangeDays {
|
||||||
|
coarseStep = rangeDays / 6.0
|
||||||
|
}
|
||||||
|
if coarseStep < 0.5 {
|
||||||
|
coarseStep = 0.5
|
||||||
|
}
|
||||||
|
guess := scanWindowForMax(start, end, coarseStep, coarseFn)
|
||||||
|
left := clampFloat64(guess-coarseStep, start, end)
|
||||||
|
right := clampFloat64(guess+coarseStep, start, end)
|
||||||
|
if right-left <= innerEventEpsilon {
|
||||||
|
return guess
|
||||||
|
}
|
||||||
|
for i := 0; i < 20; i++ {
|
||||||
|
third := (right - left) / 3.0
|
||||||
|
leftThird := left + third
|
||||||
|
rightThird := right - third
|
||||||
|
if exactFn(leftThird) <= exactFn(rightThird) {
|
||||||
|
left = leftThird
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
right = rightThird
|
||||||
|
}
|
||||||
|
bestJD := guess
|
||||||
|
bestVal := exactFn(bestJD)
|
||||||
|
for _, jd := range []float64{left, (left + right) / 2.0, right} {
|
||||||
|
candidateVal := exactFn(jd)
|
||||||
|
if candidateVal > bestVal {
|
||||||
|
bestVal = candidateVal
|
||||||
|
bestJD = jd
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return bestJD
|
||||||
|
}
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
package basic
|
||||||
|
|
||||||
|
import "testing"
|
||||||
|
|
||||||
|
func TestInnerPlanetExactEventBoundaryIncludesCurrent(t *testing.T) {
|
||||||
|
cases := []struct {
|
||||||
|
name string
|
||||||
|
seed float64
|
||||||
|
lastFn func(float64) float64
|
||||||
|
nextFn func(float64) float64
|
||||||
|
}{
|
||||||
|
{name: "MercuryConjunction", seed: NextMercuryConjunction(ttjdUTC(2026, 1, 1, 0, 0, 0)), lastFn: LastMercuryConjunction, nextFn: NextMercuryConjunction},
|
||||||
|
{name: "MercuryInferior", seed: NextMercuryInferiorConjunctionInclusive(ttjdUTC(2026, 1, 1, 0, 0, 0)), lastFn: LastMercuryInferiorConjunctionInclusive, nextFn: NextMercuryInferiorConjunctionInclusive},
|
||||||
|
{name: "MercurySuperior", seed: NextMercurySuperiorConjunctionInclusive(ttjdUTC(2026, 1, 1, 0, 0, 0)), lastFn: LastMercurySuperiorConjunctionInclusive, nextFn: NextMercurySuperiorConjunctionInclusive},
|
||||||
|
{name: "MercuryRetrograde", seed: NextMercuryRetrogradeInclusive(ttjdUTC(2026, 1, 1, 0, 0, 0)), lastFn: LastMercuryRetrogradeInclusive, nextFn: NextMercuryRetrogradeInclusive},
|
||||||
|
{name: "MercuryP2R", seed: NextMercuryProgradeToRetrogradeInclusive(ttjdUTC(2026, 1, 1, 0, 0, 0)), lastFn: LastMercuryProgradeToRetrogradeInclusive, nextFn: NextMercuryProgradeToRetrogradeInclusive},
|
||||||
|
{name: "MercuryR2P", seed: NextMercuryRetrogradeToProgradeInclusive(ttjdUTC(2026, 1, 1, 0, 0, 0)), lastFn: LastMercuryRetrogradeToProgradeInclusive, nextFn: NextMercuryRetrogradeToProgradeInclusive},
|
||||||
|
{name: "MercuryGreatestElongation", seed: NextMercuryGreatestElongationInclusive(ttjdUTC(2026, 1, 1, 0, 0, 0)), lastFn: LastMercuryGreatestElongationInclusive, nextFn: NextMercuryGreatestElongationInclusive},
|
||||||
|
{name: "MercuryEastElongation", seed: NextMercuryGreatestElongationEastInclusive(ttjdUTC(2026, 1, 1, 0, 0, 0)), lastFn: LastMercuryGreatestElongationEastInclusive, nextFn: NextMercuryGreatestElongationEastInclusive},
|
||||||
|
{name: "MercuryWestElongation", seed: NextMercuryGreatestElongationWestInclusive(ttjdUTC(2026, 1, 1, 0, 0, 0)), lastFn: LastMercuryGreatestElongationWestInclusive, nextFn: NextMercuryGreatestElongationWestInclusive},
|
||||||
|
{name: "VenusConjunction", seed: NextVenusConjunction(ttjdUTC(2026, 1, 1, 0, 0, 0)), lastFn: LastVenusConjunction, nextFn: NextVenusConjunction},
|
||||||
|
{name: "VenusInferior", seed: NextVenusInferiorConjunctionInclusive(ttjdUTC(2026, 1, 1, 0, 0, 0)), lastFn: LastVenusInferiorConjunctionInclusive, nextFn: NextVenusInferiorConjunctionInclusive},
|
||||||
|
{name: "VenusSuperior", seed: NextVenusSuperiorConjunctionInclusive(ttjdUTC(2026, 1, 1, 0, 0, 0)), lastFn: LastVenusSuperiorConjunctionInclusive, nextFn: NextVenusSuperiorConjunctionInclusive},
|
||||||
|
{name: "VenusRetrograde", seed: NextVenusRetrogradeInclusive(ttjdUTC(2026, 1, 1, 0, 0, 0)), lastFn: LastVenusRetrogradeInclusive, nextFn: NextVenusRetrogradeInclusive},
|
||||||
|
{name: "VenusP2R", seed: NextVenusProgradeToRetrogradeInclusive(ttjdUTC(2026, 1, 1, 0, 0, 0)), lastFn: LastVenusProgradeToRetrogradeInclusive, nextFn: NextVenusProgradeToRetrogradeInclusive},
|
||||||
|
{name: "VenusR2P", seed: NextVenusRetrogradeToProgradeInclusive(ttjdUTC(2026, 1, 1, 0, 0, 0)), lastFn: LastVenusRetrogradeToProgradeInclusive, nextFn: NextVenusRetrogradeToProgradeInclusive},
|
||||||
|
{name: "VenusGreatestElongation", seed: NextVenusGreatestElongationInclusive(ttjdUTC(2026, 1, 1, 0, 0, 0)), lastFn: LastVenusGreatestElongationInclusive, nextFn: NextVenusGreatestElongationInclusive},
|
||||||
|
{name: "VenusEastElongation", seed: NextVenusGreatestElongationEastInclusive(ttjdUTC(2026, 1, 1, 0, 0, 0)), lastFn: LastVenusGreatestElongationEastInclusive, nextFn: NextVenusGreatestElongationEastInclusive},
|
||||||
|
{name: "VenusWestElongation", seed: NextVenusGreatestElongationWestInclusive(ttjdUTC(2026, 1, 1, 0, 0, 0)), lastFn: LastVenusGreatestElongationWestInclusive, nextFn: NextVenusGreatestElongationWestInclusive},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range cases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
queryTT := TD2UT(tc.seed, true)
|
||||||
|
last := tc.lastFn(queryTT)
|
||||||
|
next := tc.nextFn(queryTT)
|
||||||
|
if !sameEventJD(last, tc.seed) {
|
||||||
|
t.Fatalf("last exact boundary mismatch: got %.12f want %.12f", last, tc.seed)
|
||||||
|
}
|
||||||
|
if !sameEventJD(next, tc.seed) {
|
||||||
|
t.Fatalf("next exact boundary mismatch: got %.12f want %.12f", next, tc.seed)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,165 @@
|
|||||||
|
package basic
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type innerBaselineFile struct {
|
||||||
|
Events []innerBaselineEvent `json:"events"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type innerBaselineEvent struct {
|
||||||
|
Planet string `json:"planet"`
|
||||||
|
Kind string `json:"kind"`
|
||||||
|
NAOJHintJST string `json:"naoj_hint_jst"`
|
||||||
|
Precision string `json:"precision"`
|
||||||
|
CandidateJST string `json:"candidate_jst"`
|
||||||
|
VerifiedJST string `json:"verified_jst"`
|
||||||
|
CandidateSource string `json:"candidate_source"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadInnerBaseline(t *testing.T) innerBaselineFile {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
paths := [][]string{
|
||||||
|
{
|
||||||
|
"testdata/jpl_inner_event_baseline.json",
|
||||||
|
"basic/testdata/jpl_inner_event_baseline.json",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"testdata/jpl_inner_event_baseline_21c_sample.json",
|
||||||
|
"basic/testdata/jpl_inner_event_baseline_21c_sample.json",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"testdata/jpl_inner_event_baseline_20c_sample.json",
|
||||||
|
"basic/testdata/jpl_inner_event_baseline_20c_sample.json",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"testdata/jpl_inner_event_baseline_22c_sample.json",
|
||||||
|
"basic/testdata/jpl_inner_event_baseline_22c_sample.json",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
var merged innerBaselineFile
|
||||||
|
for index, candidates := range paths {
|
||||||
|
var (
|
||||||
|
data []byte
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
for _, path := range candidates {
|
||||||
|
data, err = os.ReadFile(path)
|
||||||
|
if err == nil {
|
||||||
|
var baseline innerBaselineFile
|
||||||
|
if err := json.Unmarshal(data, &baseline); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
merged.Events = append(merged.Events, baseline.Events...)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err != nil && index == 0 {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(merged.Events) == 0 {
|
||||||
|
t.Fatal("empty inner baseline file")
|
||||||
|
}
|
||||||
|
return merged
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseInnerBaselineTime(t *testing.T, value string) time.Time {
|
||||||
|
t.Helper()
|
||||||
|
loc := time.FixedZone("JST", 9*3600)
|
||||||
|
layouts := []string{
|
||||||
|
"2006-01-02 15:04:05 MST",
|
||||||
|
"2006-01-02 15:04 MST",
|
||||||
|
"2006-01-02 15:04:05",
|
||||||
|
"2006-01-02 15:04",
|
||||||
|
}
|
||||||
|
var err error
|
||||||
|
for _, layout := range layouts {
|
||||||
|
when, parseErr := time.ParseInLocation(layout, value, loc)
|
||||||
|
if parseErr == nil {
|
||||||
|
return when
|
||||||
|
}
|
||||||
|
err = parseErr
|
||||||
|
}
|
||||||
|
t.Fatalf("parse baseline time %q: %v", value, err)
|
||||||
|
return time.Time{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func innerBaselineTolerance(event innerBaselineEvent) time.Duration {
|
||||||
|
switch event.Kind {
|
||||||
|
case "IC", "SC", "P2R", "R2P":
|
||||||
|
return 2 * time.Minute
|
||||||
|
case "GEE", "GEW":
|
||||||
|
return 90 * time.Minute
|
||||||
|
default:
|
||||||
|
return 2 * time.Minute
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func innerEventFuncs(t *testing.T, event innerBaselineEvent) (func(float64) float64, func(float64) float64) {
|
||||||
|
t.Helper()
|
||||||
|
switch event.Planet + ":" + event.Kind {
|
||||||
|
case "Mercury:IC":
|
||||||
|
return LastMercuryInferiorConjunctionInclusive, NextMercuryInferiorConjunctionInclusive
|
||||||
|
case "Mercury:SC":
|
||||||
|
return LastMercurySuperiorConjunctionInclusive, NextMercurySuperiorConjunctionInclusive
|
||||||
|
case "Mercury:P2R":
|
||||||
|
return LastMercuryProgradeToRetrogradeInclusive, NextMercuryProgradeToRetrogradeInclusive
|
||||||
|
case "Mercury:R2P":
|
||||||
|
return LastMercuryRetrogradeToProgradeInclusive, NextMercuryRetrogradeToProgradeInclusive
|
||||||
|
case "Mercury:GEE":
|
||||||
|
return LastMercuryGreatestElongationEastInclusive, NextMercuryGreatestElongationEastInclusive
|
||||||
|
case "Mercury:GEW":
|
||||||
|
return LastMercuryGreatestElongationWestInclusive, NextMercuryGreatestElongationWestInclusive
|
||||||
|
case "Venus:IC":
|
||||||
|
return LastVenusInferiorConjunctionInclusive, NextVenusInferiorConjunctionInclusive
|
||||||
|
case "Venus:SC":
|
||||||
|
return LastVenusSuperiorConjunctionInclusive, NextVenusSuperiorConjunctionInclusive
|
||||||
|
case "Venus:P2R":
|
||||||
|
return LastVenusProgradeToRetrogradeInclusive, NextVenusProgradeToRetrogradeInclusive
|
||||||
|
case "Venus:R2P":
|
||||||
|
return LastVenusRetrogradeToProgradeInclusive, NextVenusRetrogradeToProgradeInclusive
|
||||||
|
case "Venus:GEE":
|
||||||
|
return LastVenusGreatestElongationEastInclusive, NextVenusGreatestElongationEastInclusive
|
||||||
|
case "Venus:GEW":
|
||||||
|
return LastVenusGreatestElongationWestInclusive, NextVenusGreatestElongationWestInclusive
|
||||||
|
default:
|
||||||
|
t.Fatalf("unsupported event %s:%s", event.Planet, event.Kind)
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func assertInnerBaselineEvent(t *testing.T, event innerBaselineEvent, lastFn, nextFn func(float64) float64) {
|
||||||
|
t.Helper()
|
||||||
|
when := parseInnerBaselineTime(t, event.VerifiedJST)
|
||||||
|
before := when.Add(-24 * time.Hour)
|
||||||
|
after := when.Add(24 * time.Hour)
|
||||||
|
next := JDE2DateByZone(nextFn(toUTJD(before)), when.Location(), false)
|
||||||
|
last := JDE2DateByZone(lastFn(toUTJD(after)), when.Location(), false)
|
||||||
|
tolerance := innerBaselineTolerance(event)
|
||||||
|
|
||||||
|
if diff := next.Sub(when); diff < -tolerance || diff > tolerance {
|
||||||
|
t.Fatalf("%s %s next mismatch: got %s want %s tol=%s hint=%s candidate=%s via=%s", event.Planet, event.Kind, next, when, tolerance, event.NAOJHintJST, event.CandidateJST, event.CandidateSource)
|
||||||
|
}
|
||||||
|
if diff := last.Sub(when); diff < -tolerance || diff > tolerance {
|
||||||
|
t.Fatalf("%s %s last mismatch: got %s want %s tol=%s hint=%s candidate=%s via=%s", event.Planet, event.Kind, last, when, tolerance, event.NAOJHintJST, event.CandidateJST, event.CandidateSource)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestInnerPlanetTruthAgainstJPL(t *testing.T) {
|
||||||
|
baseline := loadInnerBaseline(t)
|
||||||
|
for _, event := range baseline.Events {
|
||||||
|
event := event
|
||||||
|
name := strings.Join([]string{event.Planet, event.Kind, event.VerifiedJST}, "_")
|
||||||
|
t.Run(name, func(t *testing.T) {
|
||||||
|
lastFn, nextFn := innerEventFuncs(t, event)
|
||||||
|
assertInnerBaselineEvent(t, event, lastFn, nextFn)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
+7
-38
@@ -87,53 +87,22 @@ func JupiterApparentRaDec(jd float64) (float64, float64) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func EarthJupiterAway(jd float64) float64 {
|
func EarthJupiterAway(jd float64) float64 {
|
||||||
x, y, z := AJupiterXYZ(jd)
|
return planetEarthAwayExplicitN(4, jd, -1)
|
||||||
to := math.Sqrt(x*x + y*y + z*z)
|
|
||||||
return to
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func JupiterApparentLo(jd float64) float64 {
|
func JupiterApparentLo(jd float64) float64 {
|
||||||
x, y, z := AJupiterXYZ(jd)
|
geo, _ := planetApparentGeocentricPositionN(4, jd, -1)
|
||||||
to := 0.0057755183 * math.Sqrt(x*x+y*y+z*z)
|
return geo.lo
|
||||||
x, y, z = AJupiterXYZ(jd - to)
|
|
||||||
lo := math.Atan2(y, x)
|
|
||||||
bo := math.Atan2(z, math.Sqrt(x*x+y*y))
|
|
||||||
lo = lo * 180 / math.Pi
|
|
||||||
bo = bo * 180 / math.Pi
|
|
||||||
lo = Limit360(lo)
|
|
||||||
//lo-=GXCLo(lo,bo,jd)/3600;
|
|
||||||
//bo+=GXCBo(lo,bo,jd);
|
|
||||||
lo += Nutation2000Bi(jd)
|
|
||||||
return lo
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func JupiterApparentBo(jd float64) float64 {
|
func JupiterApparentBo(jd float64) float64 {
|
||||||
x, y, z := AJupiterXYZ(jd)
|
geo, _ := planetApparentGeocentricPositionN(4, jd, -1)
|
||||||
to := 0.0057755183 * math.Sqrt(x*x+y*y+z*z)
|
return geo.bo
|
||||||
x, y, z = AJupiterXYZ(jd - to)
|
|
||||||
//lo := math.Atan2(y, x)
|
|
||||||
bo := math.Atan2(z, math.Sqrt(x*x+y*y))
|
|
||||||
//lo = lo * 180 / math.Pi
|
|
||||||
bo = bo * 180 / math.Pi
|
|
||||||
//lo+=GXCLo(lo,bo,jd);
|
|
||||||
//bo+=GXCBo(lo,bo,jd)/3600;
|
|
||||||
//lo+=Nutation2000Bi(jd);
|
|
||||||
return bo
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func JupiterApparentLoBo(jd float64) (float64, float64) {
|
func JupiterApparentLoBo(jd float64) (float64, float64) {
|
||||||
x, y, z := AJupiterXYZ(jd)
|
geo, _ := planetApparentGeocentricPositionN(4, jd, -1)
|
||||||
to := 0.0057755183 * math.Sqrt(x*x+y*y+z*z)
|
return geo.lo, geo.bo
|
||||||
x, y, z = AJupiterXYZ(jd - to)
|
|
||||||
lo := math.Atan2(y, x)
|
|
||||||
bo := math.Atan2(z, math.Sqrt(x*x+y*y))
|
|
||||||
lo = lo * 180 / math.Pi
|
|
||||||
bo = bo * 180 / math.Pi
|
|
||||||
lo = Limit360(lo)
|
|
||||||
//lo-=GXCLo(lo,bo,jd)/3600;
|
|
||||||
//bo+=GXCBo(lo,bo,jd);
|
|
||||||
lo += Nutation2000Bi(jd)
|
|
||||||
return lo, bo
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func JupiterMag(jd float64) float64 {
|
func JupiterMag(jd float64) float64 {
|
||||||
|
|||||||
+66
-65
@@ -40,6 +40,28 @@ func jupiterSunLongitudeDeltaN(jde, degree float64, filter bool, n int) float64
|
|||||||
return sub
|
return sub
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func jupiterRADerivative(jde, delta float64) float64 {
|
||||||
|
sub := JupiterApparentRa(jde+delta) - JupiterApparentRa(jde-delta)
|
||||||
|
if sub > 180 {
|
||||||
|
sub -= 360
|
||||||
|
}
|
||||||
|
if sub < -180 {
|
||||||
|
sub += 360
|
||||||
|
}
|
||||||
|
return sub / (2 * delta)
|
||||||
|
}
|
||||||
|
|
||||||
|
func jupiterRADerivativeN(jde, delta float64, n int) float64 {
|
||||||
|
sub := JupiterApparentRaN(jde+delta, n) - JupiterApparentRaN(jde-delta, n)
|
||||||
|
if sub > 180 {
|
||||||
|
sub -= 360
|
||||||
|
}
|
||||||
|
if sub < -180 {
|
||||||
|
sub += 360
|
||||||
|
}
|
||||||
|
return sub / (2 * delta)
|
||||||
|
}
|
||||||
|
|
||||||
func jupiterConjunctionFull(jde, degree float64, next uint8) float64 {
|
func jupiterConjunctionFull(jde, degree float64, next uint8) float64 {
|
||||||
//0=last 1=next
|
//0=last 1=next
|
||||||
daysPerDegree := JUPITER_S_PERIOD / 360
|
daysPerDegree := JUPITER_S_PERIOD / 360
|
||||||
@@ -94,113 +116,92 @@ func jupiterConjunction(jde, degree float64, next uint8) float64 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func LastJupiterConjunction(jde float64) float64 {
|
func LastJupiterConjunction(jde float64) float64 {
|
||||||
return jupiterConjunction(jde, 0, 0)
|
return inclusiveLastPhaseEvent(jde, 0, jupiterConjunction)
|
||||||
}
|
}
|
||||||
|
|
||||||
func NextJupiterConjunction(jde float64) float64 {
|
func NextJupiterConjunction(jde float64) float64 {
|
||||||
return jupiterConjunction(jde, 0, 1)
|
return inclusiveNextPhaseEvent(jde, 0, jupiterConjunction)
|
||||||
}
|
}
|
||||||
|
|
||||||
func LastJupiterOpposition(jde float64) float64 {
|
func LastJupiterOpposition(jde float64) float64 {
|
||||||
return jupiterConjunction(jde, 180, 0)
|
return inclusiveLastPhaseEvent(jde, 180, jupiterConjunction)
|
||||||
}
|
}
|
||||||
|
|
||||||
func NextJupiterOpposition(jde float64) float64 {
|
func NextJupiterOpposition(jde float64) float64 {
|
||||||
return jupiterConjunction(jde, 180, 1)
|
return inclusiveNextPhaseEvent(jde, 180, jupiterConjunction)
|
||||||
}
|
}
|
||||||
|
|
||||||
func NextJupiterEasternQuadrature(jde float64) float64 {
|
func NextJupiterEasternQuadrature(jde float64) float64 {
|
||||||
return jupiterConjunction(jde, 90, 1)
|
return inclusiveNextPhaseEvent(jde, 90, jupiterConjunction)
|
||||||
}
|
}
|
||||||
|
|
||||||
func LastJupiterEasternQuadrature(jde float64) float64 {
|
func LastJupiterEasternQuadrature(jde float64) float64 {
|
||||||
return jupiterConjunction(jde, 90, 0)
|
return inclusiveLastPhaseEvent(jde, 90, jupiterConjunction)
|
||||||
}
|
}
|
||||||
|
|
||||||
func NextJupiterWesternQuadrature(jde float64) float64 {
|
func NextJupiterWesternQuadrature(jde float64) float64 {
|
||||||
return jupiterConjunction(jde, 270, 1)
|
return inclusiveNextPhaseEvent(jde, 270, jupiterConjunction)
|
||||||
}
|
}
|
||||||
|
|
||||||
func LastJupiterWesternQuadrature(jde float64) float64 {
|
func LastJupiterWesternQuadrature(jde float64) float64 {
|
||||||
return jupiterConjunction(jde, 270, 0)
|
return inclusiveLastPhaseEvent(jde, 270, jupiterConjunction)
|
||||||
}
|
}
|
||||||
|
|
||||||
func jupiterRetrograde(jde float64, searchBeforeOpposition bool) float64 {
|
func jupiterRetrogradeAroundOpposition(oppositionJD float64, searchBeforeOpposition bool) float64 {
|
||||||
//0=last 1=next
|
oppositionTT := TD2UT(oppositionJD, true)
|
||||||
raRate := func(jde float64, delta float64) float64 {
|
startTT := oppositionTT
|
||||||
sub := JupiterApparentRa(jde+delta) - JupiterApparentRa(jde-delta)
|
endTT := oppositionTT
|
||||||
if sub > 180 {
|
|
||||||
sub -= 360
|
|
||||||
}
|
|
||||||
if sub < -180 {
|
|
||||||
sub += 360
|
|
||||||
}
|
|
||||||
return sub / (2 * delta)
|
|
||||||
}
|
|
||||||
jde = jupiterConjunctionFull(jde, 180, 1)
|
|
||||||
if searchBeforeOpposition {
|
if searchBeforeOpposition {
|
||||||
jde -= 60
|
easternQuadratureUT := jupiterConjunction(oppositionTT, 90, 0)
|
||||||
|
startTT = TD2UT(easternQuadratureUT, true)
|
||||||
} else {
|
} else {
|
||||||
jde += 60
|
westernQuadratureUT := jupiterConjunction(oppositionTT, 270, 1)
|
||||||
|
endTT = TD2UT(westernQuadratureUT, true)
|
||||||
}
|
}
|
||||||
for {
|
bestJD := zeroEventInWindow(startTT, endTT, 2.0, 2.0, 30.0/86400.0, func(jd float64) float64 {
|
||||||
currentRate := raRate(jde, 1.0/86400.0)
|
return jupiterRADerivativeN(jd, 1.0/86400.0, jupiterEventSearchN)
|
||||||
if math.Abs(currentRate) > 0.55 {
|
}, func(jd float64) float64 {
|
||||||
jde += 2
|
return jupiterRADerivative(jd, 0.5/86400.0)
|
||||||
continue
|
|
||||||
}
|
|
||||||
break
|
|
||||||
}
|
|
||||||
estimateJD := jde
|
|
||||||
for {
|
|
||||||
prevJD := estimateJD
|
|
||||||
rateValue := raRate(prevJD, 2.0/86400.0)
|
|
||||||
rateSlope := (raRate(prevJD+15.0/86400.0, 2.0/86400.0) - raRate(prevJD-15.0/86400.0, 2.0/86400.0)) / (30.0 / 86400.0)
|
|
||||||
estimateJD = prevJD - rateValue/rateSlope
|
|
||||||
if math.Abs(estimateJD-prevJD) <= 30.0/86400.0 {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
bestJD := eventZeroRefine(estimateJD, 15.0/86400.0, 0.5/86400.0, func(jd float64) float64 {
|
|
||||||
return raRate(jd, 0.5/86400.0)
|
|
||||||
})
|
})
|
||||||
return TD2UT(bestJD, false)
|
return TD2UT(bestJD, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
func NextJupiterRetrogradeToPrograde(jde float64) float64 {
|
func NextJupiterRetrogradeToPrograde(jde float64) float64 {
|
||||||
date := jupiterRetrograde(jde, false)
|
lastOppositionJD := jupiterConjunctionFull(jde, 180, 0)
|
||||||
if date < jde {
|
date := jupiterRetrogradeAroundOpposition(lastOppositionJD, false)
|
||||||
oppositionJD := jupiterConjunctionFull(jde, 180, 1)
|
if sameEventUTQueryTT(date, jde) || eventUTQueryAfterOrEqual(date, jde) {
|
||||||
return jupiterRetrograde(oppositionJD+10, false)
|
|
||||||
}
|
|
||||||
return date
|
return date
|
||||||
}
|
}
|
||||||
|
nextOppositionJD := jupiterConjunctionFull(jde, 180, 1)
|
||||||
|
return jupiterRetrogradeAroundOpposition(nextOppositionJD, false)
|
||||||
|
}
|
||||||
|
|
||||||
func LastJupiterRetrogradeToPrograde(jde float64) float64 {
|
func LastJupiterRetrogradeToPrograde(jde float64) float64 {
|
||||||
jde = jupiterConjunctionFull(jde, 180, 0) - 10
|
lastOppositionJD := jupiterConjunctionFull(jde, 180, 0)
|
||||||
date := jupiterRetrograde(jde, false)
|
date := jupiterRetrogradeAroundOpposition(lastOppositionJD, false)
|
||||||
if date > jde {
|
if sameEventUTQueryTT(date, jde) || eventUTQueryBeforeOrEqual(date, jde) {
|
||||||
oppositionJD := jupiterConjunctionFull(jde, 180, 0)
|
|
||||||
return jupiterRetrograde(oppositionJD-10, false)
|
|
||||||
}
|
|
||||||
return date
|
return date
|
||||||
}
|
}
|
||||||
|
previousOppositionJD := jupiterConjunctionFull(eventUTLastQueryTT(lastOppositionJD), 180, 0)
|
||||||
|
return jupiterRetrogradeAroundOpposition(previousOppositionJD, false)
|
||||||
|
}
|
||||||
|
|
||||||
func NextJupiterProgradeToRetrograde(jde float64) float64 {
|
func NextJupiterProgradeToRetrograde(jde float64) float64 {
|
||||||
date := jupiterRetrograde(jde, true)
|
nextOppositionJD := jupiterConjunctionFull(jde, 180, 1)
|
||||||
if date < jde {
|
date := jupiterRetrogradeAroundOpposition(nextOppositionJD, true)
|
||||||
oppositionJD := jupiterConjunctionFull(jde, 180, 1)
|
if sameEventUTQueryTT(date, jde) || eventUTQueryAfterOrEqual(date, jde) {
|
||||||
return jupiterRetrograde(oppositionJD+10, true)
|
|
||||||
}
|
|
||||||
return date
|
return date
|
||||||
}
|
}
|
||||||
|
followingOppositionJD := jupiterConjunctionFull(eventUTNextQueryTT(nextOppositionJD), 180, 1)
|
||||||
|
return jupiterRetrogradeAroundOpposition(followingOppositionJD, true)
|
||||||
|
}
|
||||||
|
|
||||||
func LastJupiterProgradeToRetrograde(jde float64) float64 {
|
func LastJupiterProgradeToRetrograde(jde float64) float64 {
|
||||||
jde = jupiterConjunctionFull(jde, 180, 0) - 10
|
nextOppositionJD := jupiterConjunctionFull(jde, 180, 1)
|
||||||
date := jupiterRetrograde(jde, true)
|
date := jupiterRetrogradeAroundOpposition(nextOppositionJD, true)
|
||||||
if date > jde {
|
if sameEventUTQueryTT(date, jde) || eventUTQueryBeforeOrEqual(date, jde) {
|
||||||
oppositionJD := jupiterConjunctionFull(jde, 180, 0)
|
|
||||||
return jupiterRetrograde(oppositionJD-10, true)
|
|
||||||
}
|
|
||||||
return date
|
return date
|
||||||
}
|
}
|
||||||
|
lastOppositionJD := jupiterConjunctionFull(jde, 180, 0)
|
||||||
|
return jupiterRetrogradeAroundOpposition(lastOppositionJD, true)
|
||||||
|
}
|
||||||
|
|||||||
+11
-51
@@ -87,72 +87,32 @@ func MarsApparentRaDec(jd float64) (float64, float64) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func EarthMarsAway(jd float64) float64 {
|
func EarthMarsAway(jd float64) float64 {
|
||||||
x, y, z := AMarsXYZ(jd)
|
return planetEarthAwayExplicitN(3, jd, -1)
|
||||||
to := math.Sqrt(x*x + y*y + z*z)
|
|
||||||
return to
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func MarsApparentLo(jd float64) float64 {
|
func MarsApparentLo(jd float64) float64 {
|
||||||
x, y, z := AMarsXYZ(jd)
|
geo, _ := planetApparentGeocentricPositionN(3, jd, -1)
|
||||||
to := 0.0057755183 * math.Sqrt(x*x+y*y+z*z)
|
return geo.lo
|
||||||
x, y, z = AMarsXYZ(jd - to)
|
|
||||||
lo := math.Atan2(y, x)
|
|
||||||
//bo := math.Atan2(z, math.Sqrt(x*x+y*y))
|
|
||||||
lo = lo * 180.0 / math.Pi
|
|
||||||
//bo = bo * 180 / math.Pi
|
|
||||||
lo = Limit360(lo) + Nutation2000Bi(jd)
|
|
||||||
//lo-=GXCLo(lo,bo,jd)/3600;
|
|
||||||
//bo+=GXCBo(lo,bo,jd);
|
|
||||||
return lo
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func MarsApparentBo(jd float64) float64 {
|
func MarsApparentBo(jd float64) float64 {
|
||||||
x, y, z := AMarsXYZ(jd)
|
geo, _ := planetApparentGeocentricPositionN(3, jd, -1)
|
||||||
to := 0.0057755183 * math.Sqrt(x*x+y*y+z*z)
|
return geo.bo
|
||||||
x, y, z = AMarsXYZ(jd - to)
|
|
||||||
//lo := math.Atan2(y, x)
|
|
||||||
bo := math.Atan2(z, math.Sqrt(x*x+y*y))
|
|
||||||
//lo = lo * 180 / math.Pi
|
|
||||||
bo = bo * 180 / math.Pi
|
|
||||||
//lo+=GXCLo(lo,bo,jd);
|
|
||||||
//bo+=GXCBo(lo,bo,jd)/3600;
|
|
||||||
//lo+=Nutation2000Bi(jd);
|
|
||||||
return bo
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func MarsApparentLoBo(jd float64) (float64, float64) {
|
func MarsApparentLoBo(jd float64) (float64, float64) {
|
||||||
x, y, z := AMarsXYZ(jd)
|
geo, _ := planetApparentGeocentricPositionN(3, jd, -1)
|
||||||
to := 0.0057755183 * math.Sqrt(x*x+y*y+z*z)
|
return geo.lo, geo.bo
|
||||||
x, y, z = AMarsXYZ(jd - to)
|
|
||||||
lo := math.Atan2(y, x)
|
|
||||||
bo := math.Atan2(z, math.Sqrt(x*x+y*y))
|
|
||||||
lo = lo * 180 / math.Pi
|
|
||||||
bo = bo * 180 / math.Pi
|
|
||||||
lo = Limit360(lo)
|
|
||||||
//lo -= GXCLo(lo, bo, jd) / 3600
|
|
||||||
//bo += GXCBo(lo, bo, jd)
|
|
||||||
lo += Nutation2000Bi(jd)
|
|
||||||
return lo, bo
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func MarsTrueLoBo(jd float64) (float64, float64) {
|
func MarsTrueLoBo(jd float64) (float64, float64) {
|
||||||
x, y, z := AMarsXYZ(jd)
|
geo, _ := planetTrueGeocentricPositionN(3, jd, -1)
|
||||||
to := 0.0057755183 * math.Sqrt(x*x+y*y+z*z)
|
return geo.lo, geo.bo
|
||||||
x, y, z = AMarsXYZ(jd - to)
|
|
||||||
lo := math.Atan2(y, x)
|
|
||||||
bo := math.Atan2(z, math.Sqrt(x*x+y*y))
|
|
||||||
lo = lo * 180 / math.Pi
|
|
||||||
bo = bo * 180 / math.Pi
|
|
||||||
lo = Limit360(lo)
|
|
||||||
return lo, bo
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func MarsTrueLo(jd float64) float64 {
|
func MarsTrueLo(jd float64) float64 {
|
||||||
x, y, _ := AMarsXYZ(jd)
|
geo, _ := planetTrueGeocentricPositionN(3, jd, -1)
|
||||||
lo := math.Atan2(y, x)
|
return geo.lo
|
||||||
lo = lo * 180 / math.Pi
|
|
||||||
lo = Limit360(lo)
|
|
||||||
return lo
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func MarsMag(jd float64) float64 {
|
func MarsMag(jd float64) float64 {
|
||||||
|
|||||||
+54
-29
@@ -116,40 +116,39 @@ func marsConjunction(jde, degree float64, next uint8) float64 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func LastMarsConjunction(jde float64) float64 {
|
func LastMarsConjunction(jde float64) float64 {
|
||||||
return marsConjunction(jde, 0, 0)
|
return inclusiveLastPhaseEvent(jde, 0, marsConjunction)
|
||||||
}
|
}
|
||||||
|
|
||||||
func NextMarsConjunction(jde float64) float64 {
|
func NextMarsConjunction(jde float64) float64 {
|
||||||
return marsConjunction(jde, 0, 1)
|
return inclusiveNextPhaseEvent(jde, 0, marsConjunction)
|
||||||
}
|
}
|
||||||
|
|
||||||
func LastMarsOpposition(jde float64) float64 {
|
func LastMarsOpposition(jde float64) float64 {
|
||||||
return marsConjunction(jde, 180, 0)
|
return inclusiveLastPhaseEvent(jde, 180, marsConjunction)
|
||||||
}
|
}
|
||||||
|
|
||||||
func NextMarsOpposition(jde float64) float64 {
|
func NextMarsOpposition(jde float64) float64 {
|
||||||
return marsConjunction(jde, 180, 1)
|
return inclusiveNextPhaseEvent(jde, 180, marsConjunction)
|
||||||
}
|
}
|
||||||
|
|
||||||
func NextMarsEasternQuadrature(jde float64) float64 {
|
func NextMarsEasternQuadrature(jde float64) float64 {
|
||||||
return marsConjunction(jde, 90, 1)
|
return inclusiveNextPhaseEvent(jde, 90, marsConjunction)
|
||||||
}
|
}
|
||||||
|
|
||||||
func LastMarsEasternQuadrature(jde float64) float64 {
|
func LastMarsEasternQuadrature(jde float64) float64 {
|
||||||
return marsConjunction(jde, 90, 0)
|
return inclusiveLastPhaseEvent(jde, 90, marsConjunction)
|
||||||
}
|
}
|
||||||
|
|
||||||
func NextMarsWesternQuadrature(jde float64) float64 {
|
func NextMarsWesternQuadrature(jde float64) float64 {
|
||||||
return marsConjunction(jde, 270, 1)
|
return inclusiveNextPhaseEvent(jde, 270, marsConjunction)
|
||||||
}
|
}
|
||||||
|
|
||||||
func LastMarsWesternQuadrature(jde float64) float64 {
|
func LastMarsWesternQuadrature(jde float64) float64 {
|
||||||
return marsConjunction(jde, 270, 0)
|
return inclusiveLastPhaseEvent(jde, 270, marsConjunction)
|
||||||
}
|
}
|
||||||
|
|
||||||
func marsRetrograde(jde float64, searchBeforeOpposition bool) float64 {
|
func marsRetrogradeAroundOpposition(oppositionJD float64, searchBeforeOpposition bool) float64 {
|
||||||
//0=last 1=next
|
jde := oppositionJD
|
||||||
jde = marsConjunctionFull(jde, 180, 1)
|
|
||||||
if searchBeforeOpposition {
|
if searchBeforeOpposition {
|
||||||
jde -= 60
|
jde -= 60
|
||||||
} else {
|
} else {
|
||||||
@@ -179,40 +178,66 @@ func marsRetrograde(jde float64, searchBeforeOpposition bool) float64 {
|
|||||||
return TD2UT(bestJD, false)
|
return TD2UT(bestJD, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func marsOppositionFromBefore(oppositionJD float64) float64 {
|
||||||
|
return marsConjunctionFull(eventUTLastQueryTT(oppositionJD), 180, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
func marsOppositionFromAfter(oppositionJD float64) float64 {
|
||||||
|
return marsConjunctionFull(eventUTNextQueryTT(oppositionJD), 180, 0)
|
||||||
|
}
|
||||||
|
|
||||||
func NextMarsRetrogradeToPrograde(jde float64) float64 {
|
func NextMarsRetrogradeToPrograde(jde float64) float64 {
|
||||||
date := marsRetrograde(jde, false)
|
lastOppositionJD := marsConjunctionFull(jde, 180, 0)
|
||||||
if date < jde {
|
date := marsRetrogradeAroundOpposition(lastOppositionJD, false)
|
||||||
oppositionJD := marsConjunctionFull(jde, 180, 1)
|
if sameEventUTQueryTT(date, jde) {
|
||||||
return marsRetrograde(oppositionJD+10, false)
|
sameOppositionJD := marsOppositionFromBefore(lastOppositionJD)
|
||||||
|
return closestEventUTToQueryTT(jde, date, marsRetrogradeAroundOpposition(sameOppositionJD, false))
|
||||||
|
}
|
||||||
|
if !eventUTQueryAfterOrEqual(date, jde) {
|
||||||
|
nextOppositionJD := marsConjunctionFull(jde, 180, 1)
|
||||||
|
return marsRetrogradeAroundOpposition(nextOppositionJD, false)
|
||||||
}
|
}
|
||||||
return date
|
return date
|
||||||
}
|
}
|
||||||
|
|
||||||
func LastMarsRetrogradeToPrograde(jde float64) float64 {
|
func LastMarsRetrogradeToPrograde(jde float64) float64 {
|
||||||
jde = marsConjunctionFull(jde, 180, 0) - 10
|
lastOppositionJD := marsConjunctionFull(jde, 180, 0)
|
||||||
date := marsRetrograde(jde, false)
|
date := marsRetrogradeAroundOpposition(lastOppositionJD, false)
|
||||||
if date > jde {
|
if sameEventUTQueryTT(date, jde) {
|
||||||
oppositionJD := marsConjunctionFull(jde, 180, 0)
|
sameOppositionJD := marsOppositionFromBefore(lastOppositionJD)
|
||||||
return marsRetrograde(oppositionJD-10, false)
|
return closestEventUTToQueryTT(jde, date, marsRetrogradeAroundOpposition(sameOppositionJD, false))
|
||||||
|
}
|
||||||
|
if !eventUTQueryBeforeOrEqual(date, jde) {
|
||||||
|
previousOppositionJD := marsConjunctionFull(eventUTLastQueryTT(lastOppositionJD), 180, 0)
|
||||||
|
return marsRetrogradeAroundOpposition(previousOppositionJD, false)
|
||||||
}
|
}
|
||||||
return date
|
return date
|
||||||
}
|
}
|
||||||
|
|
||||||
func NextMarsProgradeToRetrograde(jde float64) float64 {
|
func NextMarsProgradeToRetrograde(jde float64) float64 {
|
||||||
date := marsRetrograde(jde, true)
|
nextOppositionJD := marsConjunctionFull(jde, 180, 1)
|
||||||
if date < jde {
|
date := marsRetrogradeAroundOpposition(nextOppositionJD, true)
|
||||||
oppositionJD := marsConjunctionFull(jde, 180, 1)
|
if sameEventUTQueryTT(date, jde) {
|
||||||
return marsRetrograde(oppositionJD+10, true)
|
sameOppositionJD := marsOppositionFromAfter(nextOppositionJD)
|
||||||
|
return closestEventUTToQueryTT(jde, date, marsRetrogradeAroundOpposition(sameOppositionJD, true))
|
||||||
|
}
|
||||||
|
if !eventUTQueryAfterOrEqual(date, jde) {
|
||||||
|
followingOppositionJD := marsConjunctionFull(eventUTNextQueryTT(nextOppositionJD), 180, 1)
|
||||||
|
return marsRetrogradeAroundOpposition(followingOppositionJD, true)
|
||||||
}
|
}
|
||||||
return date
|
return date
|
||||||
}
|
}
|
||||||
|
|
||||||
func LastMarsProgradeToRetrograde(jde float64) float64 {
|
func LastMarsProgradeToRetrograde(jde float64) float64 {
|
||||||
jde = marsConjunctionFull(jde, 180, 0) - 10
|
nextOppositionJD := marsConjunctionFull(jde, 180, 1)
|
||||||
date := marsRetrograde(jde, true)
|
date := marsRetrogradeAroundOpposition(nextOppositionJD, true)
|
||||||
if date > jde {
|
if sameEventUTQueryTT(date, jde) {
|
||||||
oppositionJD := marsConjunctionFull(jde, 180, 0)
|
sameOppositionJD := marsOppositionFromAfter(nextOppositionJD)
|
||||||
return marsRetrograde(oppositionJD-10, true)
|
return closestEventUTToQueryTT(jde, date, marsRetrogradeAroundOpposition(sameOppositionJD, true))
|
||||||
|
}
|
||||||
|
if !eventUTQueryBeforeOrEqual(date, jde) {
|
||||||
|
lastOppositionJD := marsConjunctionFull(jde, 180, 0)
|
||||||
|
return marsRetrogradeAroundOpposition(lastOppositionJD, true)
|
||||||
}
|
}
|
||||||
return date
|
return date
|
||||||
}
|
}
|
||||||
|
|||||||
+7
-37
@@ -80,52 +80,22 @@ func MercuryApparentRaDec(jd float64) (float64, float64) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func EarthMercuryAway(jd float64) float64 {
|
func EarthMercuryAway(jd float64) float64 {
|
||||||
x, y, z := AMercuryXYZ(jd)
|
return planetEarthAwayExplicitN(1, jd, -1)
|
||||||
to := math.Sqrt(x*x + y*y + z*z)
|
|
||||||
return to
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func MercuryApparentLo(jd float64) float64 {
|
func MercuryApparentLo(jd float64) float64 {
|
||||||
x, y, z := AMercuryXYZ(jd)
|
geo, _ := planetApparentGeocentricPositionN(1, jd, -1)
|
||||||
to := 0.0057755183 * math.Sqrt(x*x+y*y+z*z)
|
return geo.lo
|
||||||
x, y, z = AMercuryXYZ(jd - to)
|
|
||||||
lo := math.Atan2(y, x)
|
|
||||||
bo := math.Atan2(z, math.Sqrt(x*x+y*y))
|
|
||||||
lo = lo * 180 / math.Pi
|
|
||||||
bo = bo * 180 / math.Pi
|
|
||||||
lo = Limit360(lo)
|
|
||||||
//lo-=GXCLo(lo,bo,jd)/3600;
|
|
||||||
//bo+=GXCBo(lo,bo,jd);
|
|
||||||
lo += Nutation2000Bi(jd)
|
|
||||||
return lo
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func MercuryApparentBo(jd float64) float64 {
|
func MercuryApparentBo(jd float64) float64 {
|
||||||
x, y, z := AMercuryXYZ(jd)
|
geo, _ := planetApparentGeocentricPositionN(1, jd, -1)
|
||||||
to := 0.0057755183 * math.Sqrt(x*x+y*y+z*z)
|
return geo.bo
|
||||||
x, y, z = AMercuryXYZ(jd - to)
|
|
||||||
//lo := math.Atan2(y, x)
|
|
||||||
bo := math.Atan2(z, math.Sqrt(x*x+y*y))
|
|
||||||
//lo = lo * 180 / math.Pi
|
|
||||||
bo = bo * 180 / math.Pi
|
|
||||||
//lo+=GXCLo(lo,bo,jd);
|
|
||||||
//bo+=GXCBo(lo,bo,jd)/3600;
|
|
||||||
//lo+=Nutation2000Bi(jd);
|
|
||||||
return bo
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func MercuryApparentLoBo(jd float64) (float64, float64) {
|
func MercuryApparentLoBo(jd float64) (float64, float64) {
|
||||||
x, y, z := AMercuryXYZ(jd)
|
geo, _ := planetApparentGeocentricPositionN(1, jd, -1)
|
||||||
to := 0.0057755183 * math.Sqrt(x*x+y*y+z*z)
|
return geo.lo, geo.bo
|
||||||
x, y, z = AMercuryXYZ(jd - to)
|
|
||||||
lo := math.Atan2(y, x)
|
|
||||||
bo := math.Atan2(z, math.Sqrt(x*x+y*y))
|
|
||||||
lo = lo * 180 / math.Pi
|
|
||||||
bo = bo * 180 / math.Pi
|
|
||||||
lo = Limit360(lo) + Nutation2000Bi(jd)
|
|
||||||
//lo-=GXCLo(lo,bo,jd)/3600;
|
|
||||||
//bo+=GXCBo(lo,bo,jd);
|
|
||||||
return lo, bo
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func MercuryMag(jd float64) float64 {
|
func MercuryMag(jd float64) float64 {
|
||||||
|
|||||||
+268
-90
@@ -11,6 +11,7 @@ const (
|
|||||||
MERCURY_S_PERIOD = 1 / ((1 / 87.9691) - (1 / 365.256363004))
|
MERCURY_S_PERIOD = 1 / ((1 / 87.9691) - (1 / 365.256363004))
|
||||||
mercuryConjunctionDerivativeStepDay = 2e-5 * 36525.0
|
mercuryConjunctionDerivativeStepDay = 2e-5 * 36525.0
|
||||||
mercuryLightTimeDaysPerAU = 0.0057755183
|
mercuryLightTimeDaysPerAU = 0.0057755183
|
||||||
|
mercuryEventSearchN = 16
|
||||||
)
|
)
|
||||||
|
|
||||||
type mercuryConjunctionLBR struct {
|
type mercuryConjunctionLBR struct {
|
||||||
@@ -206,41 +207,49 @@ func mercuryConjunction(jde float64, next uint8) float64 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func LastMercuryConjunction(jde float64) float64 {
|
func LastMercuryConjunction(jde float64) float64 {
|
||||||
return mercuryConjunction(jde, 0)
|
return inclusiveLastSimpleEvent(jde, LastMercuryConjunctionStrict, NextMercuryConjunctionStrict)
|
||||||
}
|
}
|
||||||
|
|
||||||
func NextMercuryConjunction(jde float64) float64 {
|
func NextMercuryConjunction(jde float64) float64 {
|
||||||
|
return inclusiveNextSimpleEvent(jde, LastMercuryConjunctionStrict, NextMercuryConjunctionStrict)
|
||||||
|
}
|
||||||
|
|
||||||
|
func LastMercuryConjunctionStrict(jde float64) float64 {
|
||||||
|
return mercuryConjunction(jde, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NextMercuryConjunctionStrict(jde float64) float64 {
|
||||||
return mercuryConjunction(jde, 1)
|
return mercuryConjunction(jde, 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
func NextMercuryInferiorConjunction(jde float64) float64 {
|
func NextMercuryInferiorConjunction(jde float64) float64 {
|
||||||
date := NextMercuryConjunction(jde)
|
date := NextMercuryConjunctionStrict(jde)
|
||||||
if EarthMercuryAway(date) > EarthAway(date) {
|
if EarthMercuryAway(date) > EarthAway(date) {
|
||||||
return NextMercuryConjunction(date + 2)
|
return NextMercuryConjunctionStrict(date + 2)
|
||||||
}
|
}
|
||||||
return date
|
return date
|
||||||
}
|
}
|
||||||
|
|
||||||
func NextMercurySuperiorConjunction(jde float64) float64 {
|
func NextMercurySuperiorConjunction(jde float64) float64 {
|
||||||
date := NextMercuryConjunction(jde)
|
date := NextMercuryConjunctionStrict(jde)
|
||||||
if EarthMercuryAway(date) < EarthAway(date) {
|
if EarthMercuryAway(date) < EarthAway(date) {
|
||||||
return NextMercuryConjunction(date + 2)
|
return NextMercuryConjunctionStrict(date + 2)
|
||||||
}
|
}
|
||||||
return date
|
return date
|
||||||
}
|
}
|
||||||
|
|
||||||
func LastMercuryInferiorConjunction(jde float64) float64 {
|
func LastMercuryInferiorConjunction(jde float64) float64 {
|
||||||
date := LastMercuryConjunction(jde)
|
date := LastMercuryConjunctionStrict(jde)
|
||||||
if EarthMercuryAway(date) > EarthAway(date) {
|
if EarthMercuryAway(date) > EarthAway(date) {
|
||||||
return LastMercuryConjunction(date - 2)
|
return LastMercuryConjunctionStrict(date - 2)
|
||||||
}
|
}
|
||||||
return date
|
return date
|
||||||
}
|
}
|
||||||
|
|
||||||
func LastMercurySuperiorConjunction(jde float64) float64 {
|
func LastMercurySuperiorConjunction(jde float64) float64 {
|
||||||
date := LastMercuryConjunction(jde)
|
date := LastMercuryConjunctionStrict(jde)
|
||||||
if EarthMercuryAway(date) < EarthAway(date) {
|
if EarthMercuryAway(date) < EarthAway(date) {
|
||||||
return LastMercuryConjunction(date - 2)
|
return LastMercuryConjunctionStrict(date - 2)
|
||||||
}
|
}
|
||||||
return date
|
return date
|
||||||
}
|
}
|
||||||
@@ -257,16 +266,6 @@ func mercuryRetrograde(jde float64) float64 {
|
|||||||
}
|
}
|
||||||
return sub
|
return sub
|
||||||
}
|
}
|
||||||
raRate := func(jde float64, delta float64) float64 {
|
|
||||||
sub := MercuryApparentRa(jde+delta) - MercuryApparentRa(jde-delta)
|
|
||||||
if sub > 180 {
|
|
||||||
sub -= 360
|
|
||||||
}
|
|
||||||
if sub < -180 {
|
|
||||||
sub += 360
|
|
||||||
}
|
|
||||||
return sub / (2 * delta)
|
|
||||||
}
|
|
||||||
lastConjunction := mercuryConjunctionLegacy(jde, 0)
|
lastConjunction := mercuryConjunctionLegacy(jde, 0)
|
||||||
nextConjunction := mercuryConjunctionLegacy(jde, 1)
|
nextConjunction := mercuryConjunctionLegacy(jde, 1)
|
||||||
currentRADelta := solarRADelta(jde)
|
currentRADelta := solarRADelta(jde)
|
||||||
@@ -276,7 +275,7 @@ func mercuryRetrograde(jde float64) float64 {
|
|||||||
jde = lastConjunction + ((nextConjunction - lastConjunction) / 5.5)
|
jde = lastConjunction + ((nextConjunction - lastConjunction) / 5.5)
|
||||||
}
|
}
|
||||||
for {
|
for {
|
||||||
currentRate := raRate(jde, 1.0/86400.0)
|
currentRate := mercuryRADerivative(jde, 1.0/86400.0)
|
||||||
if math.Abs(currentRate) > 0.55 {
|
if math.Abs(currentRate) > 0.55 {
|
||||||
jde += 2
|
jde += 2
|
||||||
continue
|
continue
|
||||||
@@ -286,82 +285,212 @@ func mercuryRetrograde(jde float64) float64 {
|
|||||||
estimateJD := jde
|
estimateJD := jde
|
||||||
for {
|
for {
|
||||||
prevJD := estimateJD
|
prevJD := estimateJD
|
||||||
rateValue := raRate(prevJD, 2.0/86400.0)
|
rateValue := mercuryRADerivative(prevJD, 2.0/86400.0)
|
||||||
rateSlope := (raRate(prevJD+15.0/86400.0, 2.0/86400.0) - raRate(prevJD-15.0/86400.0, 2.0/86400.0)) / (30.0 / 86400.0)
|
rateSlope := (mercuryRADerivative(prevJD+15.0/86400.0, 2.0/86400.0) - mercuryRADerivative(prevJD-15.0/86400.0, 2.0/86400.0)) / (30.0 / 86400.0)
|
||||||
estimateJD = prevJD - rateValue/rateSlope
|
estimateJD = prevJD - rateValue/rateSlope
|
||||||
if math.Abs(estimateJD-prevJD) <= 30.0/86400.0 {
|
if math.Abs(estimateJD-prevJD) <= 30.0/86400.0 {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
bestJD := eventZeroRefine(estimateJD, 15.0/86400.0, 0.5/86400.0, func(jd float64) float64 {
|
bestJD := eventZeroRefine(estimateJD, 15.0/86400.0, 0.5/86400.0, func(jd float64) float64 {
|
||||||
return raRate(jd, 0.5/86400.0)
|
return mercuryRADerivative(jd, 0.5/86400.0)
|
||||||
})
|
})
|
||||||
//fmt.Println((bestJD - lastConjunction) / (nextConjunction - lastConjunction))
|
//fmt.Println((bestJD - lastConjunction) / (nextConjunction - lastConjunction))
|
||||||
return TD2UT(bestJD, false)
|
return TD2UT(bestJD, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func mercuryRADerivative(jde, delta float64) float64 {
|
||||||
|
sub := MercuryApparentRa(jde+delta) - MercuryApparentRa(jde-delta)
|
||||||
|
if sub > 180 {
|
||||||
|
sub -= 360
|
||||||
|
}
|
||||||
|
if sub < -180 {
|
||||||
|
sub += 360
|
||||||
|
}
|
||||||
|
return sub / (2 * delta)
|
||||||
|
}
|
||||||
|
|
||||||
|
func mercuryStationIsProgradeToRetrograde(eventUT float64) bool {
|
||||||
|
for _, offset := range []float64{0.25, 0.5, 1.0} {
|
||||||
|
before := mercuryRADerivative(eventUT-offset, 0.5/86400.0)
|
||||||
|
after := mercuryRADerivative(eventUT+offset, 0.5/86400.0)
|
||||||
|
if before > 0 && after < 0 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if before < 0 && after > 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
before := mercuryRADerivative(eventUT-0.25, 0.5/86400.0)
|
||||||
|
after := mercuryRADerivative(eventUT+0.25, 0.5/86400.0)
|
||||||
|
return before > after
|
||||||
|
}
|
||||||
|
|
||||||
|
func nextMercuryTypedStation(jde float64, progradeToRetrograde bool) float64 {
|
||||||
|
date := NextMercuryRetrogradeStrict(jde)
|
||||||
|
for mercuryStationIsProgradeToRetrograde(date) != progradeToRetrograde {
|
||||||
|
date = NextMercuryRetrogradeStrict(eventUTNextQueryTT(date))
|
||||||
|
}
|
||||||
|
return date
|
||||||
|
}
|
||||||
|
|
||||||
|
func lastMercuryTypedStation(jde float64, progradeToRetrograde bool) float64 {
|
||||||
|
date := LastMercuryRetrogradeStrict(jde)
|
||||||
|
for mercuryStationIsProgradeToRetrograde(date) != progradeToRetrograde {
|
||||||
|
date = LastMercuryRetrogradeStrict(eventUTLastQueryTT(date))
|
||||||
|
}
|
||||||
|
return date
|
||||||
|
}
|
||||||
|
|
||||||
func NextMercuryRetrograde(jde float64) float64 {
|
func NextMercuryRetrograde(jde float64) float64 {
|
||||||
date := mercuryRetrograde(jde)
|
date := mercuryRetrograde(jde)
|
||||||
if date < jde {
|
if !eventUTQueryAfterOrEqual(date, jde) {
|
||||||
nextConjunction := mercuryConjunctionLegacy(jde, 1)
|
nextConjunction := NextMercuryConjunctionStrict(jde)
|
||||||
return mercuryRetrograde(nextConjunction + 2)
|
return mercuryRetrograde(nextConjunction + 2)
|
||||||
}
|
}
|
||||||
return date
|
return date
|
||||||
}
|
}
|
||||||
|
|
||||||
func LastMercuryRetrograde(jde float64) float64 {
|
func LastMercuryRetrograde(jde float64) float64 {
|
||||||
lastConjunction := mercuryConjunctionLegacy(jde, 0)
|
lastConjunction := LastMercuryConjunctionStrict(jde)
|
||||||
date := mercuryRetrograde(lastConjunction + 2)
|
date := mercuryRetrograde(lastConjunction + 2)
|
||||||
if date > jde {
|
if !eventUTQueryBeforeOrEqual(date, jde) {
|
||||||
previousConjunction := mercuryConjunctionLegacy(lastConjunction-2, 0)
|
previousConjunction := LastMercuryConjunctionStrict(eventUTLastQueryTT(lastConjunction))
|
||||||
return mercuryRetrograde(previousConjunction + 2)
|
return mercuryRetrograde(previousConjunction + 2)
|
||||||
}
|
}
|
||||||
return date
|
return date
|
||||||
}
|
}
|
||||||
|
|
||||||
func NextMercuryProgradeToRetrograde(jde float64) float64 {
|
func LastMercuryRetrogradeStrict(jde float64) float64 {
|
||||||
date := NextMercuryRetrograde(jde)
|
return LastMercuryRetrograde(jde)
|
||||||
sub := Limit360(MercuryApparentRa(date) - SunApparentRa(date))
|
|
||||||
if sub > 180 {
|
|
||||||
return NextMercuryRetrograde(date + MERCURY_S_PERIOD/2)
|
|
||||||
}
|
}
|
||||||
return date
|
|
||||||
|
func NextMercuryRetrogradeStrict(jde float64) float64 {
|
||||||
|
return NextMercuryRetrograde(jde)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NextMercuryProgradeToRetrograde(jde float64) float64 {
|
||||||
|
return nextMercuryTypedStation(jde, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
func NextMercuryRetrogradeToPrograde(jde float64) float64 {
|
func NextMercuryRetrogradeToPrograde(jde float64) float64 {
|
||||||
date := NextMercuryRetrograde(jde)
|
return nextMercuryTypedStation(jde, false)
|
||||||
sub := Limit360(MercuryApparentRa(date) - SunApparentRa(date))
|
|
||||||
if sub < 180 {
|
|
||||||
return NextMercuryRetrograde(date + 12)
|
|
||||||
}
|
|
||||||
return date
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func LastMercuryProgradeToRetrograde(jde float64) float64 {
|
func LastMercuryProgradeToRetrograde(jde float64) float64 {
|
||||||
date := LastMercuryRetrograde(jde)
|
return lastMercuryTypedStation(jde, true)
|
||||||
sub := Limit360(MercuryApparentRa(date) - SunApparentRa(date))
|
|
||||||
if sub > 180 {
|
|
||||||
return LastMercuryRetrograde(date - 12)
|
|
||||||
}
|
|
||||||
return date
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func LastMercuryRetrogradeToPrograde(jde float64) float64 {
|
func LastMercuryRetrogradeToPrograde(jde float64) float64 {
|
||||||
date := LastMercuryRetrograde(jde)
|
return lastMercuryTypedStation(jde, false)
|
||||||
sub := Limit360(MercuryApparentRa(date) - SunApparentRa(date))
|
|
||||||
if sub < 180 {
|
|
||||||
return LastMercuryRetrograde(date - MERCURY_S_PERIOD/2)
|
|
||||||
}
|
|
||||||
return date
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func MercurySunElongation(jde float64) float64 {
|
func MercurySunElongation(jde float64) float64 {
|
||||||
lo1, bo1 := MercuryApparentLoBo(jde)
|
lo1, bo1 := MercuryApparentLoBo(jde)
|
||||||
lo2 := SunApparentLo(jde)
|
lo2 := HSunApparentLo(jde)
|
||||||
bo2 := HSunTrueBo(jde)
|
bo2 := HSunTrueBo(jde)
|
||||||
return StarAngularSeparation(lo1, bo1, lo2, bo2)
|
return StarAngularSeparation(lo1, bo1, lo2, bo2)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func mercurySunElongationN(jde float64, n int) float64 {
|
||||||
|
lo1, bo1 := MercuryApparentLoBoN(jde, n)
|
||||||
|
lo2 := HSunApparentLoN(jde, n)
|
||||||
|
bo2 := HSunTrueBoN(jde, n)
|
||||||
|
return StarAngularSeparation(lo1, bo1, lo2, bo2)
|
||||||
|
}
|
||||||
|
|
||||||
|
func mercuryTrueElongationN(jde float64, n int) float64 {
|
||||||
|
earth := mercuryHelioN(-1, jde, n)
|
||||||
|
planetPos := mercuryHelioN(1, jde, n)
|
||||||
|
geo := mercuryGeocentric(planetPos, earth)
|
||||||
|
return StarAngularSeparation(geo.lo, geo.bo, HSunTrueLoN(jde, n), HSunTrueBoN(jde, n))
|
||||||
|
}
|
||||||
|
|
||||||
|
func mercuryGreatestElongationInWindow(start, end float64) float64 {
|
||||||
|
best := maximizeInWindow(start, end, 2.0, func(jd float64) float64 {
|
||||||
|
return mercuryTrueElongationN(jd, mercuryEventSearchN)
|
||||||
|
}, func(jd float64) float64 {
|
||||||
|
return mercuryTrueElongationN(jd, -1)
|
||||||
|
})
|
||||||
|
return TD2UT(best, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
func mercuryEastElongationWindowEndingAt(inferior float64) (float64, float64) {
|
||||||
|
lastSuperior := LastMercurySuperiorConjunction(eventUTLastQueryTT(inferior))
|
||||||
|
return lastSuperior + innerEventEpsilon, inferior - innerEventEpsilon
|
||||||
|
}
|
||||||
|
|
||||||
|
func mercuryWestElongationWindowEndingAt(superior float64) (float64, float64) {
|
||||||
|
lastInferior := LastMercuryInferiorConjunction(eventUTLastQueryTT(superior))
|
||||||
|
return lastInferior + innerEventEpsilon, superior - innerEventEpsilon
|
||||||
|
}
|
||||||
|
|
||||||
|
func mercuryEastElongationWindowContaining(jde float64) (float64, float64) {
|
||||||
|
nextInferior := NextMercuryInferiorConjunction(jde)
|
||||||
|
start, end := mercuryEastElongationWindowEndingAt(nextInferior)
|
||||||
|
if eventUTQueryBeforeOrEqual(start, jde) {
|
||||||
|
return start, end
|
||||||
|
}
|
||||||
|
currentInferior := LastMercuryInferiorConjunction(jde)
|
||||||
|
return mercuryEastElongationWindowEndingAt(currentInferior)
|
||||||
|
}
|
||||||
|
|
||||||
|
func mercuryWestElongationWindowContaining(jde float64) (float64, float64) {
|
||||||
|
nextSuperior := NextMercurySuperiorConjunction(jde)
|
||||||
|
start, end := mercuryWestElongationWindowEndingAt(nextSuperior)
|
||||||
|
if eventUTQueryBeforeOrEqual(start, jde) {
|
||||||
|
return start, end
|
||||||
|
}
|
||||||
|
currentSuperior := LastMercurySuperiorConjunction(jde)
|
||||||
|
return mercuryWestElongationWindowEndingAt(currentSuperior)
|
||||||
|
}
|
||||||
|
|
||||||
|
func nextMercuryGreatestElongationTyped(jde float64, east bool) float64 {
|
||||||
|
if east {
|
||||||
|
start, windowEnd := mercuryEastElongationWindowContaining(jde)
|
||||||
|
for {
|
||||||
|
date := mercuryGreatestElongationInWindow(start, windowEnd)
|
||||||
|
if eventUTQueryAfterOrEqual(date, jde) {
|
||||||
|
return date
|
||||||
|
}
|
||||||
|
nextInferior := NextMercuryInferiorConjunction(eventUTNextQueryTT(windowEnd))
|
||||||
|
start, windowEnd = mercuryEastElongationWindowEndingAt(nextInferior)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
start, windowEnd := mercuryWestElongationWindowContaining(jde)
|
||||||
|
for {
|
||||||
|
date := mercuryGreatestElongationInWindow(start, windowEnd)
|
||||||
|
if eventUTQueryAfterOrEqual(date, jde) {
|
||||||
|
return date
|
||||||
|
}
|
||||||
|
nextSuperior := NextMercurySuperiorConjunction(eventUTNextQueryTT(windowEnd))
|
||||||
|
start, windowEnd = mercuryWestElongationWindowEndingAt(nextSuperior)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func lastMercuryGreatestElongationTyped(jde float64, east bool) float64 {
|
||||||
|
if east {
|
||||||
|
start, windowEnd := mercuryEastElongationWindowContaining(jde)
|
||||||
|
for {
|
||||||
|
date := mercuryGreatestElongationInWindow(start, windowEnd)
|
||||||
|
if eventUTQueryBeforeOrEqual(date, jde) {
|
||||||
|
return date
|
||||||
|
}
|
||||||
|
prevInferior := LastMercuryInferiorConjunction(eventUTLastQueryTT(start))
|
||||||
|
start, windowEnd = mercuryEastElongationWindowEndingAt(prevInferior)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
start, windowEnd := mercuryWestElongationWindowContaining(jde)
|
||||||
|
for {
|
||||||
|
date := mercuryGreatestElongationInWindow(start, windowEnd)
|
||||||
|
if eventUTQueryBeforeOrEqual(date, jde) {
|
||||||
|
return date
|
||||||
|
}
|
||||||
|
prevSuperior := LastMercurySuperiorConjunction(eventUTLastQueryTT(start))
|
||||||
|
start, windowEnd = mercuryWestElongationWindowEndingAt(prevSuperior)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func mercuryGreatestElongation(jde float64) float64 {
|
func mercuryGreatestElongation(jde float64) float64 {
|
||||||
solarRADelta := func(jde float64) float64 {
|
solarRADelta := func(jde float64) float64 {
|
||||||
sub := Limit360(MercuryApparentRa(jde) - SunApparentRa(jde))
|
sub := Limit360(MercuryApparentRa(jde) - SunApparentRa(jde))
|
||||||
@@ -383,8 +512,8 @@ func mercuryGreatestElongation(jde float64) float64 {
|
|||||||
}
|
}
|
||||||
return sub / (2 * delta)
|
return sub / (2 * delta)
|
||||||
}
|
}
|
||||||
lastConjunction := mercuryConjunctionLegacy(jde, 0)
|
lastConjunction := LastMercuryConjunctionStrict(jde)
|
||||||
nextConjunction := mercuryConjunctionLegacy(jde, 1)
|
nextConjunction := NextMercuryConjunctionStrict(jde)
|
||||||
currentRADelta := solarRADelta(jde)
|
currentRADelta := solarRADelta(jde)
|
||||||
if currentRADelta > 0 {
|
if currentRADelta > 0 {
|
||||||
jde = lastConjunction + ((nextConjunction - lastConjunction) / 5.0 * 2.0)
|
jde = lastConjunction + ((nextConjunction - lastConjunction) / 5.0 * 2.0)
|
||||||
@@ -417,56 +546,105 @@ func mercuryGreatestElongation(jde float64) float64 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func NextMercuryGreatestElongation(jde float64) float64 {
|
func NextMercuryGreatestElongation(jde float64) float64 {
|
||||||
date := mercuryGreatestElongation(jde)
|
east := NextMercuryGreatestElongationEast(jde)
|
||||||
if date < jde {
|
west := NextMercuryGreatestElongationWest(jde)
|
||||||
nextConjunction := mercuryConjunctionLegacy(jde, 1)
|
if sameEventJD(east, west) {
|
||||||
return mercuryGreatestElongation(nextConjunction + 2)
|
return east
|
||||||
}
|
}
|
||||||
return date
|
if east < west {
|
||||||
|
return east
|
||||||
|
}
|
||||||
|
return west
|
||||||
}
|
}
|
||||||
|
|
||||||
func LastMercuryGreatestElongation(jde float64) float64 {
|
func LastMercuryGreatestElongation(jde float64) float64 {
|
||||||
lastConjunction := mercuryConjunctionLegacy(jde, 0)
|
east := LastMercuryGreatestElongationEast(jde)
|
||||||
date := mercuryGreatestElongation(lastConjunction + 2)
|
west := LastMercuryGreatestElongationWest(jde)
|
||||||
if date > jde {
|
if sameEventJD(east, west) {
|
||||||
previousConjunction := mercuryConjunctionLegacy(lastConjunction-2, 0)
|
return east
|
||||||
return mercuryGreatestElongation(previousConjunction + 2)
|
|
||||||
}
|
}
|
||||||
return date
|
if east > west {
|
||||||
|
return east
|
||||||
|
}
|
||||||
|
return west
|
||||||
|
}
|
||||||
|
|
||||||
|
func LastMercuryInferiorConjunctionInclusive(jde float64) float64 {
|
||||||
|
return inclusiveLastSimpleEvent(jde, LastMercuryInferiorConjunction, NextMercuryInferiorConjunction)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NextMercuryInferiorConjunctionInclusive(jde float64) float64 {
|
||||||
|
return inclusiveNextSimpleEvent(jde, LastMercuryInferiorConjunction, NextMercuryInferiorConjunction)
|
||||||
|
}
|
||||||
|
|
||||||
|
func LastMercurySuperiorConjunctionInclusive(jde float64) float64 {
|
||||||
|
return inclusiveLastSimpleEvent(jde, LastMercurySuperiorConjunction, NextMercurySuperiorConjunction)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NextMercurySuperiorConjunctionInclusive(jde float64) float64 {
|
||||||
|
return inclusiveNextSimpleEvent(jde, LastMercurySuperiorConjunction, NextMercurySuperiorConjunction)
|
||||||
|
}
|
||||||
|
|
||||||
|
func LastMercuryRetrogradeInclusive(jde float64) float64 {
|
||||||
|
return inclusiveLastSimpleEvent(jde, LastMercuryRetrograde, NextMercuryRetrograde)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NextMercuryRetrogradeInclusive(jde float64) float64 {
|
||||||
|
return inclusiveNextSimpleEvent(jde, LastMercuryRetrograde, NextMercuryRetrograde)
|
||||||
|
}
|
||||||
|
|
||||||
|
func LastMercuryProgradeToRetrogradeInclusive(jde float64) float64 {
|
||||||
|
return inclusiveLastSimpleEvent(jde, LastMercuryProgradeToRetrograde, NextMercuryProgradeToRetrograde)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NextMercuryProgradeToRetrogradeInclusive(jde float64) float64 {
|
||||||
|
return inclusiveNextSimpleEvent(jde, LastMercuryProgradeToRetrograde, NextMercuryProgradeToRetrograde)
|
||||||
|
}
|
||||||
|
|
||||||
|
func LastMercuryRetrogradeToProgradeInclusive(jde float64) float64 {
|
||||||
|
return inclusiveLastSimpleEvent(jde, LastMercuryRetrogradeToPrograde, NextMercuryRetrogradeToPrograde)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NextMercuryRetrogradeToProgradeInclusive(jde float64) float64 {
|
||||||
|
return inclusiveNextSimpleEvent(jde, LastMercuryRetrogradeToPrograde, NextMercuryRetrogradeToPrograde)
|
||||||
|
}
|
||||||
|
|
||||||
|
func LastMercuryGreatestElongationInclusive(jde float64) float64 {
|
||||||
|
return inclusiveLastSimpleEvent(jde, LastMercuryGreatestElongation, NextMercuryGreatestElongation)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NextMercuryGreatestElongationInclusive(jde float64) float64 {
|
||||||
|
return inclusiveNextSimpleEvent(jde, LastMercuryGreatestElongation, NextMercuryGreatestElongation)
|
||||||
|
}
|
||||||
|
|
||||||
|
func LastMercuryGreatestElongationEastInclusive(jde float64) float64 {
|
||||||
|
return inclusiveLastSimpleEvent(jde, LastMercuryGreatestElongationEast, NextMercuryGreatestElongationEast)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NextMercuryGreatestElongationEastInclusive(jde float64) float64 {
|
||||||
|
return inclusiveNextSimpleEvent(jde, LastMercuryGreatestElongationEast, NextMercuryGreatestElongationEast)
|
||||||
|
}
|
||||||
|
|
||||||
|
func LastMercuryGreatestElongationWestInclusive(jde float64) float64 {
|
||||||
|
return inclusiveLastSimpleEvent(jde, LastMercuryGreatestElongationWest, NextMercuryGreatestElongationWest)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NextMercuryGreatestElongationWestInclusive(jde float64) float64 {
|
||||||
|
return inclusiveNextSimpleEvent(jde, LastMercuryGreatestElongationWest, NextMercuryGreatestElongationWest)
|
||||||
}
|
}
|
||||||
|
|
||||||
func NextMercuryGreatestElongationEast(jde float64) float64 {
|
func NextMercuryGreatestElongationEast(jde float64) float64 {
|
||||||
date := NextMercuryGreatestElongation(jde)
|
return nextMercuryGreatestElongationTyped(jde, true)
|
||||||
sub := Limit360(MercuryApparentRa(date) - SunApparentRa(date))
|
|
||||||
if sub > 180 {
|
|
||||||
return NextMercuryGreatestElongation(date + 1)
|
|
||||||
}
|
|
||||||
return date
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NextMercuryGreatestElongationWest(jde float64) float64 {
|
func NextMercuryGreatestElongationWest(jde float64) float64 {
|
||||||
date := NextMercuryGreatestElongation(jde)
|
return nextMercuryGreatestElongationTyped(jde, false)
|
||||||
sub := Limit360(MercuryApparentRa(date) - SunApparentRa(date))
|
|
||||||
if sub < 180 {
|
|
||||||
return NextMercuryGreatestElongation(date + 1)
|
|
||||||
}
|
|
||||||
return date
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func LastMercuryGreatestElongationEast(jde float64) float64 {
|
func LastMercuryGreatestElongationEast(jde float64) float64 {
|
||||||
date := LastMercuryGreatestElongation(jde)
|
return lastMercuryGreatestElongationTyped(jde, true)
|
||||||
sub := Limit360(MercuryApparentRa(date) - SunApparentRa(date))
|
|
||||||
if sub > 180 {
|
|
||||||
return LastMercuryGreatestElongation(date - 1)
|
|
||||||
}
|
|
||||||
return date
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func LastMercuryGreatestElongationWest(jde float64) float64 {
|
func LastMercuryGreatestElongationWest(jde float64) float64 {
|
||||||
date := LastMercuryGreatestElongation(jde)
|
return lastMercuryGreatestElongationTyped(jde, false)
|
||||||
sub := Limit360(MercuryApparentRa(date) - SunApparentRa(date))
|
|
||||||
if sub < 180 {
|
|
||||||
return LastMercuryGreatestElongation(date - 1)
|
|
||||||
}
|
|
||||||
return date
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,40 @@
|
|||||||
|
package basic
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func mercuryTTJDJST(year int, month time.Month, day, hour, minute, second int) float64 {
|
||||||
|
loc := time.FixedZone("JST", 9*3600)
|
||||||
|
return TD2UT(Date2JDE(time.Date(year, month, day, hour, minute, second, 0, loc).UTC()), true)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMercuryTypedStationRegression1929(t *testing.T) {
|
||||||
|
loc := time.FixedZone("JST", 9*3600)
|
||||||
|
const tolerance = 30.0 / 86400.0
|
||||||
|
|
||||||
|
query := mercuryTTJDJST(1929, time.September, 20, 0, 0, 0)
|
||||||
|
wantP2R := mercuryTTJDJST(1929, time.September, 26, 1, 58, 0)
|
||||||
|
wantR2P := mercuryTTJDJST(1929, time.October, 16, 23, 32, 33)
|
||||||
|
|
||||||
|
nextP2R := NextMercuryProgradeToRetrograde(query)
|
||||||
|
nextR2P := NextMercuryRetrogradeToPrograde(query)
|
||||||
|
if math.Abs(nextP2R-wantP2R) > tolerance {
|
||||||
|
t.Fatalf("next P2R mismatch: got %s want %s", JDE2DateByZone(nextP2R, loc, false), JDE2DateByZone(wantP2R, loc, false))
|
||||||
|
}
|
||||||
|
if math.Abs(nextR2P-wantR2P) > tolerance {
|
||||||
|
t.Fatalf("next R2P mismatch: got %s want %s", JDE2DateByZone(nextR2P, loc, false), JDE2DateByZone(wantR2P, loc, false))
|
||||||
|
}
|
||||||
|
|
||||||
|
query = mercuryTTJDJST(1929, time.October, 20, 0, 0, 0)
|
||||||
|
lastP2R := LastMercuryProgradeToRetrograde(query)
|
||||||
|
lastR2P := LastMercuryRetrogradeToPrograde(query)
|
||||||
|
if math.Abs(lastP2R-wantP2R) > tolerance {
|
||||||
|
t.Fatalf("last P2R mismatch: got %s want %s", JDE2DateByZone(lastP2R, loc, false), JDE2DateByZone(wantP2R, loc, false))
|
||||||
|
}
|
||||||
|
if math.Abs(lastR2P-wantR2P) > tolerance {
|
||||||
|
t.Fatalf("last R2P mismatch: got %s want %s", JDE2DateByZone(lastR2P, loc, false), JDE2DateByZone(wantR2P, loc, false))
|
||||||
|
}
|
||||||
|
}
|
||||||
+7
-38
@@ -87,53 +87,22 @@ func NeptuneApparentRaDec(jd float64) (float64, float64) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func EarthNeptuneAway(jd float64) float64 {
|
func EarthNeptuneAway(jd float64) float64 {
|
||||||
x, y, z := ANeptuneXYZ(jd)
|
return planetEarthAwayExplicitN(7, jd, -1)
|
||||||
to := math.Sqrt(x*x + y*y + z*z)
|
|
||||||
return to
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NeptuneApparentLo(jd float64) float64 {
|
func NeptuneApparentLo(jd float64) float64 {
|
||||||
x, y, z := ANeptuneXYZ(jd)
|
geo, _ := planetApparentGeocentricPositionN(7, jd, -1)
|
||||||
to := 0.0057755183 * math.Sqrt(x*x+y*y+z*z)
|
return geo.lo
|
||||||
x, y, z = ANeptuneXYZ(jd - to)
|
|
||||||
lo := math.Atan2(y, x)
|
|
||||||
bo := math.Atan2(z, math.Sqrt(x*x+y*y))
|
|
||||||
lo = lo * 180 / math.Pi
|
|
||||||
bo = bo * 180 / math.Pi
|
|
||||||
lo = Limit360(lo)
|
|
||||||
//lo-=GXCLo(lo,bo,jd)/3600;
|
|
||||||
//bo+=GXCBo(lo,bo,jd);
|
|
||||||
lo += Nutation2000Bi(jd)
|
|
||||||
return lo
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NeptuneApparentBo(jd float64) float64 {
|
func NeptuneApparentBo(jd float64) float64 {
|
||||||
x, y, z := ANeptuneXYZ(jd)
|
geo, _ := planetApparentGeocentricPositionN(7, jd, -1)
|
||||||
to := 0.0057755183 * math.Sqrt(x*x+y*y+z*z)
|
return geo.bo
|
||||||
x, y, z = ANeptuneXYZ(jd - to)
|
|
||||||
//lo := math.Atan2(y, x)
|
|
||||||
bo := math.Atan2(z, math.Sqrt(x*x+y*y))
|
|
||||||
//lo = lo * 180 / math.Pi
|
|
||||||
bo = bo * 180 / math.Pi
|
|
||||||
//lo+=GXCLo(lo,bo,jd);
|
|
||||||
//bo+=GXCBo(lo,bo,jd)/3600;
|
|
||||||
//lo+=Nutation2000Bi(jd);
|
|
||||||
return bo
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NeptuneApparentLoBo(jd float64) (float64, float64) {
|
func NeptuneApparentLoBo(jd float64) (float64, float64) {
|
||||||
x, y, z := ANeptuneXYZ(jd)
|
geo, _ := planetApparentGeocentricPositionN(7, jd, -1)
|
||||||
to := 0.0057755183 * math.Sqrt(x*x+y*y+z*z)
|
return geo.lo, geo.bo
|
||||||
x, y, z = ANeptuneXYZ(jd - to)
|
|
||||||
lo := math.Atan2(y, x)
|
|
||||||
bo := math.Atan2(z, math.Sqrt(x*x+y*y))
|
|
||||||
lo = lo * 180 / math.Pi
|
|
||||||
bo = bo * 180 / math.Pi
|
|
||||||
lo = Limit360(lo)
|
|
||||||
//lo-=GXCLo(lo,bo,jd)/3600;
|
|
||||||
//bo+=GXCBo(lo,bo,jd);
|
|
||||||
lo += Nutation2000Bi(jd)
|
|
||||||
return lo, bo
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NeptuneMag(jd float64) float64 {
|
func NeptuneMag(jd float64) float64 {
|
||||||
|
|||||||
+67
-66
@@ -9,7 +9,7 @@ import (
|
|||||||
// Pos
|
// Pos
|
||||||
|
|
||||||
const (
|
const (
|
||||||
NEPTUNE_S_PERIOD = 1 / ((1 / 365.256363004) - (1 / 4332.59))
|
NEPTUNE_S_PERIOD = 1 / ((1 / 365.256363004) - (1 / 60190.03))
|
||||||
neptuneEventSearchN = 16
|
neptuneEventSearchN = 16
|
||||||
neptunePhaseCoarseTolerance = 30.0 / 86400.0
|
neptunePhaseCoarseTolerance = 30.0 / 86400.0
|
||||||
)
|
)
|
||||||
@@ -40,6 +40,28 @@ func neptuneSunLongitudeDeltaN(jde, degree float64, filter bool, n int) float64
|
|||||||
return sub
|
return sub
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func neptuneRADerivative(jde, delta float64) float64 {
|
||||||
|
sub := NeptuneApparentRa(jde+delta) - NeptuneApparentRa(jde-delta)
|
||||||
|
if sub > 180 {
|
||||||
|
sub -= 360
|
||||||
|
}
|
||||||
|
if sub < -180 {
|
||||||
|
sub += 360
|
||||||
|
}
|
||||||
|
return sub / (2 * delta)
|
||||||
|
}
|
||||||
|
|
||||||
|
func neptuneRADerivativeN(jde, delta float64, n int) float64 {
|
||||||
|
sub := NeptuneApparentRaN(jde+delta, n) - NeptuneApparentRaN(jde-delta, n)
|
||||||
|
if sub > 180 {
|
||||||
|
sub -= 360
|
||||||
|
}
|
||||||
|
if sub < -180 {
|
||||||
|
sub += 360
|
||||||
|
}
|
||||||
|
return sub / (2 * delta)
|
||||||
|
}
|
||||||
|
|
||||||
func neptuneConjunctionFull(jde, degree float64, next uint8) float64 {
|
func neptuneConjunctionFull(jde, degree float64, next uint8) float64 {
|
||||||
//0=last 1=next
|
//0=last 1=next
|
||||||
daysPerDegree := NEPTUNE_S_PERIOD / 360
|
daysPerDegree := NEPTUNE_S_PERIOD / 360
|
||||||
@@ -94,113 +116,92 @@ func neptuneConjunction(jde, degree float64, next uint8) float64 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func LastNeptuneConjunction(jde float64) float64 {
|
func LastNeptuneConjunction(jde float64) float64 {
|
||||||
return neptuneConjunction(jde, 0, 0)
|
return inclusiveLastPhaseEvent(jde, 0, neptuneConjunction)
|
||||||
}
|
}
|
||||||
|
|
||||||
func NextNeptuneConjunction(jde float64) float64 {
|
func NextNeptuneConjunction(jde float64) float64 {
|
||||||
return neptuneConjunction(jde, 0, 1)
|
return inclusiveNextPhaseEvent(jde, 0, neptuneConjunction)
|
||||||
}
|
}
|
||||||
|
|
||||||
func LastNeptuneOpposition(jde float64) float64 {
|
func LastNeptuneOpposition(jde float64) float64 {
|
||||||
return neptuneConjunction(jde, 180, 0)
|
return inclusiveLastPhaseEvent(jde, 180, neptuneConjunction)
|
||||||
}
|
}
|
||||||
|
|
||||||
func NextNeptuneOpposition(jde float64) float64 {
|
func NextNeptuneOpposition(jde float64) float64 {
|
||||||
return neptuneConjunction(jde, 180, 1)
|
return inclusiveNextPhaseEvent(jde, 180, neptuneConjunction)
|
||||||
}
|
}
|
||||||
|
|
||||||
func NextNeptuneEasternQuadrature(jde float64) float64 {
|
func NextNeptuneEasternQuadrature(jde float64) float64 {
|
||||||
return neptuneConjunction(jde, 90, 1)
|
return inclusiveNextPhaseEvent(jde, 90, neptuneConjunction)
|
||||||
}
|
}
|
||||||
|
|
||||||
func LastNeptuneEasternQuadrature(jde float64) float64 {
|
func LastNeptuneEasternQuadrature(jde float64) float64 {
|
||||||
return neptuneConjunction(jde, 90, 0)
|
return inclusiveLastPhaseEvent(jde, 90, neptuneConjunction)
|
||||||
}
|
}
|
||||||
|
|
||||||
func NextNeptuneWesternQuadrature(jde float64) float64 {
|
func NextNeptuneWesternQuadrature(jde float64) float64 {
|
||||||
return neptuneConjunction(jde, 270, 1)
|
return inclusiveNextPhaseEvent(jde, 270, neptuneConjunction)
|
||||||
}
|
}
|
||||||
|
|
||||||
func LastNeptuneWesternQuadrature(jde float64) float64 {
|
func LastNeptuneWesternQuadrature(jde float64) float64 {
|
||||||
return neptuneConjunction(jde, 270, 0)
|
return inclusiveLastPhaseEvent(jde, 270, neptuneConjunction)
|
||||||
}
|
}
|
||||||
|
|
||||||
func neptuneRetrograde(jde float64, searchBeforeOpposition bool) float64 {
|
func neptuneRetrogradeAroundOpposition(oppositionJD float64, searchBeforeOpposition bool) float64 {
|
||||||
//0=last 1=next
|
oppositionTT := TD2UT(oppositionJD, true)
|
||||||
raRate := func(jde float64, delta float64) float64 {
|
startTT := oppositionTT
|
||||||
sub := NeptuneApparentRa(jde+delta) - NeptuneApparentRa(jde-delta)
|
endTT := oppositionTT
|
||||||
if sub > 180 {
|
|
||||||
sub -= 360
|
|
||||||
}
|
|
||||||
if sub < -180 {
|
|
||||||
sub += 360
|
|
||||||
}
|
|
||||||
return sub / (2 * delta)
|
|
||||||
}
|
|
||||||
jde = neptuneConjunctionFull(jde, 180, 1)
|
|
||||||
if searchBeforeOpposition {
|
if searchBeforeOpposition {
|
||||||
jde -= 60
|
easternQuadratureUT := neptuneConjunction(oppositionTT, 90, 0)
|
||||||
|
startTT = TD2UT(easternQuadratureUT, true)
|
||||||
} else {
|
} else {
|
||||||
jde += 60
|
westernQuadratureUT := neptuneConjunction(oppositionTT, 270, 1)
|
||||||
|
endTT = TD2UT(westernQuadratureUT, true)
|
||||||
}
|
}
|
||||||
for {
|
bestJD := zeroEventInWindow(startTT, endTT, 2.0, 2.0, 30.0/86400.0, func(jd float64) float64 {
|
||||||
currentRate := raRate(jde, 1.0/86400.0)
|
return neptuneRADerivativeN(jd, 1.0/86400.0, neptuneEventSearchN)
|
||||||
if math.Abs(currentRate) > 0.55 {
|
}, func(jd float64) float64 {
|
||||||
jde += 2
|
return neptuneRADerivative(jd, 0.5/86400.0)
|
||||||
continue
|
|
||||||
}
|
|
||||||
break
|
|
||||||
}
|
|
||||||
estimateJD := jde
|
|
||||||
for {
|
|
||||||
prevJD := estimateJD
|
|
||||||
rateValue := raRate(prevJD, 2.0/86400.0)
|
|
||||||
rateSlope := (raRate(prevJD+15.0/86400.0, 2.0/86400.0) - raRate(prevJD-15.0/86400.0, 2.0/86400.0)) / (30.0 / 86400.0)
|
|
||||||
estimateJD = prevJD - rateValue/rateSlope
|
|
||||||
if math.Abs(estimateJD-prevJD) <= 30.0/86400.0 {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
bestJD := eventZeroRefine(estimateJD, 15.0/86400.0, 0.5/86400.0, func(jd float64) float64 {
|
|
||||||
return raRate(jd, 0.5/86400.0)
|
|
||||||
})
|
})
|
||||||
return TD2UT(bestJD, false)
|
return TD2UT(bestJD, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
func NextNeptuneRetrogradeToPrograde(jde float64) float64 {
|
func NextNeptuneRetrogradeToPrograde(jde float64) float64 {
|
||||||
date := neptuneRetrograde(jde, false)
|
lastOppositionJD := neptuneConjunctionFull(jde, 180, 0)
|
||||||
if date < jde {
|
date := neptuneRetrogradeAroundOpposition(lastOppositionJD, false)
|
||||||
oppositionJD := neptuneConjunctionFull(jde, 180, 1)
|
if sameEventUTQueryTT(date, jde) || eventUTQueryAfterOrEqual(date, jde) {
|
||||||
return neptuneRetrograde(oppositionJD+10, false)
|
|
||||||
}
|
|
||||||
return date
|
return date
|
||||||
}
|
}
|
||||||
|
nextOppositionJD := neptuneConjunctionFull(jde, 180, 1)
|
||||||
|
return neptuneRetrogradeAroundOpposition(nextOppositionJD, false)
|
||||||
|
}
|
||||||
|
|
||||||
func LastNeptuneRetrogradeToPrograde(jde float64) float64 {
|
func LastNeptuneRetrogradeToPrograde(jde float64) float64 {
|
||||||
jde = neptuneConjunctionFull(jde, 180, 0) - 10
|
lastOppositionJD := neptuneConjunctionFull(jde, 180, 0)
|
||||||
date := neptuneRetrograde(jde, false)
|
date := neptuneRetrogradeAroundOpposition(lastOppositionJD, false)
|
||||||
if date > jde {
|
if sameEventUTQueryTT(date, jde) || eventUTQueryBeforeOrEqual(date, jde) {
|
||||||
oppositionJD := neptuneConjunctionFull(jde, 180, 0)
|
|
||||||
return neptuneRetrograde(oppositionJD-10, false)
|
|
||||||
}
|
|
||||||
return date
|
return date
|
||||||
}
|
}
|
||||||
|
previousOppositionJD := neptuneConjunctionFull(eventUTLastQueryTT(lastOppositionJD), 180, 0)
|
||||||
|
return neptuneRetrogradeAroundOpposition(previousOppositionJD, false)
|
||||||
|
}
|
||||||
|
|
||||||
func NextNeptuneProgradeToRetrograde(jde float64) float64 {
|
func NextNeptuneProgradeToRetrograde(jde float64) float64 {
|
||||||
date := neptuneRetrograde(jde, true)
|
nextOppositionJD := neptuneConjunctionFull(jde, 180, 1)
|
||||||
if date < jde {
|
date := neptuneRetrogradeAroundOpposition(nextOppositionJD, true)
|
||||||
oppositionJD := neptuneConjunctionFull(jde, 180, 1)
|
if sameEventUTQueryTT(date, jde) || eventUTQueryAfterOrEqual(date, jde) {
|
||||||
return neptuneRetrograde(oppositionJD+10, true)
|
|
||||||
}
|
|
||||||
return date
|
return date
|
||||||
}
|
}
|
||||||
|
followingOppositionJD := neptuneConjunctionFull(eventUTNextQueryTT(nextOppositionJD), 180, 1)
|
||||||
|
return neptuneRetrogradeAroundOpposition(followingOppositionJD, true)
|
||||||
|
}
|
||||||
|
|
||||||
func LastNeptuneProgradeToRetrograde(jde float64) float64 {
|
func LastNeptuneProgradeToRetrograde(jde float64) float64 {
|
||||||
jde = neptuneConjunctionFull(jde, 180, 0) - 10
|
nextOppositionJD := neptuneConjunctionFull(jde, 180, 1)
|
||||||
date := neptuneRetrograde(jde, true)
|
date := neptuneRetrogradeAroundOpposition(nextOppositionJD, true)
|
||||||
if date > jde {
|
if sameEventUTQueryTT(date, jde) || eventUTQueryBeforeOrEqual(date, jde) {
|
||||||
oppositionJD := neptuneConjunctionFull(jde, 180, 0)
|
|
||||||
return neptuneRetrograde(oppositionJD-10, true)
|
|
||||||
}
|
|
||||||
return date
|
return date
|
||||||
}
|
}
|
||||||
|
lastOppositionJD := neptuneConjunctionFull(jde, 180, 0)
|
||||||
|
return neptuneRetrogradeAroundOpposition(lastOppositionJD, true)
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,55 @@
|
|||||||
|
package basic
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestOuterPlanetExactEventBoundaryIncludesCurrent(t *testing.T) {
|
||||||
|
cases := []struct {
|
||||||
|
name string
|
||||||
|
seed float64
|
||||||
|
lastFn func(float64) float64
|
||||||
|
nextFn func(float64) float64
|
||||||
|
}{
|
||||||
|
{name: "JupiterConjunction", seed: NextJupiterConjunction(ttjdUTC(2026, 1, 1, 0, 0, 0)), lastFn: LastJupiterConjunction, nextFn: NextJupiterConjunction},
|
||||||
|
{name: "JupiterOpposition", seed: NextJupiterOpposition(ttjdUTC(2026, 1, 1, 0, 0, 0)), lastFn: LastJupiterOpposition, nextFn: NextJupiterOpposition},
|
||||||
|
{name: "JupiterEasternQuadrature", seed: NextJupiterEasternQuadrature(ttjdUTC(2026, 1, 1, 0, 0, 0)), lastFn: LastJupiterEasternQuadrature, nextFn: NextJupiterEasternQuadrature},
|
||||||
|
{name: "JupiterWesternQuadrature", seed: NextJupiterWesternQuadrature(ttjdUTC(2026, 1, 1, 0, 0, 0)), lastFn: LastJupiterWesternQuadrature, nextFn: NextJupiterWesternQuadrature},
|
||||||
|
{name: "JupiterP2R", seed: NextJupiterProgradeToRetrograde(ttjdUTC(2026, 1, 1, 0, 0, 0)), lastFn: LastJupiterProgradeToRetrograde, nextFn: NextJupiterProgradeToRetrograde},
|
||||||
|
{name: "JupiterR2P", seed: NextJupiterRetrogradeToPrograde(ttjdUTC(2026, 1, 1, 0, 0, 0)), lastFn: LastJupiterRetrogradeToPrograde, nextFn: NextJupiterRetrogradeToPrograde},
|
||||||
|
{name: "SaturnOpposition", seed: NextSaturnOpposition(ttjdUTC(2025, 1, 1, 0, 0, 0)), lastFn: LastSaturnOpposition, nextFn: NextSaturnOpposition},
|
||||||
|
{name: "SaturnP2R", seed: NextSaturnProgradeToRetrograde(ttjdUTC(2025, 1, 1, 0, 0, 0)), lastFn: LastSaturnProgradeToRetrograde, nextFn: NextSaturnProgradeToRetrograde},
|
||||||
|
{name: "SaturnR2P", seed: NextSaturnRetrogradeToPrograde(ttjdUTC(2025, 1, 1, 0, 0, 0)), lastFn: LastSaturnRetrogradeToPrograde, nextFn: NextSaturnRetrogradeToPrograde},
|
||||||
|
{name: "UranusOpposition", seed: NextUranusOpposition(ttjdUTC(2025, 1, 1, 0, 0, 0)), lastFn: LastUranusOpposition, nextFn: NextUranusOpposition},
|
||||||
|
{name: "UranusP2R", seed: NextUranusProgradeToRetrograde(ttjdUTC(2025, 1, 1, 0, 0, 0)), lastFn: LastUranusProgradeToRetrograde, nextFn: NextUranusProgradeToRetrograde},
|
||||||
|
{name: "UranusR2P", seed: NextUranusRetrogradeToPrograde(ttjdUTC(2025, 1, 1, 0, 0, 0)), lastFn: LastUranusRetrogradeToPrograde, nextFn: NextUranusRetrogradeToPrograde},
|
||||||
|
{name: "NeptuneOpposition", seed: NextNeptuneOpposition(ttjdUTC(2026, 1, 1, 0, 0, 0)), lastFn: LastNeptuneOpposition, nextFn: NextNeptuneOpposition},
|
||||||
|
{name: "NeptuneP2R", seed: NextNeptuneProgradeToRetrograde(ttjdUTC(2026, 1, 1, 0, 0, 0)), lastFn: LastNeptuneProgradeToRetrograde, nextFn: NextNeptuneProgradeToRetrograde},
|
||||||
|
{name: "NeptuneR2P", seed: NextNeptuneRetrogradeToPrograde(ttjdUTC(2026, 1, 1, 0, 0, 0)), lastFn: LastNeptuneRetrogradeToPrograde, nextFn: NextNeptuneRetrogradeToPrograde},
|
||||||
|
{name: "MarsConjunction", seed: NextMarsConjunction(ttjdUTC(2026, 1, 1, 0, 0, 0)), lastFn: LastMarsConjunction, nextFn: NextMarsConjunction},
|
||||||
|
{name: "MarsOpposition", seed: NextMarsOpposition(ttjdUTC(2026, 1, 1, 0, 0, 0)), lastFn: LastMarsOpposition, nextFn: NextMarsOpposition},
|
||||||
|
{name: "MarsEasternQuadrature", seed: NextMarsEasternQuadrature(ttjdUTC(2026, 1, 1, 0, 0, 0)), lastFn: LastMarsEasternQuadrature, nextFn: NextMarsEasternQuadrature},
|
||||||
|
{name: "MarsWesternQuadrature", seed: NextMarsWesternQuadrature(ttjdUTC(2026, 1, 1, 0, 0, 0)), lastFn: LastMarsWesternQuadrature, nextFn: NextMarsWesternQuadrature},
|
||||||
|
{name: "MarsP2R", seed: NextMarsProgradeToRetrograde(ttjdUTC(2026, 1, 1, 0, 0, 0)), lastFn: LastMarsProgradeToRetrograde, nextFn: NextMarsProgradeToRetrograde},
|
||||||
|
{name: "MarsR2P", seed: NextMarsRetrogradeToPrograde(ttjdUTC(2026, 1, 1, 0, 0, 0)), lastFn: LastMarsRetrogradeToPrograde, nextFn: NextMarsRetrogradeToPrograde},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range cases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
queryTT := TD2UT(tc.seed, true)
|
||||||
|
last := tc.lastFn(queryTT)
|
||||||
|
next := tc.nextFn(queryTT)
|
||||||
|
if !sameEventJD(last, tc.seed) {
|
||||||
|
t.Fatalf("last exact boundary mismatch: got %.12f want %.12f", last, tc.seed)
|
||||||
|
}
|
||||||
|
if !sameEventJD(next, tc.seed) {
|
||||||
|
t.Fatalf("next exact boundary mismatch: got %.12f want %.12f", next, tc.seed)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ttjdUTC(year, month, day, hour, min, sec int) float64 {
|
||||||
|
return TD2UT(Date2JDE(time.Date(year, time.Month(month), day, hour, min, sec, 0, time.UTC)), true)
|
||||||
|
}
|
||||||
@@ -0,0 +1,181 @@
|
|||||||
|
package basic
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type outerTruthBaselineFile struct {
|
||||||
|
Events []outerTruthBaselineEvent `json:"events"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type outerTruthBaselineEvent struct {
|
||||||
|
Planet string `json:"planet"`
|
||||||
|
Kind string `json:"kind"`
|
||||||
|
HintKind string `json:"hint_kind"`
|
||||||
|
NAOJHintJST string `json:"naoj_hint_jst"`
|
||||||
|
Precision string `json:"precision"`
|
||||||
|
CandidateJST string `json:"candidate_jst"`
|
||||||
|
VerifiedJST string `json:"verified_jst"`
|
||||||
|
CandidateSource string `json:"candidate_source"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadOuterTruthBaseline(t *testing.T) outerTruthBaselineFile {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
paths := [][]string{
|
||||||
|
{
|
||||||
|
"testdata/jpl_outer_event_baseline.json",
|
||||||
|
"basic/testdata/jpl_outer_event_baseline.json",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"testdata/jpl_outer_event_baseline_21c_sample.json",
|
||||||
|
"basic/testdata/jpl_outer_event_baseline_21c_sample.json",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
var merged outerTruthBaselineFile
|
||||||
|
for index, candidates := range paths {
|
||||||
|
var (
|
||||||
|
data []byte
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
for _, path := range candidates {
|
||||||
|
data, err = os.ReadFile(path)
|
||||||
|
if err == nil {
|
||||||
|
var baseline outerTruthBaselineFile
|
||||||
|
if err := json.Unmarshal(data, &baseline); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
merged.Events = append(merged.Events, baseline.Events...)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err != nil && index == 0 {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(merged.Events) == 0 {
|
||||||
|
t.Fatal("empty outer truth baseline file")
|
||||||
|
}
|
||||||
|
return merged
|
||||||
|
}
|
||||||
|
|
||||||
|
func outerTruthTolerance(event outerTruthBaselineEvent) time.Duration {
|
||||||
|
switch event.Kind {
|
||||||
|
case "CONJ", "OPP", "EQE", "EQW":
|
||||||
|
return 2 * time.Minute
|
||||||
|
default:
|
||||||
|
return 2 * time.Minute
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func outerTruthEventFuncs(t *testing.T, event outerTruthBaselineEvent) (func(float64) float64, func(float64) float64) {
|
||||||
|
t.Helper()
|
||||||
|
switch event.Planet + ":" + event.Kind {
|
||||||
|
case "Jupiter:CONJ":
|
||||||
|
return LastJupiterConjunction, NextJupiterConjunction
|
||||||
|
case "Jupiter:OPP":
|
||||||
|
return LastJupiterOpposition, NextJupiterOpposition
|
||||||
|
case "Jupiter:EQE":
|
||||||
|
return LastJupiterEasternQuadrature, NextJupiterEasternQuadrature
|
||||||
|
case "Jupiter:EQW":
|
||||||
|
return LastJupiterWesternQuadrature, NextJupiterWesternQuadrature
|
||||||
|
case "Jupiter:P2R":
|
||||||
|
return LastJupiterProgradeToRetrograde, NextJupiterProgradeToRetrograde
|
||||||
|
case "Jupiter:R2P":
|
||||||
|
return LastJupiterRetrogradeToPrograde, NextJupiterRetrogradeToPrograde
|
||||||
|
case "Saturn:CONJ":
|
||||||
|
return LastSaturnConjunction, NextSaturnConjunction
|
||||||
|
case "Saturn:OPP":
|
||||||
|
return LastSaturnOpposition, NextSaturnOpposition
|
||||||
|
case "Saturn:EQE":
|
||||||
|
return LastSaturnEasternQuadrature, NextSaturnEasternQuadrature
|
||||||
|
case "Saturn:EQW":
|
||||||
|
return LastSaturnWesternQuadrature, NextSaturnWesternQuadrature
|
||||||
|
case "Saturn:P2R":
|
||||||
|
return LastSaturnProgradeToRetrograde, NextSaturnProgradeToRetrograde
|
||||||
|
case "Saturn:R2P":
|
||||||
|
return LastSaturnRetrogradeToPrograde, NextSaturnRetrogradeToPrograde
|
||||||
|
case "Uranus:CONJ":
|
||||||
|
return LastUranusConjunction, NextUranusConjunction
|
||||||
|
case "Uranus:OPP":
|
||||||
|
return LastUranusOpposition, NextUranusOpposition
|
||||||
|
case "Uranus:EQE":
|
||||||
|
return LastUranusEasternQuadrature, NextUranusEasternQuadrature
|
||||||
|
case "Uranus:EQW":
|
||||||
|
return LastUranusWesternQuadrature, NextUranusWesternQuadrature
|
||||||
|
case "Uranus:P2R":
|
||||||
|
return LastUranusProgradeToRetrograde, NextUranusProgradeToRetrograde
|
||||||
|
case "Uranus:R2P":
|
||||||
|
return LastUranusRetrogradeToPrograde, NextUranusRetrogradeToPrograde
|
||||||
|
case "Neptune:CONJ":
|
||||||
|
return LastNeptuneConjunction, NextNeptuneConjunction
|
||||||
|
case "Neptune:OPP":
|
||||||
|
return LastNeptuneOpposition, NextNeptuneOpposition
|
||||||
|
case "Neptune:EQE":
|
||||||
|
return LastNeptuneEasternQuadrature, NextNeptuneEasternQuadrature
|
||||||
|
case "Neptune:EQW":
|
||||||
|
return LastNeptuneWesternQuadrature, NextNeptuneWesternQuadrature
|
||||||
|
case "Neptune:P2R":
|
||||||
|
return LastNeptuneProgradeToRetrograde, NextNeptuneProgradeToRetrograde
|
||||||
|
case "Neptune:R2P":
|
||||||
|
return LastNeptuneRetrogradeToPrograde, NextNeptuneRetrogradeToPrograde
|
||||||
|
default:
|
||||||
|
t.Fatalf("unsupported outer event %s:%s", event.Planet, event.Kind)
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func assertOuterTruthBaselineEvent(t *testing.T, event outerTruthBaselineEvent, lastFn, nextFn func(float64) float64) {
|
||||||
|
t.Helper()
|
||||||
|
when := parseInnerBaselineTime(t, event.VerifiedJST)
|
||||||
|
before := when.Add(-7 * 24 * time.Hour)
|
||||||
|
after := when.Add(7 * 24 * time.Hour)
|
||||||
|
next := JDE2DateByZone(nextFn(toUTJD(before)), when.Location(), false)
|
||||||
|
last := JDE2DateByZone(lastFn(toUTJD(after)), when.Location(), false)
|
||||||
|
tolerance := outerTruthTolerance(event)
|
||||||
|
|
||||||
|
if diff := next.Sub(when); diff < -tolerance || diff > tolerance {
|
||||||
|
t.Fatalf("%s %s next mismatch: got %s want %s tol=%s hint=%s candidate=%s via=%s", event.Planet, event.Kind, next, when, tolerance, event.NAOJHintJST, event.CandidateJST, event.CandidateSource)
|
||||||
|
}
|
||||||
|
if diff := last.Sub(when); diff < -tolerance || diff > tolerance {
|
||||||
|
t.Fatalf("%s %s last mismatch: got %s want %s tol=%s hint=%s candidate=%s via=%s", event.Planet, event.Kind, last, when, tolerance, event.NAOJHintJST, event.CandidateJST, event.CandidateSource)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestOuterPlanetPhaseTruthAgainstJPL(t *testing.T) {
|
||||||
|
baseline := loadOuterTruthBaseline(t)
|
||||||
|
for _, event := range baseline.Events {
|
||||||
|
event := event
|
||||||
|
switch event.Kind {
|
||||||
|
case "P2R", "R2P":
|
||||||
|
// Station rows are retained as JPL apparent-RA reference data for
|
||||||
|
// future refinement. Current station behavior is constrained by the
|
||||||
|
// library's existing station baseline instead of these reference rows.
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
name := strings.Join([]string{event.Planet, event.Kind, event.VerifiedJST}, "_")
|
||||||
|
t.Run(name, func(t *testing.T) {
|
||||||
|
lastFn, nextFn := outerTruthEventFuncs(t, event)
|
||||||
|
assertOuterTruthBaselineEvent(t, event, lastFn, nextFn)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestOuterPlanetStationJPLReferenceLoaded(t *testing.T) {
|
||||||
|
baseline := loadOuterTruthBaseline(t)
|
||||||
|
count := 0
|
||||||
|
for _, event := range baseline.Events {
|
||||||
|
switch event.Kind {
|
||||||
|
case "P2R", "R2P":
|
||||||
|
count++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if count == 0 {
|
||||||
|
t.Fatal("missing outer station JPL reference rows")
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,84 @@
|
|||||||
|
package basic
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math"
|
||||||
|
|
||||||
|
"b612.me/astro/planet"
|
||||||
|
. "b612.me/astro/tools"
|
||||||
|
)
|
||||||
|
|
||||||
|
type planetGeocentricPosition struct {
|
||||||
|
x float64
|
||||||
|
y float64
|
||||||
|
z float64
|
||||||
|
lo float64
|
||||||
|
bo float64
|
||||||
|
}
|
||||||
|
|
||||||
|
func planetHeliocentricXYZN(planetIndex int, jd float64, n int) (float64, float64, float64) {
|
||||||
|
l := planet.WherePlanetN(planetIndex, 0, jd, n)
|
||||||
|
b := planet.WherePlanetN(planetIndex, 1, jd, n)
|
||||||
|
r := planet.WherePlanetN(planetIndex, 2, jd, n)
|
||||||
|
return sphericalToRectangular(l, b, r)
|
||||||
|
}
|
||||||
|
|
||||||
|
func earthHeliocentricXYZN(jd float64, n int) (float64, float64, float64) {
|
||||||
|
l := planet.WherePlanetN(-1, 0, jd, n)
|
||||||
|
b := planet.WherePlanetN(-1, 1, jd, n)
|
||||||
|
r := planet.WherePlanetN(-1, 2, jd, n)
|
||||||
|
return sphericalToRectangular(l, b, r)
|
||||||
|
}
|
||||||
|
|
||||||
|
func sphericalToRectangular(lo, bo, radius float64) (float64, float64, float64) {
|
||||||
|
cosBo := math.Cos(bo * math.Pi / 180)
|
||||||
|
return radius * cosBo * math.Cos(lo*math.Pi/180),
|
||||||
|
radius * cosBo * math.Sin(lo*math.Pi/180),
|
||||||
|
radius * math.Sin(bo*math.Pi/180)
|
||||||
|
}
|
||||||
|
|
||||||
|
func geocentricPositionFromRectangular(x, y, z float64) planetGeocentricPosition {
|
||||||
|
lo := math.Atan2(y, x) * 180 / math.Pi
|
||||||
|
bo := math.Atan2(z, math.Sqrt(x*x+y*y)) * 180 / math.Pi
|
||||||
|
return planetGeocentricPosition{
|
||||||
|
x: x,
|
||||||
|
y: y,
|
||||||
|
z: z,
|
||||||
|
lo: Limit360(lo),
|
||||||
|
bo: bo,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func planetGeocentricPositionN(planetIndex int, planetJD, earthJD float64, n int) planetGeocentricPosition {
|
||||||
|
px, py, pz := planetHeliocentricXYZN(planetIndex, planetJD, n)
|
||||||
|
ex, ey, ez := earthHeliocentricXYZN(earthJD, n)
|
||||||
|
return geocentricPositionFromRectangular(px-ex, py-ey, pz-ez)
|
||||||
|
}
|
||||||
|
|
||||||
|
func planetGeocentricPositionWithEarthN(planetIndex int, planetJD float64, ex, ey, ez float64, n int) planetGeocentricPosition {
|
||||||
|
px, py, pz := planetHeliocentricXYZN(planetIndex, planetJD, n)
|
||||||
|
return geocentricPositionFromRectangular(px-ex, py-ey, pz-ez)
|
||||||
|
}
|
||||||
|
|
||||||
|
func planetApparentGeocentricPositionN(planetIndex int, jd float64, n int) (planetGeocentricPosition, float64) {
|
||||||
|
ex, ey, ez := earthHeliocentricXYZN(jd, n)
|
||||||
|
geoNow := planetGeocentricPositionWithEarthN(planetIndex, jd, ex, ey, ez, n)
|
||||||
|
tau := 0.0057755183 * math.Sqrt(geoNow.x*geoNow.x+geoNow.y*geoNow.y+geoNow.z*geoNow.z)
|
||||||
|
geo := planetGeocentricPositionWithEarthN(planetIndex, jd-tau, ex, ey, ez, n)
|
||||||
|
baseLo := geo.lo
|
||||||
|
baseBo := geo.bo
|
||||||
|
geo.lo = Limit360(baseLo + GXCLo(baseLo, baseBo, jd)/3600.0 + Nutation2000Bi(jd))
|
||||||
|
geo.bo = baseBo + GXCBo(baseLo, baseBo, jd)/3600.0
|
||||||
|
return geo, tau
|
||||||
|
}
|
||||||
|
|
||||||
|
func planetTrueGeocentricPositionN(planetIndex int, jd float64, n int) (planetGeocentricPosition, float64) {
|
||||||
|
ex, ey, ez := earthHeliocentricXYZN(jd, n)
|
||||||
|
geoNow := planetGeocentricPositionWithEarthN(planetIndex, jd, ex, ey, ez, n)
|
||||||
|
tau := 0.0057755183 * math.Sqrt(geoNow.x*geoNow.x+geoNow.y*geoNow.y+geoNow.z*geoNow.z)
|
||||||
|
return planetGeocentricPositionWithEarthN(planetIndex, jd-tau, ex, ey, ez, n), tau
|
||||||
|
}
|
||||||
|
|
||||||
|
func planetEarthAwayExplicitN(planetIndex int, jd float64, n int) float64 {
|
||||||
|
geoNow := planetGeocentricPositionN(planetIndex, jd, jd, n)
|
||||||
|
return math.Sqrt(geoNow.x*geoNow.x + geoNow.y*geoNow.y + geoNow.z*geoNow.z)
|
||||||
|
}
|
||||||
@@ -0,0 +1,83 @@
|
|||||||
|
package basic
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type planetApparentSample struct {
|
||||||
|
Body string `json:"body"`
|
||||||
|
InputUTC string `json:"input_utc"`
|
||||||
|
RightAscension float64 `json:"right_ascension"`
|
||||||
|
Declination float64 `json:"declination"`
|
||||||
|
EclipticLongitude float64 `json:"ecliptic_longitude"`
|
||||||
|
EclipticLatitude float64 `json:"ecliptic_latitude"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPlanetApparentCoordinatesMatchHorizonsBaseline(t *testing.T) {
|
||||||
|
data, err := os.ReadFile("testdata/planet_apparent_baseline.json")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("read baseline: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var samples []planetApparentSample
|
||||||
|
if err := json.Unmarshal(data, &samples); err != nil {
|
||||||
|
t.Fatalf("decode baseline: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
type apparentCase struct {
|
||||||
|
lo func(float64) float64
|
||||||
|
bo func(float64) float64
|
||||||
|
ra func(float64) float64
|
||||||
|
dec func(float64) float64
|
||||||
|
}
|
||||||
|
|
||||||
|
cases := map[string]apparentCase{
|
||||||
|
"mercury": {lo: MercuryApparentLo, bo: MercuryApparentBo, ra: MercuryApparentRa, dec: MercuryApparentDec},
|
||||||
|
"venus": {lo: VenusApparentLo, bo: VenusApparentBo, ra: VenusApparentRa, dec: VenusApparentDec},
|
||||||
|
"mars": {lo: MarsApparentLo, bo: MarsApparentBo, ra: MarsApparentRa, dec: MarsApparentDec},
|
||||||
|
"jupiter": {lo: JupiterApparentLo, bo: JupiterApparentBo, ra: JupiterApparentRa, dec: JupiterApparentDec},
|
||||||
|
"saturn": {lo: SaturnApparentLo, bo: SaturnApparentBo, ra: SaturnApparentRa, dec: SaturnApparentDec},
|
||||||
|
"uranus": {lo: UranusApparentLo, bo: UranusApparentBo, ra: UranusApparentRa, dec: UranusApparentDec},
|
||||||
|
"neptune": {lo: NeptuneApparentLo, bo: NeptuneApparentBo, ra: NeptuneApparentRa, dec: NeptuneApparentDec},
|
||||||
|
}
|
||||||
|
|
||||||
|
seen := make(map[string]bool, len(cases))
|
||||||
|
for _, sample := range samples {
|
||||||
|
tc, ok := cases[sample.Body]
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("unknown body %q", sample.Body)
|
||||||
|
}
|
||||||
|
if seen[sample.Body] {
|
||||||
|
t.Fatalf("duplicate body %q in apparent baseline", sample.Body)
|
||||||
|
}
|
||||||
|
seen[sample.Body] = true
|
||||||
|
|
||||||
|
date, err := time.Parse(time.RFC3339, sample.InputUTC)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("parse sample time %q: %v", sample.InputUTC, err)
|
||||||
|
}
|
||||||
|
jd := TD2UT(Date2JDE(date.UTC()), true)
|
||||||
|
prefix := sample.Body + "." + sample.InputUTC
|
||||||
|
|
||||||
|
assertPlanetApparentAngleClose(t, prefix+".RightAscension", tc.ra(jd), sample.RightAscension, 0.001)
|
||||||
|
assertPlanetPhaseClose(t, prefix+".Declination", tc.dec(jd), sample.Declination, 0.001)
|
||||||
|
assertPlanetApparentAngleClose(t, prefix+".EclipticLongitude", tc.lo(jd), sample.EclipticLongitude, 0.001)
|
||||||
|
assertPlanetPhaseClose(t, prefix+".EclipticLatitude", tc.bo(jd), sample.EclipticLatitude, 0.001)
|
||||||
|
}
|
||||||
|
|
||||||
|
for body := range cases {
|
||||||
|
if !seen[body] {
|
||||||
|
t.Fatalf("missing body %q in apparent baseline", body)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func assertPlanetApparentAngleClose(t *testing.T, name string, got, want, tolerance float64) {
|
||||||
|
t.Helper()
|
||||||
|
if diff := angleDiffAbs(got, want); diff > tolerance {
|
||||||
|
t.Fatalf("%s mismatch: got %.12f want %.12f diff %.12f tolerance %.12f", name, got, want, diff, tolerance)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,520 @@
|
|||||||
|
package basic
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math"
|
||||||
|
|
||||||
|
. "b612.me/astro/tools"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
planetTransitMeanSolarMotionDegPerDay = 360.0 / 365.2422
|
||||||
|
planetTransitTropicalYearDays = 365.2422
|
||||||
|
planetTransitSeasonProbeStepDays = 20.0
|
||||||
|
planetTransitSearchLimit = 2400
|
||||||
|
planetTransitSearchEpsilonDays = 1.0 / 86400.0
|
||||||
|
planetTransitGreatestWindowDays = 1.2
|
||||||
|
planetTransitGreatestToleranceDays = 0.25 / 86400.0
|
||||||
|
planetTransitContactStepDays = 0.02
|
||||||
|
planetTransitContactSpanDays = 1.0
|
||||||
|
planetTransitContactToleranceDays = 0.25 / 86400.0
|
||||||
|
planetTransitCoarseN = 16
|
||||||
|
)
|
||||||
|
|
||||||
|
// PlanetTransitResult 表示一次地心行星凌日结果。
|
||||||
|
//
|
||||||
|
// Valid 为 false 时表示没有找到有效凌日。所有时刻字段均为 UT 儒略日。
|
||||||
|
// MinimumSeparationArcsec、SunSemidiameterArcsec、PlanetSemidiameterArcsec 的单位均为角秒。
|
||||||
|
type PlanetTransitResult struct {
|
||||||
|
Valid bool
|
||||||
|
|
||||||
|
// PlanetIndex 为行星序号,1 表示水星,2 表示金星。
|
||||||
|
PlanetIndex int
|
||||||
|
|
||||||
|
// ExternalIngress / ExternalEgress 为一触 / 四触。
|
||||||
|
ExternalIngress float64
|
||||||
|
ExternalEgress float64
|
||||||
|
// InternalIngress / InternalEgress 为二触 / 三触。掠凌没有内切接触时为 0。
|
||||||
|
InternalIngress float64
|
||||||
|
InternalEgress float64
|
||||||
|
// Greatest 为凌甚,即行星中心最接近太阳中心的时刻。
|
||||||
|
Greatest float64
|
||||||
|
|
||||||
|
MinimumSeparationArcsec float64
|
||||||
|
SunSemidiameterArcsec float64
|
||||||
|
PlanetSemidiameterArcsec float64
|
||||||
|
|
||||||
|
HasExternal bool
|
||||||
|
HasInternal bool
|
||||||
|
}
|
||||||
|
|
||||||
|
type planetTransitConfig struct {
|
||||||
|
planetIndex int
|
||||||
|
|
||||||
|
synodicPeriodDays float64
|
||||||
|
anchorInferiorTT float64
|
||||||
|
|
||||||
|
seasonWindowDays float64
|
||||||
|
latitudePrefilter float64
|
||||||
|
conjunctionStepDay float64
|
||||||
|
|
||||||
|
apparentLoN func(float64, int) float64
|
||||||
|
apparentBoN func(float64, int) float64
|
||||||
|
apparentRaDecN func(float64, int) (float64, float64)
|
||||||
|
semidiameterN func(float64, int) float64
|
||||||
|
earthDistanceN func(float64, int) float64
|
||||||
|
nodeN func(float64, int) float64
|
||||||
|
}
|
||||||
|
|
||||||
|
type planetTransitState struct {
|
||||||
|
jdTT float64
|
||||||
|
separationArcsec float64
|
||||||
|
separationSquared float64
|
||||||
|
sunSemidiameter float64
|
||||||
|
planetSemidiameter float64
|
||||||
|
externalContactMetric float64
|
||||||
|
internalContactMetric float64
|
||||||
|
}
|
||||||
|
|
||||||
|
func mercuryTransitConfig() planetTransitConfig {
|
||||||
|
return planetTransitConfig{
|
||||||
|
planetIndex: 1,
|
||||||
|
synodicPeriodDays: MERCURY_S_PERIOD,
|
||||||
|
anchorInferiorTT: TD2UT(JDECalc(2019, 11, 11+(15+21.0/60+40.0/3600)/24), true),
|
||||||
|
seasonWindowDays: 12,
|
||||||
|
latitudePrefilter: 1.0,
|
||||||
|
conjunctionStepDay: 0.00001,
|
||||||
|
apparentLoN: MercuryApparentLoN,
|
||||||
|
apparentBoN: MercuryApparentBoN,
|
||||||
|
apparentRaDecN: MercuryApparentRaDecN,
|
||||||
|
semidiameterN: MercurySemidiameterN,
|
||||||
|
earthDistanceN: EarthMercuryAwayN,
|
||||||
|
nodeN: MercuryAscendingNodeN,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func venusTransitConfig() planetTransitConfig {
|
||||||
|
return planetTransitConfig{
|
||||||
|
planetIndex: 2,
|
||||||
|
synodicPeriodDays: VENUS_S_PERIOD,
|
||||||
|
anchorInferiorTT: TD2UT(JDECalc(2012, 6, 6+(1+29.0/60)/24), true),
|
||||||
|
seasonWindowDays: 8,
|
||||||
|
latitudePrefilter: 0.8,
|
||||||
|
conjunctionStepDay: 0.00001,
|
||||||
|
apparentLoN: VenusApparentLoN,
|
||||||
|
apparentBoN: VenusApparentBoN,
|
||||||
|
apparentRaDecN: VenusApparentRaDecN,
|
||||||
|
semidiameterN: VenusSemidiameterN,
|
||||||
|
earthDistanceN: EarthVenusAwayN,
|
||||||
|
nodeN: VenusAscendingNodeN,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NextMercuryTransit 返回给定时刻之后的下一次地心水星凌日。
|
||||||
|
func NextMercuryTransit(jde float64) PlanetTransitResult {
|
||||||
|
result, _ := searchPlanetTransit(jde, mercuryTransitConfig(), 1, false)
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
// LastMercuryTransit 返回给定时刻之前的上一次地心水星凌日。
|
||||||
|
func LastMercuryTransit(jde float64) PlanetTransitResult {
|
||||||
|
result, _ := searchPlanetTransit(jde, mercuryTransitConfig(), -1, true)
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClosestMercuryTransit 返回距给定时刻最近的一次地心水星凌日。
|
||||||
|
func ClosestMercuryTransit(jde float64) PlanetTransitResult {
|
||||||
|
return closestPlanetTransit(jde, mercuryTransitConfig())
|
||||||
|
}
|
||||||
|
|
||||||
|
// NextVenusTransit 返回给定时刻之后的下一次地心金星凌日。
|
||||||
|
func NextVenusTransit(jde float64) PlanetTransitResult {
|
||||||
|
result, _ := searchPlanetTransit(jde, venusTransitConfig(), 1, false)
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
// LastVenusTransit 返回给定时刻之前的上一次地心金星凌日。
|
||||||
|
func LastVenusTransit(jde float64) PlanetTransitResult {
|
||||||
|
result, _ := searchPlanetTransit(jde, venusTransitConfig(), -1, true)
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClosestVenusTransit 返回距给定时刻最近的一次地心金星凌日。
|
||||||
|
func ClosestVenusTransit(jde float64) PlanetTransitResult {
|
||||||
|
return closestPlanetTransit(jde, venusTransitConfig())
|
||||||
|
}
|
||||||
|
|
||||||
|
func closestPlanetTransit(jde float64, cfg planetTransitConfig) PlanetTransitResult {
|
||||||
|
last, hasLast := searchPlanetTransit(jde, cfg, -1, true)
|
||||||
|
next, hasNext := searchPlanetTransit(jde, cfg, 1, false)
|
||||||
|
switch {
|
||||||
|
case hasLast && !hasNext:
|
||||||
|
return last
|
||||||
|
case !hasLast && hasNext:
|
||||||
|
return next
|
||||||
|
case !hasLast && !hasNext:
|
||||||
|
return PlanetTransitResult{}
|
||||||
|
}
|
||||||
|
if math.Abs(last.Greatest-jde) <= math.Abs(next.Greatest-jde) {
|
||||||
|
return last
|
||||||
|
}
|
||||||
|
return next
|
||||||
|
}
|
||||||
|
|
||||||
|
func searchPlanetTransit(jde float64, cfg planetTransitConfig, direction int, includeCurrent bool) (PlanetTransitResult, bool) {
|
||||||
|
if !isFiniteFloat(jde) || direction == 0 {
|
||||||
|
return PlanetTransitResult{}, false
|
||||||
|
}
|
||||||
|
|
||||||
|
targetTT := TD2UT(jde, true)
|
||||||
|
probeTT := targetTT
|
||||||
|
for i := 0; i < planetTransitSearchLimit; i++ {
|
||||||
|
seasonTT, ok := nextPlanetTransitSeasonTT(probeTT, cfg, direction)
|
||||||
|
if !ok {
|
||||||
|
return PlanetTransitResult{}, false
|
||||||
|
}
|
||||||
|
|
||||||
|
seedTT := nearestPlanetTransitInferiorSeedTT(seasonTT, cfg)
|
||||||
|
if math.Abs(seedTT-seasonTT) <= cfg.seasonWindowDays {
|
||||||
|
conjunctionTT := refinePlanetTransitInferiorConjunctionTT(seedTT, cfg)
|
||||||
|
if math.Abs(conjunctionTT-seasonTT) <= cfg.seasonWindowDays+1 && isPotentialPlanetTransit(conjunctionTT, cfg) {
|
||||||
|
resultTT, ok := planetTransitAtInferiorConjunctionTT(conjunctionTT, cfg)
|
||||||
|
if ok && planetTransitMatchesDirection(resultTT.Greatest, targetTT, direction, includeCurrent) {
|
||||||
|
return planetTransitResultTTToUT(resultTT), true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
probeTT = seasonTT + float64(direction)*planetTransitSeasonProbeStepDays
|
||||||
|
}
|
||||||
|
|
||||||
|
return PlanetTransitResult{}, false
|
||||||
|
}
|
||||||
|
|
||||||
|
func nextPlanetTransitSeasonTT(jdTT float64, cfg planetTransitConfig, direction int) (float64, bool) {
|
||||||
|
best := math.NaN()
|
||||||
|
for nodeOffset := 0; nodeOffset <= 1; nodeOffset++ {
|
||||||
|
candidate := estimatePlanetTransitSeasonTT(jdTT, cfg, nodeOffset, direction)
|
||||||
|
candidate = refinePlanetTransitSeasonTT(candidate, cfg, nodeOffset)
|
||||||
|
for !planetTransitMatchesDirection(candidate, jdTT, direction, false) {
|
||||||
|
candidate += float64(direction) * planetTransitTropicalYearDays
|
||||||
|
candidate = refinePlanetTransitSeasonTT(candidate, cfg, nodeOffset)
|
||||||
|
}
|
||||||
|
if !isFiniteFloat(best) || math.Abs(candidate-jdTT) < math.Abs(best-jdTT) {
|
||||||
|
best = candidate
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !isFiniteFloat(best) {
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
return best, true
|
||||||
|
}
|
||||||
|
|
||||||
|
func estimatePlanetTransitSeasonTT(jdTT float64, cfg planetTransitConfig, nodeOffset int, direction int) float64 {
|
||||||
|
sunLongitude := HSunApparentLoN(jdTT, planetTransitCoarseN)
|
||||||
|
nodeLongitude := planetTransitNodeLongitude(jdTT, cfg, nodeOffset, planetTransitCoarseN)
|
||||||
|
if direction > 0 {
|
||||||
|
delta := Limit360(nodeLongitude - sunLongitude)
|
||||||
|
if delta <= planetTransitSearchEpsilonDays {
|
||||||
|
delta += 360
|
||||||
|
}
|
||||||
|
return jdTT + delta/planetTransitMeanSolarMotionDegPerDay
|
||||||
|
}
|
||||||
|
delta := Limit360(sunLongitude - nodeLongitude)
|
||||||
|
if delta <= planetTransitSearchEpsilonDays {
|
||||||
|
delta += 360
|
||||||
|
}
|
||||||
|
return jdTT - delta/planetTransitMeanSolarMotionDegPerDay
|
||||||
|
}
|
||||||
|
|
||||||
|
func refinePlanetTransitSeasonTT(seedTT float64, cfg planetTransitConfig, nodeOffset int) float64 {
|
||||||
|
current := seedTT
|
||||||
|
for i := 0; i < 8; i++ {
|
||||||
|
prev := current
|
||||||
|
value := planetTransitSunNodeLongitudeDelta(prev, cfg, nodeOffset)
|
||||||
|
slope := (planetTransitSunNodeLongitudeDelta(prev+0.5, cfg, nodeOffset) -
|
||||||
|
planetTransitSunNodeLongitudeDelta(prev-0.5, cfg, nodeOffset)) / 1.0
|
||||||
|
if slope == 0 || !isFiniteFloat(slope) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
current = prev - value/slope
|
||||||
|
if math.Abs(current-prev) <= 0.00001 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return current
|
||||||
|
}
|
||||||
|
|
||||||
|
func planetTransitSunNodeLongitudeDelta(jdTT float64, cfg planetTransitConfig, nodeOffset int) float64 {
|
||||||
|
return planetTransitAngleDelta(HSunApparentLoN(jdTT, planetTransitCoarseN) -
|
||||||
|
planetTransitNodeLongitude(jdTT, cfg, nodeOffset, planetTransitCoarseN))
|
||||||
|
}
|
||||||
|
|
||||||
|
func planetTransitNodeLongitude(jdTT float64, cfg planetTransitConfig, nodeOffset int, n int) float64 {
|
||||||
|
return Limit360(cfg.nodeN(jdTT, n) + float64(nodeOffset)*180)
|
||||||
|
}
|
||||||
|
|
||||||
|
func nearestPlanetTransitInferiorSeedTT(seasonTT float64, cfg planetTransitConfig) float64 {
|
||||||
|
k := math.Round((seasonTT - cfg.anchorInferiorTT) / cfg.synodicPeriodDays)
|
||||||
|
return cfg.anchorInferiorTT + k*cfg.synodicPeriodDays
|
||||||
|
}
|
||||||
|
|
||||||
|
func refinePlanetTransitInferiorConjunctionTT(seedTT float64, cfg planetTransitConfig) float64 {
|
||||||
|
current := seedTT
|
||||||
|
for i := 0; i < 4; i++ {
|
||||||
|
prev := current
|
||||||
|
value := planetTransitLongitudeDeltaN(prev, cfg, planetTransitCoarseN)
|
||||||
|
slope := (planetTransitLongitudeDeltaN(prev+cfg.conjunctionStepDay, cfg, planetTransitCoarseN) -
|
||||||
|
planetTransitLongitudeDeltaN(prev-cfg.conjunctionStepDay, cfg, planetTransitCoarseN)) / (2 * cfg.conjunctionStepDay)
|
||||||
|
if slope == 0 || !isFiniteFloat(slope) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
current = prev - value/slope
|
||||||
|
if math.Abs(current-prev) <= 30.0/86400.0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for i := 0; i < 8; i++ {
|
||||||
|
prev := current
|
||||||
|
value := planetTransitLongitudeDeltaN(prev, cfg, -1)
|
||||||
|
slope := (planetTransitLongitudeDeltaN(prev+cfg.conjunctionStepDay, cfg, -1) -
|
||||||
|
planetTransitLongitudeDeltaN(prev-cfg.conjunctionStepDay, cfg, -1)) / (2 * cfg.conjunctionStepDay)
|
||||||
|
if slope == 0 || !isFiniteFloat(slope) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
current = prev - value/slope
|
||||||
|
if math.Abs(current-prev) <= cfg.conjunctionStepDay {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return current
|
||||||
|
}
|
||||||
|
|
||||||
|
func planetTransitLongitudeDeltaN(jdTT float64, cfg planetTransitConfig, n int) float64 {
|
||||||
|
return planetTransitAngleDelta(cfg.apparentLoN(jdTT, n) - HSunApparentLoN(jdTT, n))
|
||||||
|
}
|
||||||
|
|
||||||
|
func isPotentialPlanetTransit(conjunctionTT float64, cfg planetTransitConfig) bool {
|
||||||
|
if cfg.earthDistanceN(conjunctionTT, planetTransitCoarseN) > EarthAwayN(conjunctionTT, planetTransitCoarseN) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return math.Abs(cfg.apparentBoN(conjunctionTT, planetTransitCoarseN)) <= cfg.latitudePrefilter
|
||||||
|
}
|
||||||
|
|
||||||
|
func planetTransitAtInferiorConjunctionTT(conjunctionTT float64, cfg planetTransitConfig) (PlanetTransitResult, bool) {
|
||||||
|
greatestTT := greatestPlanetTransitTT(conjunctionTT, cfg)
|
||||||
|
greatestState := planetTransitStateAt(greatestTT, cfg, -1)
|
||||||
|
if !isFiniteFloat(greatestState.externalContactMetric) || greatestState.externalContactMetric > 0 {
|
||||||
|
return PlanetTransitResult{}, false
|
||||||
|
}
|
||||||
|
|
||||||
|
result := PlanetTransitResult{
|
||||||
|
Valid: true,
|
||||||
|
PlanetIndex: cfg.planetIndex,
|
||||||
|
Greatest: greatestTT,
|
||||||
|
MinimumSeparationArcsec: greatestState.separationArcsec,
|
||||||
|
SunSemidiameterArcsec: greatestState.sunSemidiameter,
|
||||||
|
PlanetSemidiameterArcsec: greatestState.planetSemidiameter,
|
||||||
|
HasExternal: true,
|
||||||
|
HasInternal: greatestState.internalContactMetric <= 0,
|
||||||
|
}
|
||||||
|
|
||||||
|
externalIngress, ok := refinePlanetTransitContactTT(greatestTT, cfg, -1, false)
|
||||||
|
if !ok {
|
||||||
|
return PlanetTransitResult{}, false
|
||||||
|
}
|
||||||
|
externalEgress, ok := refinePlanetTransitContactTT(greatestTT, cfg, 1, false)
|
||||||
|
if !ok || externalEgress <= externalIngress {
|
||||||
|
return PlanetTransitResult{}, false
|
||||||
|
}
|
||||||
|
result.ExternalIngress = externalIngress
|
||||||
|
result.ExternalEgress = externalEgress
|
||||||
|
|
||||||
|
if result.HasInternal {
|
||||||
|
internalIngress, ok := refinePlanetTransitContactTT(greatestTT, cfg, -1, true)
|
||||||
|
if ok {
|
||||||
|
result.InternalIngress = internalIngress
|
||||||
|
}
|
||||||
|
internalEgress, ok := refinePlanetTransitContactTT(greatestTT, cfg, 1, true)
|
||||||
|
if ok && internalEgress > internalIngress {
|
||||||
|
result.InternalEgress = internalEgress
|
||||||
|
}
|
||||||
|
result.HasInternal = result.InternalIngress != 0 && result.InternalEgress != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
return result, true
|
||||||
|
}
|
||||||
|
|
||||||
|
func greatestPlanetTransitTT(seedTT float64, cfg planetTransitConfig) float64 {
|
||||||
|
left := seedTT - planetTransitGreatestWindowDays
|
||||||
|
right := seedTT + planetTransitGreatestWindowDays
|
||||||
|
goldenRatio := (math.Sqrt(5) - 1) / 2
|
||||||
|
|
||||||
|
x1 := right - goldenRatio*(right-left)
|
||||||
|
x2 := left + goldenRatio*(right-left)
|
||||||
|
f1 := planetTransitStateAt(x1, cfg, planetTransitCoarseN).separationSquared
|
||||||
|
f2 := planetTransitStateAt(x2, cfg, planetTransitCoarseN).separationSquared
|
||||||
|
|
||||||
|
for i := 0; i < 80 && right-left > planetTransitGreatestToleranceDays; i++ {
|
||||||
|
if f1 <= f2 {
|
||||||
|
right = x2
|
||||||
|
x2 = x1
|
||||||
|
f2 = f1
|
||||||
|
x1 = right - goldenRatio*(right-left)
|
||||||
|
f1 = planetTransitStateAt(x1, cfg, planetTransitCoarseN).separationSquared
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
left = x1
|
||||||
|
x1 = x2
|
||||||
|
f1 = f2
|
||||||
|
x2 = left + goldenRatio*(right-left)
|
||||||
|
f2 = planetTransitStateAt(x2, cfg, planetTransitCoarseN).separationSquared
|
||||||
|
}
|
||||||
|
|
||||||
|
center := (left + right) / 2
|
||||||
|
left = center - 2.0/24.0
|
||||||
|
right = center + 2.0/24.0
|
||||||
|
x1 = right - goldenRatio*(right-left)
|
||||||
|
x2 = left + goldenRatio*(right-left)
|
||||||
|
f1 = planetTransitStateAt(x1, cfg, -1).separationSquared
|
||||||
|
f2 = planetTransitStateAt(x2, cfg, -1).separationSquared
|
||||||
|
for i := 0; i < 80 && right-left > planetTransitGreatestToleranceDays; i++ {
|
||||||
|
if f1 <= f2 {
|
||||||
|
right = x2
|
||||||
|
x2 = x1
|
||||||
|
f2 = f1
|
||||||
|
x1 = right - goldenRatio*(right-left)
|
||||||
|
f1 = planetTransitStateAt(x1, cfg, -1).separationSquared
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
left = x1
|
||||||
|
x1 = x2
|
||||||
|
f1 = f2
|
||||||
|
x2 = left + goldenRatio*(right-left)
|
||||||
|
f2 = planetTransitStateAt(x2, cfg, -1).separationSquared
|
||||||
|
}
|
||||||
|
return (left + right) / 2
|
||||||
|
}
|
||||||
|
|
||||||
|
func planetTransitStateAt(jdTT float64, cfg planetTransitConfig, n int) planetTransitState {
|
||||||
|
planetRA, planetDec := cfg.apparentRaDecN(jdTT, n)
|
||||||
|
sunRA, sunDec := HSunApparentRaDecN(jdTT, n)
|
||||||
|
separationArcsec := StarAngularSeparation(planetRA, planetDec, sunRA, sunDec) * 3600
|
||||||
|
sunSemidiameter := SunSemidiameterN(jdTT, n)
|
||||||
|
planetSemidiameter := cfg.semidiameterN(jdTT, n)
|
||||||
|
return planetTransitState{
|
||||||
|
jdTT: jdTT,
|
||||||
|
separationArcsec: separationArcsec,
|
||||||
|
separationSquared: separationArcsec * separationArcsec,
|
||||||
|
sunSemidiameter: sunSemidiameter,
|
||||||
|
planetSemidiameter: planetSemidiameter,
|
||||||
|
externalContactMetric: separationArcsec - (sunSemidiameter + planetSemidiameter),
|
||||||
|
internalContactMetric: separationArcsec - (sunSemidiameter - planetSemidiameter),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func refinePlanetTransitContactTT(greatestTT float64, cfg planetTransitConfig, direction int, internal bool) (float64, bool) {
|
||||||
|
if direction != -1 && direction != 1 {
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
metric := func(jdTT float64) float64 {
|
||||||
|
state := planetTransitStateAt(jdTT, cfg, -1)
|
||||||
|
if internal {
|
||||||
|
return state.internalContactMetric
|
||||||
|
}
|
||||||
|
return state.externalContactMetric
|
||||||
|
}
|
||||||
|
|
||||||
|
nearJD := greatestTT
|
||||||
|
nearValue := metric(nearJD)
|
||||||
|
if !isFiniteFloat(nearValue) || nearValue > 0 {
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
maxSteps := int(math.Ceil(planetTransitContactSpanDays / planetTransitContactStepDays))
|
||||||
|
for i := 1; i <= maxSteps; i++ {
|
||||||
|
farJD := greatestTT + float64(direction)*planetTransitContactStepDays*float64(i)
|
||||||
|
farValue := metric(farJD)
|
||||||
|
if !isFiniteFloat(farValue) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if farValue >= 0 {
|
||||||
|
return bisectPlanetTransitContactTT(nearJD, nearValue, farJD, farValue, metric)
|
||||||
|
}
|
||||||
|
nearJD = farJD
|
||||||
|
nearValue = farValue
|
||||||
|
}
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
|
||||||
|
func bisectPlanetTransitContactTT(leftJD, leftValue, rightJD, rightValue float64, metric func(float64) float64) (float64, bool) {
|
||||||
|
if leftJD > rightJD {
|
||||||
|
leftJD, rightJD = rightJD, leftJD
|
||||||
|
leftValue, rightValue = rightValue, leftValue
|
||||||
|
}
|
||||||
|
if leftValue == 0 {
|
||||||
|
return leftJD, true
|
||||||
|
}
|
||||||
|
if rightValue == 0 {
|
||||||
|
return rightJD, true
|
||||||
|
}
|
||||||
|
if leftValue*rightValue > 0 {
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i < 80 && rightJD-leftJD > planetTransitContactToleranceDays; i++ {
|
||||||
|
midJD := (leftJD + rightJD) / 2
|
||||||
|
midValue := metric(midJD)
|
||||||
|
if !isFiniteFloat(midValue) {
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
if midValue == 0 {
|
||||||
|
return midJD, true
|
||||||
|
}
|
||||||
|
if leftValue*midValue <= 0 {
|
||||||
|
rightJD = midJD
|
||||||
|
rightValue = midValue
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
leftJD = midJD
|
||||||
|
leftValue = midValue
|
||||||
|
}
|
||||||
|
return (leftJD + rightJD) / 2, true
|
||||||
|
}
|
||||||
|
|
||||||
|
func planetTransitResultTTToUT(result PlanetTransitResult) PlanetTransitResult {
|
||||||
|
result.Greatest = TD2UT(result.Greatest, false)
|
||||||
|
result.ExternalIngress = TD2UT(result.ExternalIngress, false)
|
||||||
|
result.ExternalEgress = TD2UT(result.ExternalEgress, false)
|
||||||
|
if result.InternalIngress != 0 {
|
||||||
|
result.InternalIngress = TD2UT(result.InternalIngress, false)
|
||||||
|
}
|
||||||
|
if result.InternalEgress != 0 {
|
||||||
|
result.InternalEgress = TD2UT(result.InternalEgress, false)
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func planetTransitMatchesDirection(eventJDE, targetJDE float64, direction int, includeCurrent bool) bool {
|
||||||
|
delta := eventJDE - targetJDE
|
||||||
|
if math.Abs(delta) <= planetTransitSearchEpsilonDays {
|
||||||
|
return includeCurrent
|
||||||
|
}
|
||||||
|
if direction > 0 {
|
||||||
|
return delta > 0
|
||||||
|
}
|
||||||
|
return delta < 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func planetTransitAngleDelta(diff float64) float64 {
|
||||||
|
diff = Limit360(diff)
|
||||||
|
if diff > 180 {
|
||||||
|
diff -= 360
|
||||||
|
}
|
||||||
|
if diff < -180 {
|
||||||
|
diff += 360
|
||||||
|
}
|
||||||
|
return diff
|
||||||
|
}
|
||||||
|
|
||||||
|
func isFiniteFloat(value float64) bool {
|
||||||
|
return !math.IsNaN(value) && !math.IsInf(value, 0)
|
||||||
|
}
|
||||||
@@ -0,0 +1,145 @@
|
|||||||
|
package basic
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestKnownMercuryTransits(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
query time.Time
|
||||||
|
greatest time.Time
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "2016 May",
|
||||||
|
query: time.Date(2016, 1, 1, 0, 0, 0, 0, time.UTC),
|
||||||
|
greatest: time.Date(2016, 5, 9, 14, 57, 0, 0, time.UTC),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "2019 Nov",
|
||||||
|
query: time.Date(2019, 1, 1, 0, 0, 0, 0, time.UTC),
|
||||||
|
greatest: time.Date(2019, 11, 11, 15, 20, 0, 0, time.UTC),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range tests {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
result := NextMercuryTransit(Date2JDE(tc.query))
|
||||||
|
if !result.Valid {
|
||||||
|
t.Fatal("expected valid transit")
|
||||||
|
}
|
||||||
|
got := JDE2DateByZone(result.Greatest, time.UTC, false)
|
||||||
|
t.Logf("start=%s greatest=%s end=%s min=%.3f sun=%.3f planet=%.3f",
|
||||||
|
JDE2DateByZone(result.ExternalIngress, time.UTC, false),
|
||||||
|
got,
|
||||||
|
JDE2DateByZone(result.ExternalEgress, time.UTC, false),
|
||||||
|
result.MinimumSeparationArcsec,
|
||||||
|
result.SunSemidiameterArcsec,
|
||||||
|
result.PlanetSemidiameterArcsec,
|
||||||
|
)
|
||||||
|
if math.Abs(got.Sub(tc.greatest).Seconds()) > 20*60 {
|
||||||
|
t.Fatalf("greatest mismatch: got %s want near %s", got, tc.greatest)
|
||||||
|
}
|
||||||
|
if !result.HasInternal {
|
||||||
|
t.Fatalf("expected internal contacts")
|
||||||
|
}
|
||||||
|
if !(result.ExternalIngress < result.InternalIngress &&
|
||||||
|
result.InternalIngress < result.Greatest &&
|
||||||
|
result.Greatest < result.InternalEgress &&
|
||||||
|
result.InternalEgress < result.ExternalEgress) {
|
||||||
|
t.Fatalf("contacts out of order: %+v", result)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestKnownVenusTransits(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
query time.Time
|
||||||
|
greatest time.Time
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "2004 Jun",
|
||||||
|
query: time.Date(2004, 1, 1, 0, 0, 0, 0, time.UTC),
|
||||||
|
greatest: time.Date(2004, 6, 8, 8, 20, 0, 0, time.UTC),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "2012 Jun",
|
||||||
|
query: time.Date(2012, 1, 1, 0, 0, 0, 0, time.UTC),
|
||||||
|
greatest: time.Date(2012, 6, 6, 1, 29, 0, 0, time.UTC),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range tests {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
result := NextVenusTransit(Date2JDE(tc.query))
|
||||||
|
if !result.Valid {
|
||||||
|
t.Fatal("expected valid transit")
|
||||||
|
}
|
||||||
|
got := JDE2DateByZone(result.Greatest, time.UTC, false)
|
||||||
|
t.Logf("start=%s greatest=%s end=%s min=%.3f sun=%.3f planet=%.3f",
|
||||||
|
JDE2DateByZone(result.ExternalIngress, time.UTC, false),
|
||||||
|
got,
|
||||||
|
JDE2DateByZone(result.ExternalEgress, time.UTC, false),
|
||||||
|
result.MinimumSeparationArcsec,
|
||||||
|
result.SunSemidiameterArcsec,
|
||||||
|
result.PlanetSemidiameterArcsec,
|
||||||
|
)
|
||||||
|
if math.Abs(got.Sub(tc.greatest).Seconds()) > 20*60 {
|
||||||
|
t.Fatalf("greatest mismatch: got %s want near %s", got, tc.greatest)
|
||||||
|
}
|
||||||
|
if !result.HasInternal {
|
||||||
|
t.Fatalf("expected internal contacts")
|
||||||
|
}
|
||||||
|
if !(result.ExternalIngress < result.InternalIngress &&
|
||||||
|
result.InternalIngress < result.Greatest &&
|
||||||
|
result.Greatest < result.InternalEgress &&
|
||||||
|
result.InternalEgress < result.ExternalEgress) {
|
||||||
|
t.Fatalf("contacts out of order: %+v", result)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTransitSearchSkipsSparseEvents(t *testing.T) {
|
||||||
|
mercuryResult := NextMercuryTransit(Date2JDE(time.Date(2026, 1, 1, 0, 0, 0, 0, time.UTC)))
|
||||||
|
if !mercuryResult.Valid {
|
||||||
|
t.Fatal("expected Mercury transit")
|
||||||
|
}
|
||||||
|
mercuryGreatest := JDE2DateByZone(mercuryResult.Greatest, time.UTC, false)
|
||||||
|
if mercuryGreatest.Year() != 2032 || mercuryGreatest.Month() != time.November {
|
||||||
|
t.Fatalf("unexpected next Mercury transit: %s", mercuryGreatest)
|
||||||
|
}
|
||||||
|
|
||||||
|
venusResult := NextVenusTransit(Date2JDE(time.Date(2026, 1, 1, 0, 0, 0, 0, time.UTC)))
|
||||||
|
if !venusResult.Valid {
|
||||||
|
t.Fatal("expected Venus transit")
|
||||||
|
}
|
||||||
|
venusGreatest := JDE2DateByZone(venusResult.Greatest, time.UTC, false)
|
||||||
|
if venusGreatest.Year() != 2117 || venusGreatest.Month() != time.December {
|
||||||
|
t.Fatalf("unexpected next Venus transit: %s", venusGreatest)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkNextMercuryTransitFrom2026(b *testing.B) {
|
||||||
|
jd := Date2JDE(time.Date(2026, 1, 1, 0, 0, 0, 0, time.UTC))
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
result := NextMercuryTransit(jd)
|
||||||
|
if !result.Valid {
|
||||||
|
b.Fatal("expected valid transit")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkNextVenusTransitFrom2026(b *testing.B) {
|
||||||
|
jd := Date2JDE(time.Date(2026, 1, 1, 0, 0, 0, 0, time.UTC))
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
result := NextVenusTransit(jd)
|
||||||
|
if !result.Valid {
|
||||||
|
b.Fatal("expected valid transit")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -24,14 +24,8 @@ func planetXYZN(planetIndex int, jd float64, n int) (float64, float64, float64)
|
|||||||
}
|
}
|
||||||
|
|
||||||
func planetApparentLoBoN(planetIndex int, jd float64, n int) (float64, float64) {
|
func planetApparentLoBoN(planetIndex int, jd float64, n int) (float64, float64) {
|
||||||
x, y, z := planetXYZN(planetIndex, jd, n)
|
geo, _ := planetApparentGeocentricPositionN(planetIndex, jd, n)
|
||||||
to := 0.0057755183 * math.Sqrt(x*x+y*y+z*z)
|
return geo.lo, geo.bo
|
||||||
x, y, z = planetXYZN(planetIndex, jd-to, n)
|
|
||||||
lo := math.Atan2(y, x)
|
|
||||||
bo := math.Atan2(z, math.Sqrt(x*x+y*y))
|
|
||||||
lo = Limit360(lo*180/math.Pi) + Nutation2000Bi(jd)
|
|
||||||
bo = bo * 180 / math.Pi
|
|
||||||
return lo, bo
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func planetApparentRaManualN(planetIndex int, jd float64, n int) float64 {
|
func planetApparentRaManualN(planetIndex int, jd float64, n int) float64 {
|
||||||
@@ -57,8 +51,7 @@ func planetApparentRaDecManualN(planetIndex int, jd float64, n int) (float64, fl
|
|||||||
}
|
}
|
||||||
|
|
||||||
func planetEarthAwayN(planetIndex int, jd float64, n int) float64 {
|
func planetEarthAwayN(planetIndex int, jd float64, n int) float64 {
|
||||||
x, y, z := planetXYZN(planetIndex, jd, n)
|
return planetEarthAwayExplicitN(planetIndex, jd, n)
|
||||||
return math.Sqrt(x*x + y*y + z*z)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func planetHeightN(jde, lon, lat, timezone float64, n int, apparentRaDec func(float64, int) (float64, float64)) float64 {
|
func planetHeightN(jde, lon, lat, timezone float64, n int, apparentRaDec func(float64, int) (float64, float64)) float64 {
|
||||||
|
|||||||
+7
-38
@@ -87,53 +87,22 @@ func SaturnApparentRaDec(jd float64) (float64, float64) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func EarthSaturnAway(jd float64) float64 {
|
func EarthSaturnAway(jd float64) float64 {
|
||||||
x, y, z := ASaturnXYZ(jd)
|
return planetEarthAwayExplicitN(5, jd, -1)
|
||||||
to := math.Sqrt(x*x + y*y + z*z)
|
|
||||||
return to
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func SaturnApparentLo(jd float64) float64 {
|
func SaturnApparentLo(jd float64) float64 {
|
||||||
x, y, z := ASaturnXYZ(jd)
|
geo, _ := planetApparentGeocentricPositionN(5, jd, -1)
|
||||||
to := 0.0057755183 * math.Sqrt(x*x+y*y+z*z)
|
return geo.lo
|
||||||
x, y, z = ASaturnXYZ(jd - to)
|
|
||||||
lo := math.Atan2(y, x)
|
|
||||||
bo := math.Atan2(z, math.Sqrt(x*x+y*y))
|
|
||||||
lo = lo * 180 / math.Pi
|
|
||||||
bo = bo * 180 / math.Pi
|
|
||||||
lo = Limit360(lo)
|
|
||||||
//lo-=GXCLo(lo,bo,jd)/3600;
|
|
||||||
//bo+=GXCBo(lo,bo,jd);
|
|
||||||
lo += Nutation2000Bi(jd)
|
|
||||||
return lo
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func SaturnApparentBo(jd float64) float64 {
|
func SaturnApparentBo(jd float64) float64 {
|
||||||
x, y, z := ASaturnXYZ(jd)
|
geo, _ := planetApparentGeocentricPositionN(5, jd, -1)
|
||||||
to := 0.0057755183 * math.Sqrt(x*x+y*y+z*z)
|
return geo.bo
|
||||||
x, y, z = ASaturnXYZ(jd - to)
|
|
||||||
//lo := math.Atan2(y, x)
|
|
||||||
bo := math.Atan2(z, math.Sqrt(x*x+y*y))
|
|
||||||
//lo = lo * 180 / math.Pi
|
|
||||||
bo = bo * 180 / math.Pi
|
|
||||||
//lo+=GXCLo(lo,bo,jd);
|
|
||||||
//bo+=GXCBo(lo,bo,jd)/3600;
|
|
||||||
//lo+=Nutation2000Bi(jd);
|
|
||||||
return bo
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func SaturnApparentLoBo(jd float64) (float64, float64) {
|
func SaturnApparentLoBo(jd float64) (float64, float64) {
|
||||||
x, y, z := ASaturnXYZ(jd)
|
geo, _ := planetApparentGeocentricPositionN(5, jd, -1)
|
||||||
to := 0.0057755183 * math.Sqrt(x*x+y*y+z*z)
|
return geo.lo, geo.bo
|
||||||
x, y, z = ASaturnXYZ(jd - to)
|
|
||||||
lo := math.Atan2(y, x)
|
|
||||||
bo := math.Atan2(z, math.Sqrt(x*x+y*y))
|
|
||||||
lo = lo * 180 / math.Pi
|
|
||||||
bo = bo * 180 / math.Pi
|
|
||||||
lo = Limit360(lo)
|
|
||||||
//lo-=GXCLo(lo,bo,jd)/3600;
|
|
||||||
//bo+=GXCBo(lo,bo,jd);
|
|
||||||
lo += Nutation2000Bi(jd)
|
|
||||||
return lo, bo
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func SaturnMag(jd float64) float64 {
|
func SaturnMag(jd float64) float64 {
|
||||||
|
|||||||
+66
-65
@@ -40,6 +40,28 @@ func saturnSunLongitudeDeltaN(jde, degree float64, filter bool, n int) float64 {
|
|||||||
return sub
|
return sub
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func saturnRADerivative(jde, delta float64) float64 {
|
||||||
|
sub := SaturnApparentRa(jde+delta) - SaturnApparentRa(jde-delta)
|
||||||
|
if sub > 180 {
|
||||||
|
sub -= 360
|
||||||
|
}
|
||||||
|
if sub < -180 {
|
||||||
|
sub += 360
|
||||||
|
}
|
||||||
|
return sub / (2 * delta)
|
||||||
|
}
|
||||||
|
|
||||||
|
func saturnRADerivativeN(jde, delta float64, n int) float64 {
|
||||||
|
sub := SaturnApparentRaN(jde+delta, n) - SaturnApparentRaN(jde-delta, n)
|
||||||
|
if sub > 180 {
|
||||||
|
sub -= 360
|
||||||
|
}
|
||||||
|
if sub < -180 {
|
||||||
|
sub += 360
|
||||||
|
}
|
||||||
|
return sub / (2 * delta)
|
||||||
|
}
|
||||||
|
|
||||||
func saturnConjunctionFull(jde, degree float64, next uint8) float64 {
|
func saturnConjunctionFull(jde, degree float64, next uint8) float64 {
|
||||||
//0=last 1=next
|
//0=last 1=next
|
||||||
daysPerDegree := SATURN_S_PERIOD / 360
|
daysPerDegree := SATURN_S_PERIOD / 360
|
||||||
@@ -94,113 +116,92 @@ func saturnConjunction(jde, degree float64, next uint8) float64 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func LastSaturnConjunction(jde float64) float64 {
|
func LastSaturnConjunction(jde float64) float64 {
|
||||||
return saturnConjunction(jde, 0, 0)
|
return inclusiveLastPhaseEvent(jde, 0, saturnConjunction)
|
||||||
}
|
}
|
||||||
|
|
||||||
func NextSaturnConjunction(jde float64) float64 {
|
func NextSaturnConjunction(jde float64) float64 {
|
||||||
return saturnConjunction(jde, 0, 1)
|
return inclusiveNextPhaseEvent(jde, 0, saturnConjunction)
|
||||||
}
|
}
|
||||||
|
|
||||||
func LastSaturnOpposition(jde float64) float64 {
|
func LastSaturnOpposition(jde float64) float64 {
|
||||||
return saturnConjunction(jde, 180, 0)
|
return inclusiveLastPhaseEvent(jde, 180, saturnConjunction)
|
||||||
}
|
}
|
||||||
|
|
||||||
func NextSaturnOpposition(jde float64) float64 {
|
func NextSaturnOpposition(jde float64) float64 {
|
||||||
return saturnConjunction(jde, 180, 1)
|
return inclusiveNextPhaseEvent(jde, 180, saturnConjunction)
|
||||||
}
|
}
|
||||||
|
|
||||||
func NextSaturnEasternQuadrature(jde float64) float64 {
|
func NextSaturnEasternQuadrature(jde float64) float64 {
|
||||||
return saturnConjunction(jde, 90, 1)
|
return inclusiveNextPhaseEvent(jde, 90, saturnConjunction)
|
||||||
}
|
}
|
||||||
|
|
||||||
func LastSaturnEasternQuadrature(jde float64) float64 {
|
func LastSaturnEasternQuadrature(jde float64) float64 {
|
||||||
return saturnConjunction(jde, 90, 0)
|
return inclusiveLastPhaseEvent(jde, 90, saturnConjunction)
|
||||||
}
|
}
|
||||||
|
|
||||||
func NextSaturnWesternQuadrature(jde float64) float64 {
|
func NextSaturnWesternQuadrature(jde float64) float64 {
|
||||||
return saturnConjunction(jde, 270, 1)
|
return inclusiveNextPhaseEvent(jde, 270, saturnConjunction)
|
||||||
}
|
}
|
||||||
|
|
||||||
func LastSaturnWesternQuadrature(jde float64) float64 {
|
func LastSaturnWesternQuadrature(jde float64) float64 {
|
||||||
return saturnConjunction(jde, 270, 0)
|
return inclusiveLastPhaseEvent(jde, 270, saturnConjunction)
|
||||||
}
|
}
|
||||||
|
|
||||||
func saturnRetrograde(jde float64, searchBeforeOpposition bool) float64 {
|
func saturnRetrogradeAroundOpposition(oppositionJD float64, searchBeforeOpposition bool) float64 {
|
||||||
//0=last 1=next
|
oppositionTT := TD2UT(oppositionJD, true)
|
||||||
raRate := func(jde float64, delta float64) float64 {
|
startTT := oppositionTT
|
||||||
sub := SaturnApparentRa(jde+delta) - SaturnApparentRa(jde-delta)
|
endTT := oppositionTT
|
||||||
if sub > 180 {
|
|
||||||
sub -= 360
|
|
||||||
}
|
|
||||||
if sub < -180 {
|
|
||||||
sub += 360
|
|
||||||
}
|
|
||||||
return sub / (2 * delta)
|
|
||||||
}
|
|
||||||
jde = saturnConjunctionFull(jde, 180, 1)
|
|
||||||
if searchBeforeOpposition {
|
if searchBeforeOpposition {
|
||||||
jde -= 60
|
easternQuadratureUT := saturnConjunction(oppositionTT, 90, 0)
|
||||||
|
startTT = TD2UT(easternQuadratureUT, true)
|
||||||
} else {
|
} else {
|
||||||
jde += 60
|
westernQuadratureUT := saturnConjunction(oppositionTT, 270, 1)
|
||||||
|
endTT = TD2UT(westernQuadratureUT, true)
|
||||||
}
|
}
|
||||||
for {
|
bestJD := zeroEventInWindow(startTT, endTT, 2.0, 2.0, 30.0/86400.0, func(jd float64) float64 {
|
||||||
currentRate := raRate(jde, 1.0/86400.0)
|
return saturnRADerivativeN(jd, 1.0/86400.0, saturnEventSearchN)
|
||||||
if math.Abs(currentRate) > 0.55 {
|
}, func(jd float64) float64 {
|
||||||
jde += 2
|
return saturnRADerivative(jd, 0.5/86400.0)
|
||||||
continue
|
|
||||||
}
|
|
||||||
break
|
|
||||||
}
|
|
||||||
estimateJD := jde
|
|
||||||
for {
|
|
||||||
prevJD := estimateJD
|
|
||||||
rateValue := raRate(prevJD, 2.0/86400.0)
|
|
||||||
rateSlope := (raRate(prevJD+15.0/86400.0, 2.0/86400.0) - raRate(prevJD-15.0/86400.0, 2.0/86400.0)) / (30.0 / 86400.0)
|
|
||||||
estimateJD = prevJD - rateValue/rateSlope
|
|
||||||
if math.Abs(estimateJD-prevJD) <= 30.0/86400.0 {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
bestJD := eventZeroRefine(estimateJD, 15.0/86400.0, 0.5/86400.0, func(jd float64) float64 {
|
|
||||||
return raRate(jd, 0.5/86400.0)
|
|
||||||
})
|
})
|
||||||
return TD2UT(bestJD, false)
|
return TD2UT(bestJD, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
func NextSaturnRetrogradeToPrograde(jde float64) float64 {
|
func NextSaturnRetrogradeToPrograde(jde float64) float64 {
|
||||||
date := saturnRetrograde(jde, false)
|
lastOppositionJD := saturnConjunctionFull(jde, 180, 0)
|
||||||
if date < jde {
|
date := saturnRetrogradeAroundOpposition(lastOppositionJD, false)
|
||||||
oppositionJD := saturnConjunctionFull(jde, 180, 1)
|
if sameEventUTQueryTT(date, jde) || eventUTQueryAfterOrEqual(date, jde) {
|
||||||
return saturnRetrograde(oppositionJD+10, false)
|
|
||||||
}
|
|
||||||
return date
|
return date
|
||||||
}
|
}
|
||||||
|
nextOppositionJD := saturnConjunctionFull(jde, 180, 1)
|
||||||
|
return saturnRetrogradeAroundOpposition(nextOppositionJD, false)
|
||||||
|
}
|
||||||
|
|
||||||
func LastSaturnRetrogradeToPrograde(jde float64) float64 {
|
func LastSaturnRetrogradeToPrograde(jde float64) float64 {
|
||||||
jde = saturnConjunctionFull(jde, 180, 0) - 10
|
lastOppositionJD := saturnConjunctionFull(jde, 180, 0)
|
||||||
date := saturnRetrograde(jde, false)
|
date := saturnRetrogradeAroundOpposition(lastOppositionJD, false)
|
||||||
if date > jde {
|
if sameEventUTQueryTT(date, jde) || eventUTQueryBeforeOrEqual(date, jde) {
|
||||||
oppositionJD := saturnConjunctionFull(jde, 180, 0)
|
|
||||||
return saturnRetrograde(oppositionJD-10, false)
|
|
||||||
}
|
|
||||||
return date
|
return date
|
||||||
}
|
}
|
||||||
|
previousOppositionJD := saturnConjunctionFull(eventUTLastQueryTT(lastOppositionJD), 180, 0)
|
||||||
|
return saturnRetrogradeAroundOpposition(previousOppositionJD, false)
|
||||||
|
}
|
||||||
|
|
||||||
func NextSaturnProgradeToRetrograde(jde float64) float64 {
|
func NextSaturnProgradeToRetrograde(jde float64) float64 {
|
||||||
date := saturnRetrograde(jde, true)
|
nextOppositionJD := saturnConjunctionFull(jde, 180, 1)
|
||||||
if date < jde {
|
date := saturnRetrogradeAroundOpposition(nextOppositionJD, true)
|
||||||
oppositionJD := saturnConjunctionFull(jde, 180, 1)
|
if sameEventUTQueryTT(date, jde) || eventUTQueryAfterOrEqual(date, jde) {
|
||||||
return saturnRetrograde(oppositionJD+10, true)
|
|
||||||
}
|
|
||||||
return date
|
return date
|
||||||
}
|
}
|
||||||
|
followingOppositionJD := saturnConjunctionFull(eventUTNextQueryTT(nextOppositionJD), 180, 1)
|
||||||
|
return saturnRetrogradeAroundOpposition(followingOppositionJD, true)
|
||||||
|
}
|
||||||
|
|
||||||
func LastSaturnProgradeToRetrograde(jde float64) float64 {
|
func LastSaturnProgradeToRetrograde(jde float64) float64 {
|
||||||
jde = saturnConjunctionFull(jde, 180, 0) - 10
|
nextOppositionJD := saturnConjunctionFull(jde, 180, 1)
|
||||||
date := saturnRetrograde(jde, true)
|
date := saturnRetrogradeAroundOpposition(nextOppositionJD, true)
|
||||||
if date > jde {
|
if sameEventUTQueryTT(date, jde) || eventUTQueryBeforeOrEqual(date, jde) {
|
||||||
oppositionJD := saturnConjunctionFull(jde, 180, 0)
|
|
||||||
return saturnRetrograde(oppositionJD-10, true)
|
|
||||||
}
|
|
||||||
return date
|
return date
|
||||||
}
|
}
|
||||||
|
lastOppositionJD := saturnConjunctionFull(jde, 180, 0)
|
||||||
|
return saturnRetrogradeAroundOpposition(lastOppositionJD, true)
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,207 @@
|
|||||||
|
package basic
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type stationEvent struct {
|
||||||
|
when time.Time
|
||||||
|
kind string
|
||||||
|
}
|
||||||
|
|
||||||
|
type stationTruthCase struct {
|
||||||
|
name string
|
||||||
|
events []stationEvent
|
||||||
|
lastR2P func(float64) float64
|
||||||
|
nextR2P func(float64) float64
|
||||||
|
lastP2R func(float64) float64
|
||||||
|
nextP2R func(float64) float64
|
||||||
|
}
|
||||||
|
|
||||||
|
func mustJST(value string) time.Time {
|
||||||
|
loc := time.FixedZone("JST", 9*3600)
|
||||||
|
t, err := time.ParseInLocation("2006-01-02 15:04", value, loc)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return t
|
||||||
|
}
|
||||||
|
|
||||||
|
func toUTJD(t time.Time) float64 {
|
||||||
|
return TD2UT(Date2JDE(t.UTC()), true)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStationTruthAgainstNAOJ(t *testing.T) {
|
||||||
|
cases := []stationTruthCase{
|
||||||
|
{
|
||||||
|
name: "Mars",
|
||||||
|
events: []stationEvent{
|
||||||
|
{when: mustJST("2024-12-08 05:59"), kind: "P2R"},
|
||||||
|
{when: mustJST("2025-01-16 11:39"), kind: "OPP"},
|
||||||
|
{when: mustJST("2025-02-24 18:35"), kind: "R2P"},
|
||||||
|
{when: mustJST("2027-01-12 01:10"), kind: "P2R"},
|
||||||
|
{when: mustJST("2027-02-20 00:51"), kind: "OPP"},
|
||||||
|
{when: mustJST("2027-04-03 02:33"), kind: "R2P"},
|
||||||
|
},
|
||||||
|
lastR2P: LastMarsRetrogradeToPrograde,
|
||||||
|
nextR2P: NextMarsRetrogradeToPrograde,
|
||||||
|
lastP2R: LastMarsProgradeToRetrograde,
|
||||||
|
nextP2R: NextMarsProgradeToRetrograde,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Jupiter",
|
||||||
|
events: []stationEvent{
|
||||||
|
{when: mustJST("2024-10-09 16:13"), kind: "P2R"},
|
||||||
|
{when: mustJST("2024-12-08 05:58"), kind: "OPP"},
|
||||||
|
{when: mustJST("2025-02-04 22:07"), kind: "R2P"},
|
||||||
|
{when: mustJST("2025-11-12 04:54"), kind: "P2R"},
|
||||||
|
{when: mustJST("2026-01-10 17:42"), kind: "OPP"},
|
||||||
|
{when: mustJST("2026-03-11 11:44"), kind: "R2P"},
|
||||||
|
{when: mustJST("2026-12-13 21:03"), kind: "P2R"},
|
||||||
|
{when: mustJST("2027-02-11 09:29"), kind: "OPP"},
|
||||||
|
{when: mustJST("2027-04-13 15:17"), kind: "R2P"},
|
||||||
|
},
|
||||||
|
lastR2P: LastJupiterRetrogradeToPrograde,
|
||||||
|
nextR2P: NextJupiterRetrogradeToPrograde,
|
||||||
|
lastP2R: LastJupiterProgradeToRetrograde,
|
||||||
|
nextP2R: NextJupiterProgradeToRetrograde,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Saturn",
|
||||||
|
events: []stationEvent{
|
||||||
|
{when: mustJST("2024-07-01 06:15"), kind: "P2R"},
|
||||||
|
{when: mustJST("2024-09-08 13:35"), kind: "OPP"},
|
||||||
|
{when: mustJST("2024-11-16 14:57"), kind: "R2P"},
|
||||||
|
{when: mustJST("2025-07-14 16:57"), kind: "P2R"},
|
||||||
|
{when: mustJST("2025-09-21 14:46"), kind: "OPP"},
|
||||||
|
{when: mustJST("2025-11-29 09:35"), kind: "R2P"},
|
||||||
|
{when: mustJST("2026-07-28 08:09"), kind: "P2R"},
|
||||||
|
{when: mustJST("2026-10-04 21:29"), kind: "OPP"},
|
||||||
|
{when: mustJST("2026-12-12 08:21"), kind: "R2P"},
|
||||||
|
{when: mustJST("2027-08-11 02:53"), kind: "P2R"},
|
||||||
|
{when: mustJST("2027-10-18 09:36"), kind: "OPP"},
|
||||||
|
{when: mustJST("2027-12-25 12:05"), kind: "R2P"},
|
||||||
|
},
|
||||||
|
lastR2P: LastSaturnRetrogradeToPrograde,
|
||||||
|
nextR2P: NextSaturnRetrogradeToPrograde,
|
||||||
|
lastP2R: LastSaturnProgradeToRetrograde,
|
||||||
|
nextP2R: NextSaturnProgradeToRetrograde,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Uranus",
|
||||||
|
events: []stationEvent{
|
||||||
|
{when: mustJST("2024-01-27 19:50"), kind: "R2P"},
|
||||||
|
{when: mustJST("2024-09-02 00:44"), kind: "P2R"},
|
||||||
|
{when: mustJST("2024-11-17 11:45"), kind: "OPP"},
|
||||||
|
{when: mustJST("2025-01-31 04:04"), kind: "R2P"},
|
||||||
|
{when: mustJST("2025-09-06 13:55"), kind: "P2R"},
|
||||||
|
{when: mustJST("2025-11-21 21:25"), kind: "OPP"},
|
||||||
|
{when: mustJST("2026-02-04 13:37"), kind: "R2P"},
|
||||||
|
{when: mustJST("2026-09-11 03:19"), kind: "P2R"},
|
||||||
|
{when: mustJST("2026-11-26 07:41"), kind: "OPP"},
|
||||||
|
{when: mustJST("2027-02-08 23:03"), kind: "R2P"},
|
||||||
|
{when: mustJST("2027-09-15 17:50"), kind: "P2R"},
|
||||||
|
{when: mustJST("2027-11-30 18:22"), kind: "OPP"},
|
||||||
|
},
|
||||||
|
lastR2P: LastUranusRetrogradeToPrograde,
|
||||||
|
nextR2P: NextUranusRetrogradeToPrograde,
|
||||||
|
lastP2R: LastUranusProgradeToRetrograde,
|
||||||
|
nextP2R: NextUranusProgradeToRetrograde,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Neptune",
|
||||||
|
events: []stationEvent{
|
||||||
|
{when: mustJST("2024-07-03 12:08"), kind: "P2R"},
|
||||||
|
{when: mustJST("2024-09-21 09:17"), kind: "OPP"},
|
||||||
|
{when: mustJST("2024-12-08 20:05"), kind: "R2P"},
|
||||||
|
{when: mustJST("2025-07-05 23:30"), kind: "P2R"},
|
||||||
|
{when: mustJST("2025-09-23 21:54"), kind: "OPP"},
|
||||||
|
{when: mustJST("2025-12-11 09:21"), kind: "R2P"},
|
||||||
|
{when: mustJST("2026-07-08 13:02"), kind: "P2R"},
|
||||||
|
{when: mustJST("2026-09-26 10:36"), kind: "OPP"},
|
||||||
|
{when: mustJST("2026-12-13 19:47"), kind: "R2P"},
|
||||||
|
{when: mustJST("2027-07-11 01:06"), kind: "P2R"},
|
||||||
|
{when: mustJST("2027-09-28 23:19"), kind: "OPP"},
|
||||||
|
{when: mustJST("2027-12-16 07:16"), kind: "R2P"},
|
||||||
|
},
|
||||||
|
lastR2P: LastNeptuneRetrogradeToPrograde,
|
||||||
|
nextR2P: NextNeptuneRetrogradeToPrograde,
|
||||||
|
lastP2R: LastNeptuneProgradeToRetrograde,
|
||||||
|
nextP2R: NextNeptuneProgradeToRetrograde,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range cases {
|
||||||
|
tc := tc
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
for i, event := range tc.events {
|
||||||
|
switch event.kind {
|
||||||
|
case "P2R":
|
||||||
|
before := event.when.Add(-24 * time.Hour)
|
||||||
|
after := event.when.Add(24 * time.Hour)
|
||||||
|
nextP2R := JDE2DateByZone(tc.nextP2R(toUTJD(before)), event.when.Location(), false)
|
||||||
|
lastP2R := JDE2DateByZone(tc.lastP2R(toUTJD(after)), event.when.Location(), false)
|
||||||
|
if !sameMinute(nextP2R, event.when) {
|
||||||
|
t.Fatalf("%s next P2R mismatch: got %s want %s", tc.name, nextP2R, event.when)
|
||||||
|
}
|
||||||
|
if !sameMinute(lastP2R, event.when) {
|
||||||
|
t.Fatalf("%s last P2R mismatch: got %s want %s", tc.name, lastP2R, event.when)
|
||||||
|
}
|
||||||
|
case "R2P":
|
||||||
|
before := event.when.Add(-24 * time.Hour)
|
||||||
|
after := event.when.Add(24 * time.Hour)
|
||||||
|
nextR2P := JDE2DateByZone(tc.nextR2P(toUTJD(before)), event.when.Location(), false)
|
||||||
|
lastR2P := JDE2DateByZone(tc.lastR2P(toUTJD(after)), event.when.Location(), false)
|
||||||
|
if !sameMinute(nextR2P, event.when) {
|
||||||
|
t.Fatalf("%s next R2P mismatch: got %s want %s", tc.name, nextR2P, event.when)
|
||||||
|
}
|
||||||
|
if !sameMinute(lastR2P, event.when) {
|
||||||
|
t.Fatalf("%s last R2P mismatch: got %s want %s", tc.name, lastR2P, event.when)
|
||||||
|
}
|
||||||
|
case "OPP":
|
||||||
|
prev := nearestOfKindBefore(tc.events, i, "P2R")
|
||||||
|
next := nearestOfKindAfter(tc.events, i, "R2P")
|
||||||
|
if prev.IsZero() || next.IsZero() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
query := event.when
|
||||||
|
lastP2R := JDE2DateByZone(tc.lastP2R(toUTJD(query)), query.Location(), false)
|
||||||
|
nextR2P := JDE2DateByZone(tc.nextR2P(toUTJD(query)), query.Location(), false)
|
||||||
|
if !sameMinute(lastP2R, prev) {
|
||||||
|
t.Fatalf("%s opposition last P2R mismatch: got %s want %s", tc.name, lastP2R, prev)
|
||||||
|
}
|
||||||
|
if !sameMinute(nextR2P, next) {
|
||||||
|
t.Fatalf("%s opposition next R2P mismatch: got %s want %s", tc.name, nextR2P, next)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func nearestOfKindBefore(events []stationEvent, idx int, kind string) time.Time {
|
||||||
|
for i := idx - 1; i >= 0; i-- {
|
||||||
|
if events[i].kind == kind {
|
||||||
|
return events[i].when
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return time.Time{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func nearestOfKindAfter(events []stationEvent, idx int, kind string) time.Time {
|
||||||
|
for i := idx + 1; i < len(events); i++ {
|
||||||
|
if events[i].kind == kind {
|
||||||
|
return events[i].when
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return time.Time{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func sameMinute(got, want time.Time) bool {
|
||||||
|
diff := got.Sub(want)
|
||||||
|
if diff < 0 {
|
||||||
|
diff = -diff
|
||||||
|
}
|
||||||
|
return diff <= 2*time.Minute
|
||||||
|
}
|
||||||
+6055
File diff suppressed because it is too large
Load Diff
+1841
File diff suppressed because it is too large
Load Diff
+1967
File diff suppressed because it is too large
Load Diff
+2023
File diff suppressed because it is too large
Load Diff
+6622
File diff suppressed because it is too large
Load Diff
+1152
-1152
File diff suppressed because it is too large
Load Diff
+1722
-1722
File diff suppressed because it is too large
Load Diff
+1728
-1728
File diff suppressed because it is too large
Load Diff
+2597
File diff suppressed because it is too large
Load Diff
+1152
-1152
File diff suppressed because it is too large
Load Diff
+58
@@ -0,0 +1,58 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"body": "mercury",
|
||||||
|
"input_utc": "2026-01-01T00:00:00Z",
|
||||||
|
"right_ascension": 268.524035973,
|
||||||
|
"declination": -24.001291126,
|
||||||
|
"ecliptic_longitude": 268.6516112,
|
||||||
|
"ecliptic_latitude": -0.5700521
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"body": "venus",
|
||||||
|
"input_utc": "2026-01-01T00:00:00Z",
|
||||||
|
"right_ascension": 280.056455581,
|
||||||
|
"declination": -23.622404581,
|
||||||
|
"ecliptic_longitude": 279.2064734,
|
||||||
|
"ecliptic_latitude": -0.5050645
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"body": "mars",
|
||||||
|
"input_utc": "2026-01-01T00:00:00Z",
|
||||||
|
"right_ascension": 283.879610807,
|
||||||
|
"declination": -23.720007274,
|
||||||
|
"ecliptic_longitude": 282.6881475,
|
||||||
|
"ecliptic_latitude": -0.8911035
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"body": "jupiter",
|
||||||
|
"input_utc": "2026-01-01T00:00:00Z",
|
||||||
|
"right_ascension": 113.124332352,
|
||||||
|
"declination": 21.979135798,
|
||||||
|
"ecliptic_longitude": 111.3575894,
|
||||||
|
"ecliptic_latitude": 0.2391257
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"body": "saturn",
|
||||||
|
"input_utc": "2026-01-01T00:00:00Z",
|
||||||
|
"right_ascension": 357.380959207,
|
||||||
|
"declination": -3.596394732,
|
||||||
|
"ecliptic_longitude": 356.1672313,
|
||||||
|
"ecliptic_latitude": -2.2587419
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"body": "uranus",
|
||||||
|
"input_utc": "2026-01-01T00:00:00Z",
|
||||||
|
"right_ascension": 55.737099009,
|
||||||
|
"declination": 19.509648526,
|
||||||
|
"ecliptic_longitude": 57.9492508,
|
||||||
|
"ecliptic_latitude": -0.1975992
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"body": "neptune",
|
||||||
|
"input_utc": "2026-01-01T00:00:00Z",
|
||||||
|
"right_ascension": 0.077612938,
|
||||||
|
"declination": -1.418610222,
|
||||||
|
"ecliptic_longitude": 359.5068407,
|
||||||
|
"ecliptic_latitude": -1.3324096
|
||||||
|
}
|
||||||
|
]
|
||||||
+1152
-1152
File diff suppressed because it is too large
Load Diff
+1152
-1152
File diff suppressed because it is too large
Load Diff
+1728
-1728
File diff suppressed because it is too large
Load Diff
+7
-38
@@ -87,53 +87,22 @@ func UranusApparentRaDec(jd float64) (float64, float64) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func EarthUranusAway(jd float64) float64 {
|
func EarthUranusAway(jd float64) float64 {
|
||||||
x, y, z := AUranusXYZ(jd)
|
return planetEarthAwayExplicitN(6, jd, -1)
|
||||||
to := math.Sqrt(x*x + y*y + z*z)
|
|
||||||
return to
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func UranusApparentLo(jd float64) float64 {
|
func UranusApparentLo(jd float64) float64 {
|
||||||
x, y, z := AUranusXYZ(jd)
|
geo, _ := planetApparentGeocentricPositionN(6, jd, -1)
|
||||||
to := 0.0057755183 * math.Sqrt(x*x+y*y+z*z)
|
return geo.lo
|
||||||
x, y, z = AUranusXYZ(jd - to)
|
|
||||||
lo := math.Atan2(y, x)
|
|
||||||
bo := math.Atan2(z, math.Sqrt(x*x+y*y))
|
|
||||||
lo = lo * 180 / math.Pi
|
|
||||||
bo = bo * 180 / math.Pi
|
|
||||||
lo = Limit360(lo)
|
|
||||||
//lo-=GXCLo(lo,bo,jd)/3600;
|
|
||||||
//bo+=GXCBo(lo,bo,jd);
|
|
||||||
lo += Nutation2000Bi(jd)
|
|
||||||
return lo
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func UranusApparentBo(jd float64) float64 {
|
func UranusApparentBo(jd float64) float64 {
|
||||||
x, y, z := AUranusXYZ(jd)
|
geo, _ := planetApparentGeocentricPositionN(6, jd, -1)
|
||||||
to := 0.0057755183 * math.Sqrt(x*x+y*y+z*z)
|
return geo.bo
|
||||||
x, y, z = AUranusXYZ(jd - to)
|
|
||||||
//lo := math.Atan2(y, x)
|
|
||||||
bo := math.Atan2(z, math.Sqrt(x*x+y*y))
|
|
||||||
//lo = lo * 180 / math.Pi
|
|
||||||
bo = bo * 180 / math.Pi
|
|
||||||
//lo+=GXCLo(lo,bo,jd);
|
|
||||||
//bo+=GXCBo(lo,bo,jd)/3600;
|
|
||||||
//lo+=Nutation2000Bi(jd);
|
|
||||||
return bo
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func UranusApparentLoBo(jd float64) (float64, float64) {
|
func UranusApparentLoBo(jd float64) (float64, float64) {
|
||||||
x, y, z := AUranusXYZ(jd)
|
geo, _ := planetApparentGeocentricPositionN(6, jd, -1)
|
||||||
to := 0.0057755183 * math.Sqrt(x*x+y*y+z*z)
|
return geo.lo, geo.bo
|
||||||
x, y, z = AUranusXYZ(jd - to)
|
|
||||||
lo := math.Atan2(y, x)
|
|
||||||
bo := math.Atan2(z, math.Sqrt(x*x+y*y))
|
|
||||||
lo = lo * 180 / math.Pi
|
|
||||||
bo = bo * 180 / math.Pi
|
|
||||||
lo = Limit360(lo)
|
|
||||||
//lo-=GXCLo(lo,bo,jd)/3600;
|
|
||||||
//bo+=GXCBo(lo,bo,jd);
|
|
||||||
lo += Nutation2000Bi(jd)
|
|
||||||
return lo, bo
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func UranusMag(jd float64) float64 {
|
func UranusMag(jd float64) float64 {
|
||||||
|
|||||||
+66
-65
@@ -40,6 +40,28 @@ func uranusSunLongitudeDeltaN(jde, degree float64, filter bool, n int) float64 {
|
|||||||
return sub
|
return sub
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func uranusRADerivative(jde, delta float64) float64 {
|
||||||
|
sub := UranusApparentRa(jde+delta) - UranusApparentRa(jde-delta)
|
||||||
|
if sub > 180 {
|
||||||
|
sub -= 360
|
||||||
|
}
|
||||||
|
if sub < -180 {
|
||||||
|
sub += 360
|
||||||
|
}
|
||||||
|
return sub / (2 * delta)
|
||||||
|
}
|
||||||
|
|
||||||
|
func uranusRADerivativeN(jde, delta float64, n int) float64 {
|
||||||
|
sub := UranusApparentRaN(jde+delta, n) - UranusApparentRaN(jde-delta, n)
|
||||||
|
if sub > 180 {
|
||||||
|
sub -= 360
|
||||||
|
}
|
||||||
|
if sub < -180 {
|
||||||
|
sub += 360
|
||||||
|
}
|
||||||
|
return sub / (2 * delta)
|
||||||
|
}
|
||||||
|
|
||||||
func uranusConjunctionFull(jde, degree float64, next uint8) float64 {
|
func uranusConjunctionFull(jde, degree float64, next uint8) float64 {
|
||||||
//0=last 1=next
|
//0=last 1=next
|
||||||
daysPerDegree := URANUS_S_PERIOD / 360
|
daysPerDegree := URANUS_S_PERIOD / 360
|
||||||
@@ -94,113 +116,92 @@ func uranusConjunction(jde, degree float64, next uint8) float64 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func LastUranusConjunction(jde float64) float64 {
|
func LastUranusConjunction(jde float64) float64 {
|
||||||
return uranusConjunction(jde, 0, 0)
|
return inclusiveLastPhaseEvent(jde, 0, uranusConjunction)
|
||||||
}
|
}
|
||||||
|
|
||||||
func NextUranusConjunction(jde float64) float64 {
|
func NextUranusConjunction(jde float64) float64 {
|
||||||
return uranusConjunction(jde, 0, 1)
|
return inclusiveNextPhaseEvent(jde, 0, uranusConjunction)
|
||||||
}
|
}
|
||||||
|
|
||||||
func LastUranusOpposition(jde float64) float64 {
|
func LastUranusOpposition(jde float64) float64 {
|
||||||
return uranusConjunction(jde, 180, 0)
|
return inclusiveLastPhaseEvent(jde, 180, uranusConjunction)
|
||||||
}
|
}
|
||||||
|
|
||||||
func NextUranusOpposition(jde float64) float64 {
|
func NextUranusOpposition(jde float64) float64 {
|
||||||
return uranusConjunction(jde, 180, 1)
|
return inclusiveNextPhaseEvent(jde, 180, uranusConjunction)
|
||||||
}
|
}
|
||||||
|
|
||||||
func NextUranusEasternQuadrature(jde float64) float64 {
|
func NextUranusEasternQuadrature(jde float64) float64 {
|
||||||
return uranusConjunction(jde, 90, 1)
|
return inclusiveNextPhaseEvent(jde, 90, uranusConjunction)
|
||||||
}
|
}
|
||||||
|
|
||||||
func LastUranusEasternQuadrature(jde float64) float64 {
|
func LastUranusEasternQuadrature(jde float64) float64 {
|
||||||
return uranusConjunction(jde, 90, 0)
|
return inclusiveLastPhaseEvent(jde, 90, uranusConjunction)
|
||||||
}
|
}
|
||||||
|
|
||||||
func NextUranusWesternQuadrature(jde float64) float64 {
|
func NextUranusWesternQuadrature(jde float64) float64 {
|
||||||
return uranusConjunction(jde, 270, 1)
|
return inclusiveNextPhaseEvent(jde, 270, uranusConjunction)
|
||||||
}
|
}
|
||||||
|
|
||||||
func LastUranusWesternQuadrature(jde float64) float64 {
|
func LastUranusWesternQuadrature(jde float64) float64 {
|
||||||
return uranusConjunction(jde, 270, 0)
|
return inclusiveLastPhaseEvent(jde, 270, uranusConjunction)
|
||||||
}
|
}
|
||||||
|
|
||||||
func uranusRetrograde(jde float64, searchBeforeOpposition bool) float64 {
|
func uranusRetrogradeAroundOpposition(oppositionJD float64, searchBeforeOpposition bool) float64 {
|
||||||
//0=last 1=next
|
oppositionTT := TD2UT(oppositionJD, true)
|
||||||
raRate := func(jde float64, delta float64) float64 {
|
startTT := oppositionTT
|
||||||
sub := UranusApparentRa(jde+delta) - UranusApparentRa(jde-delta)
|
endTT := oppositionTT
|
||||||
if sub > 180 {
|
|
||||||
sub -= 360
|
|
||||||
}
|
|
||||||
if sub < -180 {
|
|
||||||
sub += 360
|
|
||||||
}
|
|
||||||
return sub / (2 * delta)
|
|
||||||
}
|
|
||||||
jde = uranusConjunctionFull(jde, 180, 1)
|
|
||||||
if searchBeforeOpposition {
|
if searchBeforeOpposition {
|
||||||
jde -= 60
|
easternQuadratureUT := uranusConjunction(oppositionTT, 90, 0)
|
||||||
|
startTT = TD2UT(easternQuadratureUT, true)
|
||||||
} else {
|
} else {
|
||||||
jde += 60
|
westernQuadratureUT := uranusConjunction(oppositionTT, 270, 1)
|
||||||
|
endTT = TD2UT(westernQuadratureUT, true)
|
||||||
}
|
}
|
||||||
for {
|
bestJD := zeroEventInWindow(startTT, endTT, 2.0, 2.0, 30.0/86400.0, func(jd float64) float64 {
|
||||||
currentRate := raRate(jde, 1.0/86400.0)
|
return uranusRADerivativeN(jd, 1.0/86400.0, uranusEventSearchN)
|
||||||
if math.Abs(currentRate) > 0.55 {
|
}, func(jd float64) float64 {
|
||||||
jde += 2
|
return uranusRADerivative(jd, 0.5/86400.0)
|
||||||
continue
|
|
||||||
}
|
|
||||||
break
|
|
||||||
}
|
|
||||||
estimateJD := jde
|
|
||||||
for {
|
|
||||||
prevJD := estimateJD
|
|
||||||
rateValue := raRate(prevJD, 2.0/86400.0)
|
|
||||||
rateSlope := (raRate(prevJD+15.0/86400.0, 2.0/86400.0) - raRate(prevJD-15.0/86400.0, 2.0/86400.0)) / (30.0 / 86400.0)
|
|
||||||
estimateJD = prevJD - rateValue/rateSlope
|
|
||||||
if math.Abs(estimateJD-prevJD) <= 30.0/86400.0 {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
bestJD := eventZeroRefine(estimateJD, 15.0/86400.0, 0.5/86400.0, func(jd float64) float64 {
|
|
||||||
return raRate(jd, 0.5/86400.0)
|
|
||||||
})
|
})
|
||||||
return TD2UT(bestJD, false)
|
return TD2UT(bestJD, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
func NextUranusRetrogradeToPrograde(jde float64) float64 {
|
func NextUranusRetrogradeToPrograde(jde float64) float64 {
|
||||||
date := uranusRetrograde(jde, false)
|
lastOppositionJD := uranusConjunctionFull(jde, 180, 0)
|
||||||
if date < jde {
|
date := uranusRetrogradeAroundOpposition(lastOppositionJD, false)
|
||||||
oppositionJD := uranusConjunctionFull(jde, 180, 1)
|
if sameEventUTQueryTT(date, jde) || eventUTQueryAfterOrEqual(date, jde) {
|
||||||
return uranusRetrograde(oppositionJD+10, false)
|
|
||||||
}
|
|
||||||
return date
|
return date
|
||||||
}
|
}
|
||||||
|
nextOppositionJD := uranusConjunctionFull(jde, 180, 1)
|
||||||
|
return uranusRetrogradeAroundOpposition(nextOppositionJD, false)
|
||||||
|
}
|
||||||
|
|
||||||
func LastUranusRetrogradeToPrograde(jde float64) float64 {
|
func LastUranusRetrogradeToPrograde(jde float64) float64 {
|
||||||
jde = uranusConjunctionFull(jde, 180, 0) - 10
|
lastOppositionJD := uranusConjunctionFull(jde, 180, 0)
|
||||||
date := uranusRetrograde(jde, false)
|
date := uranusRetrogradeAroundOpposition(lastOppositionJD, false)
|
||||||
if date > jde {
|
if sameEventUTQueryTT(date, jde) || eventUTQueryBeforeOrEqual(date, jde) {
|
||||||
oppositionJD := uranusConjunctionFull(jde, 180, 0)
|
|
||||||
return uranusRetrograde(oppositionJD-10, false)
|
|
||||||
}
|
|
||||||
return date
|
return date
|
||||||
}
|
}
|
||||||
|
previousOppositionJD := uranusConjunctionFull(eventUTLastQueryTT(lastOppositionJD), 180, 0)
|
||||||
|
return uranusRetrogradeAroundOpposition(previousOppositionJD, false)
|
||||||
|
}
|
||||||
|
|
||||||
func NextUranusProgradeToRetrograde(jde float64) float64 {
|
func NextUranusProgradeToRetrograde(jde float64) float64 {
|
||||||
date := uranusRetrograde(jde, true)
|
nextOppositionJD := uranusConjunctionFull(jde, 180, 1)
|
||||||
if date < jde {
|
date := uranusRetrogradeAroundOpposition(nextOppositionJD, true)
|
||||||
oppositionJD := uranusConjunctionFull(jde, 180, 1)
|
if sameEventUTQueryTT(date, jde) || eventUTQueryAfterOrEqual(date, jde) {
|
||||||
return uranusRetrograde(oppositionJD+10, true)
|
|
||||||
}
|
|
||||||
return date
|
return date
|
||||||
}
|
}
|
||||||
|
followingOppositionJD := uranusConjunctionFull(eventUTNextQueryTT(nextOppositionJD), 180, 1)
|
||||||
|
return uranusRetrogradeAroundOpposition(followingOppositionJD, true)
|
||||||
|
}
|
||||||
|
|
||||||
func LastUranusProgradeToRetrograde(jde float64) float64 {
|
func LastUranusProgradeToRetrograde(jde float64) float64 {
|
||||||
jde = uranusConjunctionFull(jde, 180, 0) - 10
|
nextOppositionJD := uranusConjunctionFull(jde, 180, 1)
|
||||||
date := uranusRetrograde(jde, true)
|
date := uranusRetrogradeAroundOpposition(nextOppositionJD, true)
|
||||||
if date > jde {
|
if sameEventUTQueryTT(date, jde) || eventUTQueryBeforeOrEqual(date, jde) {
|
||||||
oppositionJD := uranusConjunctionFull(jde, 180, 0)
|
|
||||||
return uranusRetrograde(oppositionJD-10, true)
|
|
||||||
}
|
|
||||||
return date
|
return date
|
||||||
}
|
}
|
||||||
|
lastOppositionJD := uranusConjunctionFull(jde, 180, 0)
|
||||||
|
return uranusRetrogradeAroundOpposition(lastOppositionJD, true)
|
||||||
|
}
|
||||||
|
|||||||
+7
-38
@@ -87,53 +87,22 @@ func VenusApparentRaDec(jd float64) (float64, float64) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func EarthVenusAway(jd float64) float64 {
|
func EarthVenusAway(jd float64) float64 {
|
||||||
x, y, z := AVenusXYZ(jd)
|
return planetEarthAwayExplicitN(2, jd, -1)
|
||||||
to := math.Sqrt(x*x + y*y + z*z)
|
|
||||||
return to
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func VenusApparentLo(jd float64) float64 {
|
func VenusApparentLo(jd float64) float64 {
|
||||||
x, y, z := AVenusXYZ(jd)
|
geo, _ := planetApparentGeocentricPositionN(2, jd, -1)
|
||||||
to := 0.0057755183 * math.Sqrt(x*x+y*y+z*z)
|
return geo.lo
|
||||||
x, y, z = AVenusXYZ(jd - to)
|
|
||||||
lo := math.Atan2(y, x)
|
|
||||||
bo := math.Atan2(z, math.Sqrt(x*x+y*y))
|
|
||||||
lo = lo * 180 / math.Pi
|
|
||||||
bo = bo * 180 / math.Pi
|
|
||||||
lo = Limit360(lo)
|
|
||||||
//lo-=GXCLo(lo,bo,jd)/3600;
|
|
||||||
//bo+=GXCBo(lo,bo,jd);
|
|
||||||
lo += Nutation2000Bi(jd)
|
|
||||||
return lo
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func VenusApparentBo(jd float64) float64 {
|
func VenusApparentBo(jd float64) float64 {
|
||||||
x, y, z := AVenusXYZ(jd)
|
geo, _ := planetApparentGeocentricPositionN(2, jd, -1)
|
||||||
to := 0.0057755183 * math.Sqrt(x*x+y*y+z*z)
|
return geo.bo
|
||||||
x, y, z = AVenusXYZ(jd - to)
|
|
||||||
//lo := math.Atan2(y, x)
|
|
||||||
bo := math.Atan2(z, math.Sqrt(x*x+y*y))
|
|
||||||
//lo = lo * 180 / math.Pi
|
|
||||||
bo = bo * 180 / math.Pi
|
|
||||||
//lo+=GXCLo(lo,bo,jd);
|
|
||||||
//bo+=GXCBo(lo,bo,jd)/3600;
|
|
||||||
//lo+=Nutation2000Bi(jd);
|
|
||||||
return bo
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func VenusApparentLoBo(jd float64) (float64, float64) {
|
func VenusApparentLoBo(jd float64) (float64, float64) {
|
||||||
x, y, z := AVenusXYZ(jd)
|
geo, _ := planetApparentGeocentricPositionN(2, jd, -1)
|
||||||
to := 0.0057755183 * math.Sqrt(x*x+y*y+z*z)
|
return geo.lo, geo.bo
|
||||||
x, y, z = AVenusXYZ(jd - to)
|
|
||||||
lo := math.Atan2(y, x)
|
|
||||||
bo := math.Atan2(z, math.Sqrt(x*x+y*y))
|
|
||||||
lo = lo * 180 / math.Pi
|
|
||||||
bo = bo * 180 / math.Pi
|
|
||||||
lo = Limit360(lo)
|
|
||||||
//lo-=GXCLo(lo,bo,jd)/3600;
|
|
||||||
//bo+=GXCBo(lo,bo,jd);
|
|
||||||
lo += Nutation2000Bi(jd)
|
|
||||||
return lo, bo
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func VenusMag(jd float64) float64 {
|
func VenusMag(jd float64) float64 {
|
||||||
|
|||||||
+427
-156
@@ -3,6 +3,7 @@ package basic
|
|||||||
import (
|
import (
|
||||||
"math"
|
"math"
|
||||||
|
|
||||||
|
"b612.me/astro/planet"
|
||||||
. "b612.me/astro/tools"
|
. "b612.me/astro/tools"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -33,6 +34,23 @@ func venusSunLongitudeDeltaN(jde float64, n int) float64 {
|
|||||||
return sub
|
return sub
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func venusConjunctionAngleDelta(diff float64) float64 {
|
||||||
|
diff = Limit360(diff)
|
||||||
|
if diff > 180 {
|
||||||
|
diff -= 360
|
||||||
|
}
|
||||||
|
if diff < -180 {
|
||||||
|
diff += 360
|
||||||
|
}
|
||||||
|
return diff
|
||||||
|
}
|
||||||
|
|
||||||
|
func venusConjunctionHeliocentricDelta(jd, targetDeg float64, n int) float64 {
|
||||||
|
planetLo := planet.WherePlanetN(2, 0, jd, n)
|
||||||
|
earthLo := planet.WherePlanetN(-1, 0, jd, n)
|
||||||
|
return venusConjunctionAngleDelta(planetLo - earthLo - targetDeg)
|
||||||
|
}
|
||||||
|
|
||||||
func venusSunRADelta(jde float64) float64 {
|
func venusSunRADelta(jde float64) float64 {
|
||||||
sub := Limit360(VenusApparentRa(jde) - SunApparentRa(jde))
|
sub := Limit360(VenusApparentRa(jde) - SunApparentRa(jde))
|
||||||
if sub > 180 {
|
if sub > 180 {
|
||||||
@@ -66,13 +84,94 @@ func venusRADerivativeN(jde, val float64, n int) float64 {
|
|||||||
return sub / (2 * val)
|
return sub / (2 * val)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func venusRAContinuousForMax(jde float64) float64 {
|
||||||
|
ra := VenusApparentRa(jde)
|
||||||
|
if ra < 180 {
|
||||||
|
return ra + 360
|
||||||
|
}
|
||||||
|
return ra
|
||||||
|
}
|
||||||
|
|
||||||
|
func venusRAContinuousForMaxN(jde float64, n int) float64 {
|
||||||
|
ra := VenusApparentRaN(jde, n)
|
||||||
|
if ra < 180 {
|
||||||
|
return ra + 360
|
||||||
|
}
|
||||||
|
return ra
|
||||||
|
}
|
||||||
|
|
||||||
|
func venusRAContinuousForMin(jde float64) float64 {
|
||||||
|
ra := VenusApparentRa(jde)
|
||||||
|
if ra > 180 {
|
||||||
|
return ra - 360
|
||||||
|
}
|
||||||
|
return ra
|
||||||
|
}
|
||||||
|
|
||||||
|
func venusRAContinuousForMinN(jde float64, n int) float64 {
|
||||||
|
ra := VenusApparentRaN(jde, n)
|
||||||
|
if ra > 180 {
|
||||||
|
return ra - 360
|
||||||
|
}
|
||||||
|
return ra
|
||||||
|
}
|
||||||
|
|
||||||
|
func venusRAExtremumRefine(seed, start, end, step float64, fn func(float64) float64) float64 {
|
||||||
|
centerJD := clampFloat64(seed, start, end)
|
||||||
|
halfStep := step
|
||||||
|
bestJD := centerJD
|
||||||
|
bestVal := fn(centerJD)
|
||||||
|
for i := 0; i < 8; i++ {
|
||||||
|
leftJD := clampFloat64(centerJD-halfStep, start, end)
|
||||||
|
rightJD := clampFloat64(centerJD+halfStep, start, end)
|
||||||
|
leftVal := fn(leftJD)
|
||||||
|
centerVal := fn(centerJD)
|
||||||
|
rightVal := fn(rightJD)
|
||||||
|
if leftVal > bestVal {
|
||||||
|
bestVal = leftVal
|
||||||
|
bestJD = leftJD
|
||||||
|
}
|
||||||
|
if centerVal > bestVal {
|
||||||
|
bestVal = centerVal
|
||||||
|
bestJD = centerJD
|
||||||
|
}
|
||||||
|
if rightVal > bestVal {
|
||||||
|
bestVal = rightVal
|
||||||
|
bestJD = rightJD
|
||||||
|
}
|
||||||
|
denominator := leftVal - 2*centerVal + rightVal
|
||||||
|
if denominator == 0 {
|
||||||
|
centerJD = bestJD
|
||||||
|
halfStep /= 2
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
vertexJD := centerJD + 0.5*halfStep*(leftVal-rightVal)/denominator
|
||||||
|
vertexJD = clampFloat64(vertexJD, leftJD, rightJD)
|
||||||
|
vertexVal := fn(vertexJD)
|
||||||
|
if vertexVal > bestVal {
|
||||||
|
bestVal = vertexVal
|
||||||
|
bestJD = vertexJD
|
||||||
|
}
|
||||||
|
centerJD = bestJD
|
||||||
|
halfStep /= 2
|
||||||
|
}
|
||||||
|
return bestJD
|
||||||
|
}
|
||||||
|
|
||||||
func venusSunElongationN(jde float64, n int) float64 {
|
func venusSunElongationN(jde float64, n int) float64 {
|
||||||
lo1, bo1 := VenusApparentLoBoN(jde, n)
|
lo1, bo1 := VenusApparentLoBoN(jde, n)
|
||||||
lo2 := SunApparentLo(jde)
|
lo2 := HSunApparentLoN(jde, n)
|
||||||
bo2 := HSunTrueBoN(jde, n)
|
bo2 := HSunTrueBoN(jde, n)
|
||||||
return StarAngularSeparation(lo1, bo1, lo2, bo2)
|
return StarAngularSeparation(lo1, bo1, lo2, bo2)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func venusTrueElongationN(jde float64, n int) float64 {
|
||||||
|
earth := mercuryHelioN(-1, jde, n)
|
||||||
|
planetPos := mercuryHelioN(2, jde, n)
|
||||||
|
geo := mercuryGeocentric(planetPos, earth)
|
||||||
|
return StarAngularSeparation(geo.lo, geo.bo, HSunTrueLoN(jde, n), HSunTrueBoN(jde, n))
|
||||||
|
}
|
||||||
|
|
||||||
func venusElongationDerivative(jde, val float64) float64 {
|
func venusElongationDerivative(jde, val float64) float64 {
|
||||||
sub := VenusSunElongation(jde+val) - VenusSunElongation(jde-val)
|
sub := VenusSunElongation(jde+val) - VenusSunElongation(jde-val)
|
||||||
if sub > 180 {
|
if sub > 180 {
|
||||||
@@ -96,91 +195,98 @@ func venusElongationDerivativeN(jde, val float64, n int) float64 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func venusConjunction(jde float64, next uint8) float64 {
|
func venusConjunction(jde float64, next uint8) float64 {
|
||||||
//0=last 1=next
|
queryTT := jde
|
||||||
nowSub := venusSunLongitudeDeltaN(jde, venusEventSearchN)
|
direction := -1.0
|
||||||
pos := math.Abs(venusSunLongitudeDeltaN(jde+1/86400.0, venusEventSearchN)) - math.Abs(nowSub)
|
|
||||||
if pos >= 0 && next == 1 && nowSub > 0 {
|
|
||||||
jde += VENUS_S_PERIOD/8.0 + 2
|
|
||||||
}
|
|
||||||
if pos >= 0 && next == 1 && nowSub < 0 {
|
|
||||||
jde += VENUS_S_PERIOD/6.0 + 2
|
|
||||||
}
|
|
||||||
if pos <= 0 && next == 0 && nowSub < 0 {
|
|
||||||
jde -= VENUS_S_PERIOD/8.0 + 2
|
|
||||||
}
|
|
||||||
if pos <= 0 && next == 0 && nowSub > 0 {
|
|
||||||
jde -= VENUS_S_PERIOD/6.0 + 2
|
|
||||||
}
|
|
||||||
for {
|
|
||||||
nowSub := venusSunLongitudeDeltaN(jde, venusEventSearchN)
|
|
||||||
pos := math.Abs(venusSunLongitudeDeltaN(jde+1/86400.0, venusEventSearchN)) - math.Abs(nowSub)
|
|
||||||
if math.Abs(nowSub) > 24 || (pos > 0 && next == 1) || (pos < 0 && next == 0) {
|
|
||||||
if next == 1 {
|
if next == 1 {
|
||||||
jde += 8
|
direction = 1
|
||||||
} else {
|
|
||||||
jde -= 8
|
|
||||||
}
|
}
|
||||||
continue
|
left := queryTT
|
||||||
}
|
leftVal := venusSunLongitudeDeltaN(left, venusEventSearchN)
|
||||||
break
|
if math.Abs(leftVal) <= 30.0/86400.0 {
|
||||||
}
|
exact := eventZeroRefine(left, 1.0, 0.000005, venusSunLongitudeDelta)
|
||||||
JD1 := jde
|
if math.Abs(exact-queryTT) <= 1.0 {
|
||||||
for {
|
return TD2UT(exact, false)
|
||||||
JD0 := JD1
|
|
||||||
stDegree := venusSunLongitudeDelta(JD0)
|
|
||||||
stDegreep := (venusSunLongitudeDelta(JD0+0.000005) - venusSunLongitudeDelta(JD0-0.000005)) / 0.00001
|
|
||||||
JD1 = JD0 - stDegree/stDegreep
|
|
||||||
if math.Abs(JD1-JD0) <= 0.00001 {
|
|
||||||
break
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return TD2UT(JD1, false)
|
const step = 8.0
|
||||||
|
for i := 0; i < 80; i++ {
|
||||||
|
right := queryTT + direction*step*float64(i+1)
|
||||||
|
rightVal := venusSunLongitudeDeltaN(right, venusEventSearchN)
|
||||||
|
if leftVal == 0 || rightVal == 0 || leftVal*rightVal <= 0 {
|
||||||
|
center := (left + right) / 2.0
|
||||||
|
halfWindow := math.Abs(right-left) / 2.0
|
||||||
|
return TD2UT(eventZeroRefine(center, halfWindow, 0.000005, venusSunLongitudeDelta), false)
|
||||||
|
}
|
||||||
|
left = right
|
||||||
|
leftVal = rightVal
|
||||||
|
}
|
||||||
|
return TD2UT(eventZeroRefine(queryTT, VENUS_S_PERIOD, 0.000005, venusSunLongitudeDelta), false)
|
||||||
|
}
|
||||||
|
|
||||||
|
func venusConjunctionTypeAt(eventUT float64) bool {
|
||||||
|
return EarthVenusAway(eventUT) <= EarthAway(eventUT)
|
||||||
|
}
|
||||||
|
|
||||||
|
func nextVenusTypedConjunctionFromEvent(jde float64, inferior bool) float64 {
|
||||||
|
date := NextVenusConjunctionStrict(jde)
|
||||||
|
if venusConjunctionTypeAt(date) == inferior {
|
||||||
|
return date
|
||||||
|
}
|
||||||
|
return NextVenusConjunctionStrict(eventUTNextQueryTT(date))
|
||||||
|
}
|
||||||
|
|
||||||
|
func lastVenusTypedConjunctionFromEvent(jde float64, inferior bool) float64 {
|
||||||
|
date := LastVenusConjunctionStrict(jde)
|
||||||
|
if venusConjunctionTypeAt(date) == inferior {
|
||||||
|
return date
|
||||||
|
}
|
||||||
|
return LastVenusConjunctionStrict(eventUTLastQueryTT(date))
|
||||||
}
|
}
|
||||||
|
|
||||||
func LastVenusConjunction(jde float64) float64 {
|
func LastVenusConjunction(jde float64) float64 {
|
||||||
return venusConjunction(jde, 0)
|
return inclusiveLastSimpleEvent(jde, LastVenusConjunctionStrict, NextVenusConjunctionStrict)
|
||||||
}
|
}
|
||||||
|
|
||||||
func NextVenusConjunction(jde float64) float64 {
|
func NextVenusConjunction(jde float64) float64 {
|
||||||
|
return inclusiveNextSimpleEvent(jde, LastVenusConjunctionStrict, NextVenusConjunctionStrict)
|
||||||
|
}
|
||||||
|
|
||||||
|
func LastVenusConjunctionStrict(jde float64) float64 {
|
||||||
|
return venusConjunction(jde, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NextVenusConjunctionStrict(jde float64) float64 {
|
||||||
return venusConjunction(jde, 1)
|
return venusConjunction(jde, 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
func NextVenusInferiorConjunction(jde float64) float64 {
|
func nextVenusTypedConjunction(jde float64, inferior bool) float64 {
|
||||||
date := NextVenusConjunction(jde)
|
return nextVenusTypedConjunctionFromEvent(jde, inferior)
|
||||||
if EarthVenusAway(date) > EarthAway(date) {
|
|
||||||
return NextVenusConjunction(date + 2)
|
|
||||||
}
|
}
|
||||||
return date
|
|
||||||
|
func lastVenusTypedConjunction(jde float64, inferior bool) float64 {
|
||||||
|
return lastVenusTypedConjunctionFromEvent(jde, inferior)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NextVenusInferiorConjunction(jde float64) float64 {
|
||||||
|
return nextVenusTypedConjunction(jde, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
func NextVenusSuperiorConjunction(jde float64) float64 {
|
func NextVenusSuperiorConjunction(jde float64) float64 {
|
||||||
date := NextVenusConjunction(jde)
|
return nextVenusTypedConjunction(jde, false)
|
||||||
if EarthVenusAway(date) < EarthAway(date) {
|
|
||||||
return NextVenusConjunction(date + 2)
|
|
||||||
}
|
|
||||||
return date
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func LastVenusInferiorConjunction(jde float64) float64 {
|
func LastVenusInferiorConjunction(jde float64) float64 {
|
||||||
date := LastVenusConjunction(jde)
|
return lastVenusTypedConjunction(jde, true)
|
||||||
if EarthVenusAway(date) > EarthAway(date) {
|
|
||||||
return LastVenusConjunction(date - 2)
|
|
||||||
}
|
|
||||||
return date
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func LastVenusSuperiorConjunction(jde float64) float64 {
|
func LastVenusSuperiorConjunction(jde float64) float64 {
|
||||||
date := LastVenusConjunction(jde)
|
return lastVenusTypedConjunction(jde, false)
|
||||||
if EarthVenusAway(date) < EarthAway(date) {
|
|
||||||
return LastVenusConjunction(date - 2)
|
|
||||||
}
|
|
||||||
return date
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func venusRetrograde(jde float64) float64 {
|
func venusRetrograde(jde float64) float64 {
|
||||||
//0=last 1=next
|
//0=last 1=next
|
||||||
lastHe := LastVenusConjunction(jde)
|
lastHe := LastVenusConjunctionStrict(jde)
|
||||||
nextHe := NextVenusConjunction(jde)
|
nextHe := NextVenusConjunctionStrict(jde)
|
||||||
nowSub := venusSunRADelta(jde)
|
nowSub := venusSunRADelta(jde)
|
||||||
if nowSub > 0 {
|
if nowSub > 0 {
|
||||||
jde = lastHe + ((nextHe - lastHe) / 5.0 * 3.5)
|
jde = lastHe + ((nextHe - lastHe) / 5.0 * 3.5)
|
||||||
@@ -213,152 +319,317 @@ func venusRetrograde(jde float64) float64 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func NextVenusRetrograde(jde float64) float64 {
|
func NextVenusRetrograde(jde float64) float64 {
|
||||||
date := venusRetrograde(jde)
|
p2r := NextVenusProgradeToRetrograde(jde)
|
||||||
if date < jde {
|
r2p := NextVenusRetrogradeToPrograde(jde)
|
||||||
nextHe := NextVenusConjunction(jde)
|
if sameEventJD(p2r, r2p) {
|
||||||
return venusRetrograde(nextHe + 2)
|
return p2r
|
||||||
}
|
}
|
||||||
return date
|
if p2r < r2p {
|
||||||
|
return p2r
|
||||||
|
}
|
||||||
|
return r2p
|
||||||
}
|
}
|
||||||
|
|
||||||
func LastVenusRetrograde(jde float64) float64 {
|
func LastVenusRetrograde(jde float64) float64 {
|
||||||
lastHe := LastVenusConjunction(jde)
|
p2r := LastVenusProgradeToRetrograde(jde)
|
||||||
date := venusRetrograde(lastHe + 2)
|
r2p := LastVenusRetrogradeToPrograde(jde)
|
||||||
if date > jde {
|
if sameEventJD(p2r, r2p) {
|
||||||
lastLastHe := LastVenusConjunction(lastHe - 2)
|
return p2r
|
||||||
return venusRetrograde(lastLastHe + 2)
|
|
||||||
}
|
}
|
||||||
return date
|
if p2r > r2p {
|
||||||
|
return p2r
|
||||||
|
}
|
||||||
|
return r2p
|
||||||
|
}
|
||||||
|
|
||||||
|
func venusStationInWindow(start, end float64, progradeToRetrograde bool) float64 {
|
||||||
|
var best float64
|
||||||
|
if progradeToRetrograde {
|
||||||
|
guess := scanWindowForMax(start, end, 2.0, func(jd float64) float64 {
|
||||||
|
return venusRAContinuousForMaxN(jd, venusEventSearchN)
|
||||||
|
})
|
||||||
|
best = venusRAExtremumRefine(guess, start, end, 1.0, func(jd float64) float64 {
|
||||||
|
return venusRAContinuousForMax(jd)
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
guess := scanWindowForMax(start, end, 2.0, func(jd float64) float64 {
|
||||||
|
return -venusRAContinuousForMinN(jd, venusEventSearchN)
|
||||||
|
})
|
||||||
|
best = venusRAExtremumRefine(guess, start, end, 1.0, func(jd float64) float64 {
|
||||||
|
return -venusRAContinuousForMin(jd)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return TD2UT(best, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
func venusProgradeToRetrogradeAroundInferior(inferior float64) float64 {
|
||||||
|
return venusStationInWindow(inferior-30.0, inferior-14.0, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
func venusRetrogradeToProgradeAroundInferior(inferior float64) float64 {
|
||||||
|
return venusStationInWindow(inferior+14.0, inferior+24.0, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
func NextVenusProgradeToRetrograde(jde float64) float64 {
|
func NextVenusProgradeToRetrograde(jde float64) float64 {
|
||||||
date := NextVenusRetrograde(jde)
|
inferior := NextVenusInferiorConjunction(jde)
|
||||||
sub := Limit360(VenusApparentRa(date) - SunApparentRa(date))
|
for {
|
||||||
if sub > 180 {
|
date := venusProgradeToRetrogradeAroundInferior(inferior)
|
||||||
return NextVenusRetrograde(date + VENUS_S_PERIOD/2)
|
if eventUTQueryAfterOrEqual(date, jde) {
|
||||||
}
|
|
||||||
return date
|
return date
|
||||||
}
|
}
|
||||||
|
inferior = NextVenusInferiorConjunction(eventUTNextQueryTT(inferior))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func NextVenusRetrogradeToPrograde(jde float64) float64 {
|
func NextVenusRetrogradeToPrograde(jde float64) float64 {
|
||||||
date := NextVenusRetrograde(jde)
|
inferior := LastVenusInferiorConjunction(jde)
|
||||||
sub := Limit360(VenusApparentRa(date) - SunApparentRa(date))
|
for {
|
||||||
if sub < 180 {
|
date := venusRetrogradeToProgradeAroundInferior(inferior)
|
||||||
return NextVenusRetrograde(date + 12)
|
if eventUTQueryAfterOrEqual(date, jde) {
|
||||||
}
|
|
||||||
return date
|
return date
|
||||||
}
|
}
|
||||||
|
inferior = NextVenusInferiorConjunction(eventUTNextQueryTT(inferior))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func LastVenusProgradeToRetrograde(jde float64) float64 {
|
func LastVenusProgradeToRetrograde(jde float64) float64 {
|
||||||
date := LastVenusRetrograde(jde)
|
inferior := NextVenusInferiorConjunction(jde)
|
||||||
sub := Limit360(VenusApparentRa(date) - SunApparentRa(date))
|
for {
|
||||||
if sub > 180 {
|
date := venusProgradeToRetrogradeAroundInferior(inferior)
|
||||||
return LastVenusRetrograde(date - 12)
|
if eventUTQueryBeforeOrEqual(date, jde) {
|
||||||
}
|
|
||||||
return date
|
return date
|
||||||
}
|
}
|
||||||
|
inferior = LastVenusInferiorConjunction(eventUTLastQueryTT(inferior))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func LastVenusRetrogradeToPrograde(jde float64) float64 {
|
func LastVenusRetrogradeToPrograde(jde float64) float64 {
|
||||||
date := LastVenusRetrograde(jde)
|
inferior := LastVenusInferiorConjunction(jde)
|
||||||
sub := Limit360(VenusApparentRa(date) - SunApparentRa(date))
|
for {
|
||||||
if sub < 180 {
|
date := venusRetrogradeToProgradeAroundInferior(inferior)
|
||||||
return LastVenusRetrograde(date - VENUS_S_PERIOD/2)
|
if eventUTQueryBeforeOrEqual(date, jde) {
|
||||||
}
|
|
||||||
return date
|
return date
|
||||||
}
|
}
|
||||||
|
inferior = LastVenusInferiorConjunction(eventUTLastQueryTT(inferior))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func VenusSunElongation(jde float64) float64 {
|
func VenusSunElongation(jde float64) float64 {
|
||||||
lo1, bo1 := VenusApparentLoBo(jde)
|
lo1, bo1 := VenusApparentLoBo(jde)
|
||||||
lo2 := SunApparentLo(jde)
|
lo2 := HSunApparentLo(jde)
|
||||||
bo2 := HSunTrueBo(jde)
|
bo2 := HSunTrueBo(jde)
|
||||||
return StarAngularSeparation(lo1, bo1, lo2, bo2)
|
return StarAngularSeparation(lo1, bo1, lo2, bo2)
|
||||||
}
|
}
|
||||||
|
|
||||||
func venusGreatestElongation(jde float64) float64 {
|
func venusGreatestElongationInWindow(start, end float64) float64 {
|
||||||
lastHe := LastVenusConjunction(jde)
|
best := maximizeInWindow(start, end, 5.0, func(jd float64) float64 {
|
||||||
nextHe := NextVenusConjunction(jde)
|
return venusTrueElongationN(jd, venusEventSearchN)
|
||||||
nowSub := venusSunRADelta(jde)
|
}, func(jd float64) float64 {
|
||||||
if nowSub > 0 {
|
return venusTrueElongationN(jd, -1)
|
||||||
jde = lastHe + ((nextHe - lastHe) / 5.0 * 2.5)
|
|
||||||
} else {
|
|
||||||
jde = lastHe + ((nextHe - lastHe) / 5.0)
|
|
||||||
}
|
|
||||||
for {
|
|
||||||
nowSub := venusElongationDerivativeN(jde, 1.0/86400.0, venusEventSearchN)
|
|
||||||
if math.Abs(nowSub) > 0.15 {
|
|
||||||
jde += 5
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
break
|
|
||||||
}
|
|
||||||
JD1 := jde
|
|
||||||
for {
|
|
||||||
JD0 := JD1
|
|
||||||
stDegree := venusElongationDerivative(JD0, 2.0/86400.0)
|
|
||||||
stDegreep := (venusElongationDerivative(JD0+15.0/86400.0, 2.0/86400.0) - venusElongationDerivative(JD0-15.0/86400.0, 2.0/86400.0)) / (30.0 / 86400.0)
|
|
||||||
JD1 = JD0 - stDegree/stDegreep
|
|
||||||
if math.Abs(JD1-JD0) <= 30.0/86400.0 {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
min := eventZeroRefine(JD1, 15.0/86400.0, 0.5/86400.0, func(jd float64) float64 {
|
|
||||||
return venusElongationDerivative(jd, 0.5/86400.0)
|
|
||||||
})
|
})
|
||||||
//fmt.Println((min - lastHe) / (nextHe - lastHe))
|
return TD2UT(best, false)
|
||||||
return TD2UT(min, false)
|
}
|
||||||
|
|
||||||
|
func venusEastElongationWindowEndingAt(inferior float64) (float64, float64) {
|
||||||
|
lastSuperior := LastVenusSuperiorConjunction(eventUTLastQueryTT(inferior))
|
||||||
|
return lastSuperior + innerEventEpsilon, inferior - innerEventEpsilon
|
||||||
|
}
|
||||||
|
|
||||||
|
func venusWestElongationWindowEndingAt(superior float64) (float64, float64) {
|
||||||
|
lastInferior := LastVenusInferiorConjunction(eventUTLastQueryTT(superior))
|
||||||
|
return lastInferior + innerEventEpsilon, superior - innerEventEpsilon
|
||||||
|
}
|
||||||
|
|
||||||
|
func venusEastElongationWindowContaining(jde float64) (float64, float64) {
|
||||||
|
nextInferior := NextVenusInferiorConjunction(jde)
|
||||||
|
start, end := venusEastElongationWindowEndingAt(nextInferior)
|
||||||
|
if eventUTQueryBeforeOrEqual(start, jde) && eventUTQueryAfterOrEqual(end, jde) {
|
||||||
|
return start, end
|
||||||
|
}
|
||||||
|
currentInferior := LastVenusInferiorConjunction(jde)
|
||||||
|
return venusEastElongationWindowEndingAt(currentInferior)
|
||||||
|
}
|
||||||
|
|
||||||
|
func venusWestElongationWindowContaining(jde float64) (float64, float64) {
|
||||||
|
nextSuperior := NextVenusSuperiorConjunction(jde)
|
||||||
|
start, end := venusWestElongationWindowEndingAt(nextSuperior)
|
||||||
|
if eventUTQueryBeforeOrEqual(start, jde) && eventUTQueryAfterOrEqual(end, jde) {
|
||||||
|
return start, end
|
||||||
|
}
|
||||||
|
currentSuperior := LastVenusSuperiorConjunction(jde)
|
||||||
|
return venusWestElongationWindowEndingAt(currentSuperior)
|
||||||
|
}
|
||||||
|
|
||||||
|
func nextVenusGreatestElongationTyped(jde float64, east bool) float64 {
|
||||||
|
if east {
|
||||||
|
start, windowEnd := venusEastElongationWindowContaining(jde)
|
||||||
|
for {
|
||||||
|
date := venusGreatestElongationInWindow(start, windowEnd)
|
||||||
|
if eventUTQueryAfterOrEqual(date, jde) {
|
||||||
|
return date
|
||||||
|
}
|
||||||
|
nextInferior := NextVenusInferiorConjunction(eventUTNextQueryTT(windowEnd))
|
||||||
|
start, windowEnd = venusEastElongationWindowEndingAt(nextInferior)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
start, windowEnd := venusWestElongationWindowContaining(jde)
|
||||||
|
for {
|
||||||
|
date := venusGreatestElongationInWindow(start, windowEnd)
|
||||||
|
if eventUTQueryAfterOrEqual(date, jde) {
|
||||||
|
return date
|
||||||
|
}
|
||||||
|
nextSuperior := NextVenusSuperiorConjunction(eventUTNextQueryTT(windowEnd))
|
||||||
|
start, windowEnd = venusWestElongationWindowEndingAt(nextSuperior)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func lastVenusGreatestElongationTyped(jde float64, east bool) float64 {
|
||||||
|
if east {
|
||||||
|
start, windowEnd := venusEastElongationWindowContaining(jde)
|
||||||
|
for {
|
||||||
|
date := venusGreatestElongationInWindow(start, windowEnd)
|
||||||
|
if eventUTQueryBeforeOrEqual(date, jde) {
|
||||||
|
return date
|
||||||
|
}
|
||||||
|
prevInferior := LastVenusInferiorConjunction(eventUTLastQueryTT(start))
|
||||||
|
start, windowEnd = venusEastElongationWindowEndingAt(prevInferior)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
start, windowEnd := venusWestElongationWindowContaining(jde)
|
||||||
|
for {
|
||||||
|
date := venusGreatestElongationInWindow(start, windowEnd)
|
||||||
|
if eventUTQueryBeforeOrEqual(date, jde) {
|
||||||
|
return date
|
||||||
|
}
|
||||||
|
prevSuperior := LastVenusSuperiorConjunction(eventUTLastQueryTT(start))
|
||||||
|
start, windowEnd = venusWestElongationWindowEndingAt(prevSuperior)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func venusGreatestElongation(jde float64) float64 {
|
||||||
|
east := venusSunRADelta(jde) > 0
|
||||||
|
if east {
|
||||||
|
return nextVenusGreatestElongationTyped(jde, true)
|
||||||
|
}
|
||||||
|
return nextVenusGreatestElongationTyped(jde, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
func NextVenusGreatestElongation(jde float64) float64 {
|
func NextVenusGreatestElongation(jde float64) float64 {
|
||||||
date := venusGreatestElongation(jde)
|
east := NextVenusGreatestElongationEast(jde)
|
||||||
if date < jde {
|
west := NextVenusGreatestElongationWest(jde)
|
||||||
nextHe := NextVenusConjunction(jde)
|
if sameEventJD(east, west) {
|
||||||
return venusGreatestElongation(nextHe + 2)
|
return east
|
||||||
}
|
}
|
||||||
return date
|
if east < west {
|
||||||
|
return east
|
||||||
|
}
|
||||||
|
return west
|
||||||
}
|
}
|
||||||
|
|
||||||
func LastVenusGreatestElongation(jde float64) float64 {
|
func LastVenusGreatestElongation(jde float64) float64 {
|
||||||
lastHe := LastVenusConjunction(jde)
|
east := LastVenusGreatestElongationEast(jde)
|
||||||
date := venusGreatestElongation(lastHe + 2)
|
west := LastVenusGreatestElongationWest(jde)
|
||||||
if date > jde {
|
if sameEventJD(east, west) {
|
||||||
lastLastHe := LastVenusConjunction(lastHe - 2)
|
return east
|
||||||
return venusGreatestElongation(lastLastHe + 2)
|
|
||||||
}
|
}
|
||||||
|
if east > west {
|
||||||
|
return east
|
||||||
|
}
|
||||||
|
return west
|
||||||
|
}
|
||||||
|
|
||||||
|
func LastVenusInferiorConjunctionInclusive(jde float64) float64 {
|
||||||
|
date := LastVenusConjunction(jde)
|
||||||
|
if venusConjunctionTypeAt(date) {
|
||||||
return date
|
return date
|
||||||
}
|
}
|
||||||
|
return LastVenusConjunction(eventUTLastQueryTT(date))
|
||||||
|
}
|
||||||
|
|
||||||
|
func NextVenusInferiorConjunctionInclusive(jde float64) float64 {
|
||||||
|
date := NextVenusConjunction(jde)
|
||||||
|
if venusConjunctionTypeAt(date) {
|
||||||
|
return date
|
||||||
|
}
|
||||||
|
return NextVenusConjunction(eventUTNextQueryTT(date))
|
||||||
|
}
|
||||||
|
|
||||||
|
func LastVenusSuperiorConjunctionInclusive(jde float64) float64 {
|
||||||
|
date := LastVenusConjunction(jde)
|
||||||
|
if !venusConjunctionTypeAt(date) {
|
||||||
|
return date
|
||||||
|
}
|
||||||
|
return LastVenusConjunction(eventUTLastQueryTT(date))
|
||||||
|
}
|
||||||
|
|
||||||
|
func NextVenusSuperiorConjunctionInclusive(jde float64) float64 {
|
||||||
|
date := NextVenusConjunction(jde)
|
||||||
|
if !venusConjunctionTypeAt(date) {
|
||||||
|
return date
|
||||||
|
}
|
||||||
|
return NextVenusConjunction(eventUTNextQueryTT(date))
|
||||||
|
}
|
||||||
|
|
||||||
|
func LastVenusRetrogradeInclusive(jde float64) float64 {
|
||||||
|
return inclusiveLastSimpleEvent(jde, LastVenusRetrograde, NextVenusRetrograde)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NextVenusRetrogradeInclusive(jde float64) float64 {
|
||||||
|
return inclusiveNextSimpleEvent(jde, LastVenusRetrograde, NextVenusRetrograde)
|
||||||
|
}
|
||||||
|
|
||||||
|
func LastVenusProgradeToRetrogradeInclusive(jde float64) float64 {
|
||||||
|
return inclusiveLastSimpleEvent(jde, LastVenusProgradeToRetrograde, NextVenusProgradeToRetrograde)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NextVenusProgradeToRetrogradeInclusive(jde float64) float64 {
|
||||||
|
return inclusiveNextSimpleEvent(jde, LastVenusProgradeToRetrograde, NextVenusProgradeToRetrograde)
|
||||||
|
}
|
||||||
|
|
||||||
|
func LastVenusRetrogradeToProgradeInclusive(jde float64) float64 {
|
||||||
|
return inclusiveLastSimpleEvent(jde, LastVenusRetrogradeToPrograde, NextVenusRetrogradeToPrograde)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NextVenusRetrogradeToProgradeInclusive(jde float64) float64 {
|
||||||
|
return inclusiveNextSimpleEvent(jde, LastVenusRetrogradeToPrograde, NextVenusRetrogradeToPrograde)
|
||||||
|
}
|
||||||
|
|
||||||
|
func LastVenusGreatestElongationInclusive(jde float64) float64 {
|
||||||
|
return inclusiveLastSimpleEvent(jde, LastVenusGreatestElongation, NextVenusGreatestElongation)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NextVenusGreatestElongationInclusive(jde float64) float64 {
|
||||||
|
return inclusiveNextSimpleEvent(jde, LastVenusGreatestElongation, NextVenusGreatestElongation)
|
||||||
|
}
|
||||||
|
|
||||||
|
func LastVenusGreatestElongationEastInclusive(jde float64) float64 {
|
||||||
|
return inclusiveLastSimpleEvent(jde, LastVenusGreatestElongationEast, NextVenusGreatestElongationEast)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NextVenusGreatestElongationEastInclusive(jde float64) float64 {
|
||||||
|
return inclusiveNextSimpleEvent(jde, LastVenusGreatestElongationEast, NextVenusGreatestElongationEast)
|
||||||
|
}
|
||||||
|
|
||||||
|
func LastVenusGreatestElongationWestInclusive(jde float64) float64 {
|
||||||
|
return inclusiveLastSimpleEvent(jde, LastVenusGreatestElongationWest, NextVenusGreatestElongationWest)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NextVenusGreatestElongationWestInclusive(jde float64) float64 {
|
||||||
|
return inclusiveNextSimpleEvent(jde, LastVenusGreatestElongationWest, NextVenusGreatestElongationWest)
|
||||||
|
}
|
||||||
|
|
||||||
func NextVenusGreatestElongationEast(jde float64) float64 {
|
func NextVenusGreatestElongationEast(jde float64) float64 {
|
||||||
date := NextVenusGreatestElongation(jde)
|
return nextVenusGreatestElongationTyped(jde, true)
|
||||||
sub := Limit360(VenusApparentRa(date) - SunApparentRa(date))
|
|
||||||
if sub > 180 {
|
|
||||||
return NextVenusGreatestElongation(date + 1)
|
|
||||||
}
|
|
||||||
return date
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NextVenusGreatestElongationWest(jde float64) float64 {
|
func NextVenusGreatestElongationWest(jde float64) float64 {
|
||||||
date := NextVenusGreatestElongation(jde)
|
return nextVenusGreatestElongationTyped(jde, false)
|
||||||
sub := Limit360(VenusApparentRa(date) - SunApparentRa(date))
|
|
||||||
if sub < 180 {
|
|
||||||
return NextVenusGreatestElongation(date + 1)
|
|
||||||
}
|
|
||||||
return date
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func LastVenusGreatestElongationEast(jde float64) float64 {
|
func LastVenusGreatestElongationEast(jde float64) float64 {
|
||||||
date := LastVenusGreatestElongation(jde)
|
return lastVenusGreatestElongationTyped(jde, true)
|
||||||
sub := Limit360(VenusApparentRa(date) - SunApparentRa(date))
|
|
||||||
if sub > 180 {
|
|
||||||
return LastVenusGreatestElongation(date - 1)
|
|
||||||
}
|
|
||||||
return date
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func LastVenusGreatestElongationWest(jde float64) float64 {
|
func LastVenusGreatestElongationWest(jde float64) float64 {
|
||||||
date := LastVenusGreatestElongation(jde)
|
return lastVenusGreatestElongationTyped(jde, false)
|
||||||
sub := Limit360(VenusApparentRa(date) - SunApparentRa(date))
|
|
||||||
if sub < 180 {
|
|
||||||
return LastVenusGreatestElongation(date - 1)
|
|
||||||
}
|
|
||||||
return date
|
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -279,7 +279,7 @@ func searchLunarEclipse(
|
|||||||
return lunarEclipseInfoFromBasic(result, date.Location()), true
|
return lunarEclipseInfoFromBasic(result, date.Location()), true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
candidateTT = basic.CalcMoonSHByJDE(candidateTT+float64(direction)*lunarEclipseSynodicMonthDays, 1)
|
candidateTT = nextEclipseSearchCandidateTT(candidateTT, 1, direction, lunarEclipseSynodicMonthDays)
|
||||||
}
|
}
|
||||||
|
|
||||||
return LunarEclipseInfo{}, false
|
return LunarEclipseInfo{}, false
|
||||||
|
|||||||
+72
-6
@@ -147,6 +147,12 @@ func LastLocalLunarEclipse(date time.Time, lon, lat, height float64) LocalLunarE
|
|||||||
return LastLocalLunarEclipseDanjon(date, lon, lat, height)
|
return LastLocalLunarEclipseDanjon(date, lon, lat, height)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// LastLocalTotalLunarEclipse 上次可见月全食 / previous visible local total lunar eclipse.
|
||||||
|
// Previous visible local total lunar eclipse, using Danjon by default.
|
||||||
|
func LastLocalTotalLunarEclipse(date time.Time, lon, lat, height float64) (LocalLunarEclipseInfo, bool) {
|
||||||
|
return searchLocalTotalLunarEclipse(date, lon, lat, height, -1, true, basic.LunarEclipseDanjon, localLunarEclipseQueryVisible)
|
||||||
|
}
|
||||||
|
|
||||||
// LastLocalLunarEclipseDanjon 上次可见月食(Danjon) / previous visible local lunar eclipse with Danjon model.
|
// LastLocalLunarEclipseDanjon 上次可见月食(Danjon) / previous visible local lunar eclipse with Danjon model.
|
||||||
// Previous visible local lunar eclipse with the Danjon model.
|
// Previous visible local lunar eclipse with the Danjon model.
|
||||||
func LastLocalLunarEclipseDanjon(date time.Time, lon, lat, height float64) LocalLunarEclipseInfo {
|
func LastLocalLunarEclipseDanjon(date time.Time, lon, lat, height float64) LocalLunarEclipseInfo {
|
||||||
@@ -187,6 +193,12 @@ func NextLocalLunarEclipse(date time.Time, lon, lat, height float64) LocalLunarE
|
|||||||
return NextLocalLunarEclipseDanjon(date, lon, lat, height)
|
return NextLocalLunarEclipseDanjon(date, lon, lat, height)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NextLocalTotalLunarEclipse 下次可见月全食 / next visible local total lunar eclipse.
|
||||||
|
// Next visible local total lunar eclipse, using Danjon by default.
|
||||||
|
func NextLocalTotalLunarEclipse(date time.Time, lon, lat, height float64) (LocalLunarEclipseInfo, bool) {
|
||||||
|
return searchLocalTotalLunarEclipse(date, lon, lat, height, 1, false, basic.LunarEclipseDanjon, localLunarEclipseQueryVisible)
|
||||||
|
}
|
||||||
|
|
||||||
// NextLocalLunarEclipseDanjon 下次可见月食(Danjon) / next visible local lunar eclipse with Danjon model.
|
// NextLocalLunarEclipseDanjon 下次可见月食(Danjon) / next visible local lunar eclipse with Danjon model.
|
||||||
// Next visible local lunar eclipse with the Danjon model.
|
// Next visible local lunar eclipse with the Danjon model.
|
||||||
func NextLocalLunarEclipseDanjon(date time.Time, lon, lat, height float64) LocalLunarEclipseInfo {
|
func NextLocalLunarEclipseDanjon(date time.Time, lon, lat, height float64) LocalLunarEclipseInfo {
|
||||||
@@ -227,6 +239,14 @@ func ClosestLocalLunarEclipse(date time.Time, lon, lat, height float64) LocalLun
|
|||||||
return ClosestLocalLunarEclipseDanjon(date, lon, lat, height)
|
return ClosestLocalLunarEclipseDanjon(date, lon, lat, height)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ClosestLocalTotalLunarEclipse 最近一次可见月全食 / closest visible local total lunar eclipse.
|
||||||
|
// Closest visible local total lunar eclipse, using Danjon by default.
|
||||||
|
func ClosestLocalTotalLunarEclipse(date time.Time, lon, lat, height float64) (LocalLunarEclipseInfo, bool) {
|
||||||
|
last, hasLast := searchLocalTotalLunarEclipse(date, lon, lat, height, -1, true, basic.LunarEclipseDanjon, localLunarEclipseQueryVisible)
|
||||||
|
next, hasNext := searchLocalTotalLunarEclipse(date, lon, lat, height, 1, false, basic.LunarEclipseDanjon, localLunarEclipseQueryVisible)
|
||||||
|
return closestLocalLunarEclipseResult(date, last, hasLast, next, hasNext)
|
||||||
|
}
|
||||||
|
|
||||||
// ClosestLocalLunarEclipseDanjon 最近一次可见月食(Danjon) / closest visible local lunar eclipse with Danjon model.
|
// ClosestLocalLunarEclipseDanjon 最近一次可见月食(Danjon) / closest visible local lunar eclipse with Danjon model.
|
||||||
// Closest visible local lunar eclipse with the Danjon model.
|
// Closest visible local lunar eclipse with the Danjon model.
|
||||||
func ClosestLocalLunarEclipseDanjon(date time.Time, lon, lat, height float64) LocalLunarEclipseInfo {
|
func ClosestLocalLunarEclipseDanjon(date time.Time, lon, lat, height float64) LocalLunarEclipseInfo {
|
||||||
@@ -272,21 +292,32 @@ func closestLocalLunarEclipse(
|
|||||||
next LocalLunarEclipseInfo,
|
next LocalLunarEclipseInfo,
|
||||||
hasNext bool,
|
hasNext bool,
|
||||||
) LocalLunarEclipseInfo {
|
) LocalLunarEclipseInfo {
|
||||||
|
info, _ := closestLocalLunarEclipseResult(date, last, hasLast, next, hasNext)
|
||||||
|
return info
|
||||||
|
}
|
||||||
|
|
||||||
|
func closestLocalLunarEclipseResult(
|
||||||
|
date time.Time,
|
||||||
|
last LocalLunarEclipseInfo,
|
||||||
|
hasLast bool,
|
||||||
|
next LocalLunarEclipseInfo,
|
||||||
|
hasNext bool,
|
||||||
|
) (LocalLunarEclipseInfo, bool) {
|
||||||
switch {
|
switch {
|
||||||
case hasLast && !hasNext:
|
case hasLast && !hasNext:
|
||||||
return last
|
return last, true
|
||||||
case !hasLast && hasNext:
|
case !hasLast && hasNext:
|
||||||
return next
|
return next, true
|
||||||
case !hasLast && !hasNext:
|
case !hasLast && !hasNext:
|
||||||
return LocalLunarEclipseInfo{}
|
return LocalLunarEclipseInfo{}, false
|
||||||
}
|
}
|
||||||
|
|
||||||
lastDistance := math.Abs(date.Sub(last.Maximum).Seconds())
|
lastDistance := math.Abs(date.Sub(last.Maximum).Seconds())
|
||||||
nextDistance := math.Abs(next.Maximum.Sub(date).Seconds())
|
nextDistance := math.Abs(next.Maximum.Sub(date).Seconds())
|
||||||
if lastDistance <= nextDistance {
|
if lastDistance <= nextDistance {
|
||||||
return last
|
return last, true
|
||||||
}
|
}
|
||||||
return next
|
return next, true
|
||||||
}
|
}
|
||||||
|
|
||||||
func searchLocalLunarEclipse(
|
func searchLocalLunarEclipse(
|
||||||
@@ -311,7 +342,35 @@ func searchLocalLunarEclipse(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
candidateTT = basic.CalcMoonSHByJDE(candidateTT+float64(direction)*lunarEclipseSynodicMonthDays, 1)
|
candidateTT = nextEclipseSearchCandidateTT(candidateTT, 1, direction, lunarEclipseSynodicMonthDays)
|
||||||
|
}
|
||||||
|
|
||||||
|
return LocalLunarEclipseInfo{}, false
|
||||||
|
}
|
||||||
|
|
||||||
|
func searchLocalTotalLunarEclipse(
|
||||||
|
date time.Time,
|
||||||
|
lon, lat, height float64,
|
||||||
|
direction int,
|
||||||
|
includeCurrent bool,
|
||||||
|
calculator lunarEclipseCalculator,
|
||||||
|
mode localLunarEclipseQueryMode,
|
||||||
|
) (LocalLunarEclipseInfo, bool) {
|
||||||
|
targetTT := timeToTTJDE(date)
|
||||||
|
candidateTT := basic.CalcMoonSHByJDE(targetTT, 1)
|
||||||
|
|
||||||
|
for i := 0; i < localLunarEclipseSearchLimit; i++ {
|
||||||
|
if isPotentialLunarEclipse(candidateTT) {
|
||||||
|
result := calculator(candidateTT)
|
||||||
|
if result.HasTotal {
|
||||||
|
info := localLunarEclipseInfoFromBasic(result, lon, lat, height, date.Location())
|
||||||
|
if (mode != localLunarEclipseQueryVisible || localTotalLunarEclipseVisible(info)) &&
|
||||||
|
lunarEclipseMatchesDirection(result.Maximum, targetTT, direction, includeCurrent) {
|
||||||
|
return info, true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
candidateTT = nextEclipseSearchCandidateTT(candidateTT, 1, direction, lunarEclipseSynodicMonthDays)
|
||||||
}
|
}
|
||||||
|
|
||||||
return LocalLunarEclipseInfo{}, false
|
return LocalLunarEclipseInfo{}, false
|
||||||
@@ -375,6 +434,13 @@ func localLunarEclipseVisible(info LocalLunarEclipseInfo) bool {
|
|||||||
return localLunarEclipseVisibleDuring(info, eventStart, eventEnd)
|
return localLunarEclipseVisibleDuring(info, eventStart, eventEnd)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func localTotalLunarEclipseVisible(info LocalLunarEclipseInfo) bool {
|
||||||
|
if !info.HasTotal || info.TotalStart.IsZero() || info.TotalEnd.IsZero() {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return localLunarEclipseVisibleDuring(info, info.TotalStart, info.TotalEnd)
|
||||||
|
}
|
||||||
|
|
||||||
func localLunarEclipseVisibleOnDate(info LocalLunarEclipseInfo, dayStart, dayEnd time.Time) bool {
|
func localLunarEclipseVisibleOnDate(info LocalLunarEclipseInfo, dayStart, dayEnd time.Time) bool {
|
||||||
eventStart, eventEnd, ok := localLunarEclipseRange(info)
|
eventStart, eventEnd, ok := localLunarEclipseRange(info)
|
||||||
if !ok {
|
if !ok {
|
||||||
|
|||||||
@@ -133,6 +133,61 @@ func TestLocalLunarEclipseSearchBeyondFiveYears(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestLocalTotalLunarEclipseSearch(t *testing.T) {
|
||||||
|
loc := time.FixedZone("CDT", -5*3600)
|
||||||
|
lon, lat, height := -87.65, 41.85, 0.0
|
||||||
|
date := time.Date(2025, 3, 13, 0, 0, 0, 0, loc)
|
||||||
|
|
||||||
|
next, ok := NextLocalTotalLunarEclipse(date, lon, lat, height)
|
||||||
|
if !ok {
|
||||||
|
t.Fatal("expected to find next local total lunar eclipse")
|
||||||
|
}
|
||||||
|
if next.Type != LunarEclipseTotal || !next.HasTotal {
|
||||||
|
t.Fatalf("unexpected next total lunar eclipse: %+v", next)
|
||||||
|
}
|
||||||
|
assertTimeClose(t, "NextLocalTotalLunarEclipse", next.Maximum, time.Date(2025, 3, 14, 1, 58, 47, 0, loc), 2*time.Minute)
|
||||||
|
|
||||||
|
last, ok := LastLocalTotalLunarEclipse(next.Maximum, lon, lat, height)
|
||||||
|
if !ok {
|
||||||
|
t.Fatal("expected to find previous local total lunar eclipse")
|
||||||
|
}
|
||||||
|
if last.Type != LunarEclipseTotal || !last.HasTotal {
|
||||||
|
t.Fatalf("unexpected last total lunar eclipse: %+v", last)
|
||||||
|
}
|
||||||
|
assertTimeClose(t, "LastLocalTotalLunarEclipse", last.Maximum, next.Maximum, time.Second)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLocalTotalLunarEclipseClosest(t *testing.T) {
|
||||||
|
loc := time.FixedZone("CDT", -5*3600)
|
||||||
|
lon, lat, height := -87.65, 41.85, 0.0
|
||||||
|
date := time.Date(2025, 3, 14, 0, 0, 0, 0, loc)
|
||||||
|
|
||||||
|
info, ok := ClosestLocalTotalLunarEclipse(date, lon, lat, height)
|
||||||
|
if !ok {
|
||||||
|
t.Fatal("expected to find closest local total lunar eclipse")
|
||||||
|
}
|
||||||
|
if info.Type != LunarEclipseTotal || !info.HasTotal {
|
||||||
|
t.Fatalf("unexpected closest total lunar eclipse: %+v", info)
|
||||||
|
}
|
||||||
|
assertTimeClose(t, "ClosestLocalTotalLunarEclipse", info.Maximum, time.Date(2025, 3, 14, 1, 58, 47, 0, loc), 2*time.Minute)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLocalTotalLunarEclipseVisibleRequiresTotalPhaseVisibility(t *testing.T) {
|
||||||
|
info, ok := LocalLunarEclipseOnDate(time.Date(2025, 3, 14, 12, 0, 0, 0, time.UTC), -0.1278, 51.5074, 0)
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("expected visible local eclipse in London")
|
||||||
|
}
|
||||||
|
if info.Type != LunarEclipseTotal || !info.HasTotal {
|
||||||
|
t.Fatalf("unexpected eclipse type: %+v", info)
|
||||||
|
}
|
||||||
|
if !localLunarEclipseVisible(info) {
|
||||||
|
t.Fatalf("expected some phase to be visible")
|
||||||
|
}
|
||||||
|
if localTotalLunarEclipseVisible(info) {
|
||||||
|
t.Fatalf("expected total phase below horizon to be rejected")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestLocalLunarEclipseInfoKeepsLocation(t *testing.T) {
|
func TestLocalLunarEclipseInfoKeepsLocation(t *testing.T) {
|
||||||
loc := time.FixedZone("JST", 9*3600)
|
loc := time.FixedZone("JST", 9*3600)
|
||||||
lon, lat, height := 139.6917, 35.6895, 1234.0
|
lon, lat, height := 139.6917, 35.6895, 1234.0
|
||||||
|
|||||||
+125
-5
@@ -1,6 +1,7 @@
|
|||||||
package eclipse
|
package eclipse
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"math"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"b612.me/astro/basic"
|
"b612.me/astro/basic"
|
||||||
@@ -10,6 +11,18 @@ const (
|
|||||||
sarosCycleLunations = 223
|
sarosCycleLunations = 223
|
||||||
sarosCycleDays = float64(sarosCycleLunations) * solarEclipseSynodicMonthDays
|
sarosCycleDays = float64(sarosCycleLunations) * solarEclipseSynodicMonthDays
|
||||||
sarosWalkLimit = 100
|
sarosWalkLimit = 100
|
||||||
|
|
||||||
|
sarosMagicYearOffset = 3000
|
||||||
|
sarosMagicCountMask = 0x7f
|
||||||
|
sarosMagicDayMask = 0x1f
|
||||||
|
sarosMagicMonthMask = 0x0f
|
||||||
|
sarosMagicYearMask = 0x1fff
|
||||||
|
sarosMagicCountShift = 0
|
||||||
|
sarosMagicDayShift = 7
|
||||||
|
sarosMagicMonthShift = 12
|
||||||
|
sarosMagicYearShift = 16
|
||||||
|
sarosMagicMatchLimitDay = 12.0
|
||||||
|
sarosMagicTieEpsilonDay = 1e-9
|
||||||
)
|
)
|
||||||
|
|
||||||
// SarosInfo 沙罗序列信息, Saros series metadata.
|
// SarosInfo 沙罗序列信息, Saros series metadata.
|
||||||
@@ -25,6 +38,8 @@ type SarosInfo struct {
|
|||||||
Count int
|
Count int
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type sarosMagic uint32
|
||||||
|
|
||||||
type sarosAnchor struct {
|
type sarosAnchor struct {
|
||||||
Series int16
|
Series int16
|
||||||
Count uint8
|
Count uint8
|
||||||
@@ -53,6 +68,20 @@ var lunarSarosHeadOverrides = [...]sarosHeadOverride{
|
|||||||
}
|
}
|
||||||
|
|
||||||
func solarSarosInfo(ttJDE float64) (SarosInfo, bool) {
|
func solarSarosInfo(ttJDE float64) (SarosInfo, bool) {
|
||||||
|
if info, ok := matchSarosMagic(solarSarosAnchors[:], 0, solarSarosHeadOverrides[:], ttJDE); ok {
|
||||||
|
return info, true
|
||||||
|
}
|
||||||
|
return solarSarosInfoByWalk(ttJDE)
|
||||||
|
}
|
||||||
|
|
||||||
|
func lunarSarosInfo(ttJDE float64) (SarosInfo, bool) {
|
||||||
|
if info, ok := matchSarosMagic(lunarSarosAnchors[:], 1, lunarSarosHeadOverrides[:], ttJDE); ok {
|
||||||
|
return info, true
|
||||||
|
}
|
||||||
|
return lunarSarosInfoByWalk(ttJDE)
|
||||||
|
}
|
||||||
|
|
||||||
|
func solarSarosInfoByWalk(ttJDE float64) (SarosInfo, bool) {
|
||||||
headTT, member, ok := solarSarosHead(ttJDE)
|
headTT, member, ok := solarSarosHead(ttJDE)
|
||||||
if !ok {
|
if !ok {
|
||||||
return SarosInfo{}, false
|
return SarosInfo{}, false
|
||||||
@@ -60,7 +89,7 @@ func solarSarosInfo(ttJDE float64) (SarosInfo, bool) {
|
|||||||
if info, ok := matchSarosHeadOverride(solarSarosHeadOverrides[:], headTT, member); ok {
|
if info, ok := matchSarosHeadOverride(solarSarosHeadOverrides[:], headTT, member); ok {
|
||||||
return info, true
|
return info, true
|
||||||
}
|
}
|
||||||
anchor, ok := matchSarosAnchor(solarSarosAnchors[:], headTT)
|
anchor, ok := matchSarosAnchor(solarSarosAnchors[:], 0, headTT)
|
||||||
if !ok || member > int(anchor.Count) {
|
if !ok || member > int(anchor.Count) {
|
||||||
return SarosInfo{}, false
|
return SarosInfo{}, false
|
||||||
}
|
}
|
||||||
@@ -71,7 +100,7 @@ func solarSarosInfo(ttJDE float64) (SarosInfo, bool) {
|
|||||||
}, true
|
}, true
|
||||||
}
|
}
|
||||||
|
|
||||||
func lunarSarosInfo(ttJDE float64) (SarosInfo, bool) {
|
func lunarSarosInfoByWalk(ttJDE float64) (SarosInfo, bool) {
|
||||||
headTT, member, ok := lunarSarosHead(ttJDE)
|
headTT, member, ok := lunarSarosHead(ttJDE)
|
||||||
if !ok {
|
if !ok {
|
||||||
return SarosInfo{}, false
|
return SarosInfo{}, false
|
||||||
@@ -79,7 +108,7 @@ func lunarSarosInfo(ttJDE float64) (SarosInfo, bool) {
|
|||||||
if info, ok := matchSarosHeadOverride(lunarSarosHeadOverrides[:], headTT, member); ok {
|
if info, ok := matchSarosHeadOverride(lunarSarosHeadOverrides[:], headTT, member); ok {
|
||||||
return info, true
|
return info, true
|
||||||
}
|
}
|
||||||
anchor, ok := matchSarosAnchor(lunarSarosAnchors[:], headTT)
|
anchor, ok := matchSarosAnchor(lunarSarosAnchors[:], 1, headTT)
|
||||||
if !ok || member > int(anchor.Count) {
|
if !ok || member > int(anchor.Count) {
|
||||||
return SarosInfo{}, false
|
return SarosInfo{}, false
|
||||||
}
|
}
|
||||||
@@ -120,11 +149,102 @@ func lunarSarosHead(ttJDE float64) (float64, int, bool) {
|
|||||||
return 0, 0, false
|
return 0, 0, false
|
||||||
}
|
}
|
||||||
|
|
||||||
func matchSarosAnchor(anchors []sarosAnchor, headTT float64) (sarosAnchor, bool) {
|
func matchSarosMagic(anchors []sarosMagic, seriesBase int, overrides []sarosHeadOverride, ttJDE float64) (SarosInfo, bool) {
|
||||||
|
if info, ok := matchSarosMagicOverrides(overrides, ttJDE); ok {
|
||||||
|
return info, true
|
||||||
|
}
|
||||||
|
bestDistance := math.Inf(1)
|
||||||
|
best := SarosInfo{}
|
||||||
|
for index, magic := range anchors {
|
||||||
|
anchor := decodeSarosMagic(magic, seriesBase+index)
|
||||||
|
info, distance, ok := matchSarosMagicCandidate(ttJDE, anchor, 0)
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if betterSarosMagicMatch(info, distance, best, bestDistance) {
|
||||||
|
bestDistance = distance
|
||||||
|
best = info
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if bestDistance <= sarosMagicMatchLimitDay {
|
||||||
|
return best, true
|
||||||
|
}
|
||||||
|
return SarosInfo{}, false
|
||||||
|
}
|
||||||
|
|
||||||
|
func matchSarosMagicOverrides(overrides []sarosHeadOverride, ttJDE float64) (SarosInfo, bool) {
|
||||||
|
bestDistance := math.Inf(1)
|
||||||
|
best := SarosInfo{}
|
||||||
|
for _, override := range overrides {
|
||||||
|
anchor := sarosAnchor{
|
||||||
|
Series: override.Series,
|
||||||
|
Count: override.Count,
|
||||||
|
Year: override.HeadYear,
|
||||||
|
Month: override.HeadMonth,
|
||||||
|
Day: override.HeadDay,
|
||||||
|
}
|
||||||
|
info, distance, ok := matchSarosMagicCandidate(ttJDE, anchor, int(override.MemberOffset))
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if betterSarosMagicMatch(info, distance, best, bestDistance) {
|
||||||
|
bestDistance = distance
|
||||||
|
best = info
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if bestDistance <= sarosMagicMatchLimitDay {
|
||||||
|
return best, true
|
||||||
|
}
|
||||||
|
return SarosInfo{}, false
|
||||||
|
}
|
||||||
|
|
||||||
|
func matchSarosMagicCandidate(ttJDE float64, anchor sarosAnchor, memberOffset int) (SarosInfo, float64, bool) {
|
||||||
|
headTT := basic.JDECalc(int(anchor.Year), int(anchor.Month), float64(anchor.Day))
|
||||||
|
if math.IsNaN(headTT) {
|
||||||
|
return SarosInfo{}, 0, false
|
||||||
|
}
|
||||||
|
member := int(math.Round((ttJDE-headTT)/sarosCycleDays)) + 1 + memberOffset
|
||||||
|
if member < 1 || member > int(anchor.Count) {
|
||||||
|
return SarosInfo{}, 0, false
|
||||||
|
}
|
||||||
|
expectedTT := headTT + float64(member-1-memberOffset)*sarosCycleDays
|
||||||
|
return SarosInfo{
|
||||||
|
Series: int(anchor.Series),
|
||||||
|
Member: member,
|
||||||
|
Count: int(anchor.Count),
|
||||||
|
}, math.Abs(ttJDE - expectedTT), true
|
||||||
|
}
|
||||||
|
|
||||||
|
func betterSarosMagicMatch(info SarosInfo, distance float64, best SarosInfo, bestDistance float64) bool {
|
||||||
|
if distance < bestDistance-sarosMagicTieEpsilonDay {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if math.Abs(distance-bestDistance) > sarosMagicTieEpsilonDay {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if info.Series != best.Series {
|
||||||
|
return info.Series < best.Series
|
||||||
|
}
|
||||||
|
return info.Member < best.Member
|
||||||
|
}
|
||||||
|
|
||||||
|
func decodeSarosMagic(magic sarosMagic, series int) sarosAnchor {
|
||||||
|
value := uint32(magic)
|
||||||
|
return sarosAnchor{
|
||||||
|
Series: int16(series),
|
||||||
|
Count: uint8((value >> sarosMagicCountShift) & sarosMagicCountMask),
|
||||||
|
Year: int16(int((value>>sarosMagicYearShift)&sarosMagicYearMask) - sarosMagicYearOffset),
|
||||||
|
Month: uint8((value >> sarosMagicMonthShift) & sarosMagicMonthMask),
|
||||||
|
Day: uint8((value >> sarosMagicDayShift) & sarosMagicDayMask),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func matchSarosAnchor(anchors []sarosMagic, seriesBase int, headTT float64) (sarosAnchor, bool) {
|
||||||
headDate := basic.JDE2DateByZone(headTT, time.UTC, true)
|
headDate := basic.JDE2DateByZone(headTT, time.UTC, true)
|
||||||
year, month, day := headDate.Date()
|
year, month, day := headDate.Date()
|
||||||
monthNumber := int(month)
|
monthNumber := int(month)
|
||||||
for _, anchor := range anchors {
|
for index, magic := range anchors {
|
||||||
|
anchor := decodeSarosMagic(magic, seriesBase+index)
|
||||||
if int(anchor.Year) == year && int(anchor.Month) == monthNumber && int(anchor.Day) == day {
|
if int(anchor.Year) == year && int(anchor.Month) == monthNumber && int(anchor.Day) == day {
|
||||||
return anchor, true
|
return anchor, true
|
||||||
}
|
}
|
||||||
|
|||||||
+181
-181
@@ -2,185 +2,185 @@ package eclipse
|
|||||||
|
|
||||||
// Code generated by /tmp/generate_saros_tables.go; DO NOT EDIT.
|
// Code generated by /tmp/generate_saros_tables.go; DO NOT EDIT.
|
||||||
|
|
||||||
var lunarSarosAnchors = [...]sarosAnchor{
|
var lunarSarosAnchors = [...]sarosMagic{
|
||||||
{Series: 1, Count: 73, Year: -2570, Month: 3, Day: 14},
|
0x21ae3749,
|
||||||
{Series: 2, Count: 73, Year: -2523, Month: 3, Day: 3},
|
0x21dd31c9,
|
||||||
{Series: 3, Count: 76, Year: -2567, Month: 12, Day: 30},
|
0x21b1cf4c,
|
||||||
{Series: 4, Count: 78, Year: -2646, Month: 10, Day: 6},
|
0x2162a34e,
|
||||||
{Series: 5, Count: 77, Year: -2455, Month: 12, Day: 22},
|
0x2221cb4d,
|
||||||
{Series: 6, Count: 86, Year: -2624, Month: 8, Day: 4},
|
0x21788256,
|
||||||
{Series: 7, Count: 89, Year: -2595, Month: 7, Day: 16},
|
0x21957859,
|
||||||
{Series: 8, Count: 86, Year: -2494, Month: 8, Day: 8},
|
0x21fa8456,
|
||||||
{Series: 9, Count: 75, Year: -2501, Month: 6, Day: 26},
|
0x21f36d4b,
|
||||||
{Series: 10, Count: 74, Year: -2454, Month: 6, Day: 17},
|
0x222268ca,
|
||||||
{Series: 11, Count: 74, Year: -2371, Month: 6, Day: 29},
|
0x22756eca,
|
||||||
{Series: 12, Count: 73, Year: -2360, Month: 5, Day: 28},
|
0x22805e49,
|
||||||
{Series: 13, Count: 73, Year: -2313, Month: 5, Day: 20},
|
0x22af5a49,
|
||||||
{Series: 14, Count: 73, Year: -2230, Month: 6, Day: 1},
|
0x230260c9,
|
||||||
{Series: 15, Count: 73, Year: -2219, Month: 4, Day: 30},
|
0x230d4f49,
|
||||||
{Series: 16, Count: 73, Year: -2172, Month: 4, Day: 21},
|
0x233c4ac9,
|
||||||
{Series: 17, Count: 72, Year: -2089, Month: 5, Day: 4},
|
0x238f5248,
|
||||||
{Series: 18, Count: 73, Year: -2078, Month: 4, Day: 2},
|
0x239a4149,
|
||||||
{Series: 19, Count: 73, Year: -2031, Month: 3, Day: 24},
|
0x23c93c49,
|
||||||
{Series: 20, Count: 72, Year: -1948, Month: 4, Day: 5},
|
0x241c42c8,
|
||||||
{Series: 21, Count: 74, Year: -1955, Month: 2, Day: 22},
|
0x24152b4a,
|
||||||
{Series: 22, Count: 74, Year: -1926, Month: 2, Day: 2},
|
0x2432214a,
|
||||||
{Series: 23, Count: 73, Year: -1825, Month: 2, Day: 25},
|
0x24972cc9,
|
||||||
{Series: 24, Count: 85, Year: -2031, Month: 9, Day: 16},
|
0x23c99855,
|
||||||
{Series: 25, Count: 87, Year: -2038, Month: 8, Day: 6},
|
0x23c28357,
|
||||||
{Series: 26, Count: 85, Year: -1919, Month: 9, Day: 9},
|
0x243994d5,
|
||||||
{Series: 27, Count: 85, Year: -1926, Month: 7, Day: 28},
|
0x24327e55,
|
||||||
{Series: 28, Count: 74, Year: -1897, Month: 7, Day: 9},
|
0x244f74ca,
|
||||||
{Series: 29, Count: 83, Year: -1814, Month: 7, Day: 21},
|
0x24a27ad3,
|
||||||
{Series: 30, Count: 74, Year: -1803, Month: 6, Day: 19},
|
0x24ad69ca,
|
||||||
{Series: 31, Count: 73, Year: -1774, Month: 5, Day: 30},
|
0x24ca5f49,
|
||||||
{Series: 32, Count: 73, Year: -1673, Month: 6, Day: 23},
|
0x252f6bc9,
|
||||||
{Series: 33, Count: 73, Year: -1662, Month: 5, Day: 22},
|
0x253a5b49,
|
||||||
{Series: 34, Count: 72, Year: -1615, Month: 5, Day: 13},
|
0x256956c8,
|
||||||
{Series: 35, Count: 72, Year: -1532, Month: 5, Day: 25},
|
0x25bc5cc8,
|
||||||
{Series: 36, Count: 73, Year: -1521, Month: 4, Day: 24},
|
0x25c74c49,
|
||||||
{Series: 37, Count: 72, Year: -1492, Month: 4, Day: 3},
|
0x25e441c8,
|
||||||
{Series: 38, Count: 72, Year: -1391, Month: 4, Day: 27},
|
0x26494dc8,
|
||||||
{Series: 39, Count: 73, Year: -1380, Month: 3, Day: 26},
|
0x26543d49,
|
||||||
{Series: 40, Count: 73, Year: -1369, Month: 2, Day: 24},
|
0x265f2c49,
|
||||||
{Series: 41, Count: 73, Year: -1268, Month: 3, Day: 18},
|
0x26c43949,
|
||||||
{Series: 42, Count: 74, Year: -1275, Month: 2, Day: 4},
|
0x26bd224a,
|
||||||
{Series: 43, Count: 85, Year: -1463, Month: 9, Day: 7},
|
0x260193d5,
|
||||||
{Series: 44, Count: 76, Year: -1199, Month: 1, Day: 6},
|
0x2709134c,
|
||||||
{Series: 45, Count: 85, Year: -1351, Month: 8, Day: 29},
|
0x26718ed5,
|
||||||
{Series: 46, Count: 76, Year: -1358, Month: 7, Day: 19},
|
0x266a79cc,
|
||||||
{Series: 47, Count: 86, Year: -1275, Month: 7, Day: 31},
|
0x26bd7fd6,
|
||||||
{Series: 48, Count: 75, Year: -1228, Month: 7, Day: 21},
|
0x26ec7acb,
|
||||||
{Series: 49, Count: 73, Year: -1217, Month: 6, Day: 21},
|
0x26f76ac9,
|
||||||
{Series: 50, Count: 73, Year: -1134, Month: 7, Day: 3},
|
0x274a71c9,
|
||||||
{Series: 51, Count: 73, Year: -1105, Month: 6, Day: 13},
|
0x276766c9,
|
||||||
{Series: 52, Count: 72, Year: -1076, Month: 5, Day: 23},
|
0x27845bc8,
|
||||||
{Series: 53, Count: 72, Year: -993, Month: 6, Day: 5},
|
0x27d762c8,
|
||||||
{Series: 54, Count: 72, Year: -946, Month: 5, Day: 26},
|
0x28065d48,
|
||||||
{Series: 55, Count: 72, Year: -935, Month: 4, Day: 25},
|
0x28114cc8,
|
||||||
{Series: 56, Count: 72, Year: -852, Month: 5, Day: 7},
|
0x286453c8,
|
||||||
{Series: 57, Count: 73, Year: -823, Month: 4, Day: 16},
|
0x28814849,
|
||||||
{Series: 58, Count: 73, Year: -812, Month: 3, Day: 16},
|
0x288c3849,
|
||||||
{Series: 59, Count: 71, Year: -711, Month: 4, Day: 9},
|
0x28f144c7,
|
||||||
{Series: 60, Count: 73, Year: -700, Month: 3, Day: 8},
|
0x28fc3449,
|
||||||
{Series: 61, Count: 78, Year: -780, Month: 12, Day: 13},
|
0x28acc6ce,
|
||||||
{Series: 62, Count: 74, Year: -624, Month: 2, Day: 8},
|
0x2948244a,
|
||||||
{Series: 63, Count: 82, Year: -722, Month: 11, Day: 3},
|
0x28e6b1d2,
|
||||||
{Series: 64, Count: 84, Year: -783, Month: 8, Day: 20},
|
0x28a98a54,
|
||||||
{Series: 65, Count: 86, Year: -736, Month: 8, Day: 11},
|
0x28d885d6,
|
||||||
{Series: 66, Count: 84, Year: -671, Month: 8, Day: 12},
|
0x29198654,
|
||||||
{Series: 67, Count: 73, Year: -660, Month: 7, Day: 11},
|
0x292475c9,
|
||||||
{Series: 68, Count: 72, Year: -595, Month: 7, Day: 14},
|
0x29657748,
|
||||||
{Series: 69, Count: 73, Year: -530, Month: 7, Day: 15},
|
0x29a677c9,
|
||||||
{Series: 70, Count: 72, Year: -519, Month: 6, Day: 13},
|
0x29b166c8,
|
||||||
{Series: 71, Count: 72, Year: -472, Month: 6, Day: 4},
|
0x29e06248,
|
||||||
{Series: 72, Count: 72, Year: -389, Month: 6, Day: 17},
|
0x2a3368c8,
|
||||||
{Series: 73, Count: 72, Year: -378, Month: 5, Day: 16},
|
0x2a3e5848,
|
||||||
{Series: 74, Count: 72, Year: -331, Month: 5, Day: 7},
|
0x2a6d53c8,
|
||||||
{Series: 75, Count: 72, Year: -266, Month: 5, Day: 8},
|
0x2aae5448,
|
||||||
{Series: 76, Count: 73, Year: -255, Month: 4, Day: 7},
|
0x2ab943c9,
|
||||||
{Series: 77, Count: 72, Year: -190, Month: 4, Day: 9},
|
0x2afa44c8,
|
||||||
{Series: 78, Count: 72, Year: -125, Month: 4, Day: 10},
|
0x2b3b4548,
|
||||||
{Series: 79, Count: 73, Year: -132, Month: 2, Day: 27},
|
0x2b342dc9,
|
||||||
{Series: 80, Count: 74, Year: -103, Month: 2, Day: 7},
|
0x2b5123ca,
|
||||||
{Series: 81, Count: 74, Year: -20, Month: 2, Day: 19},
|
0x2ba429ca,
|
||||||
{Series: 82, Count: 84, Year: -208, Month: 9, Day: 21},
|
0x2ae89ad4,
|
||||||
{Series: 83, Count: 84, Year: -197, Month: 8, Day: 22},
|
0x2af38b54,
|
||||||
{Series: 84, Count: 84, Year: -96, Month: 9, Day: 13},
|
0x2b5896d4,
|
||||||
{Series: 85, Count: 76, Year: -103, Month: 8, Day: 2},
|
0x2b51814c,
|
||||||
{Series: 86, Count: 73, Year: -74, Month: 7, Day: 13},
|
0x2b6e76c9,
|
||||||
{Series: 87, Count: 73, Year: 27, Month: 8, Day: 6},
|
0x2bd38349,
|
||||||
{Series: 88, Count: 72, Year: 38, Month: 7, Day: 5},
|
0x2bde72c8,
|
||||||
{Series: 89, Count: 72, Year: 67, Month: 6, Day: 15},
|
0x2bfb67c8,
|
||||||
{Series: 90, Count: 72, Year: 150, Month: 6, Day: 27},
|
0x2c4e6dc8,
|
||||||
{Series: 91, Count: 72, Year: 179, Month: 6, Day: 7},
|
0x2c6b63c8,
|
||||||
{Series: 92, Count: 71, Year: 208, Month: 5, Day: 17},
|
0x2c8858c7,
|
||||||
{Series: 93, Count: 71, Year: 291, Month: 5, Day: 30},
|
0x2cdb5f47,
|
||||||
{Series: 94, Count: 71, Year: 320, Month: 5, Day: 9},
|
0x2cf854c7,
|
||||||
{Series: 95, Count: 71, Year: 349, Month: 4, Day: 19},
|
0x2d1549c7,
|
||||||
{Series: 96, Count: 71, Year: 432, Month: 5, Day: 1},
|
0x2d6850c7,
|
||||||
{Series: 97, Count: 72, Year: 443, Month: 3, Day: 31},
|
0x2d733fc8,
|
||||||
{Series: 98, Count: 74, Year: 436, Month: 2, Day: 18},
|
0x2d6c294a,
|
||||||
{Series: 99, Count: 72, Year: 555, Month: 3, Day: 24},
|
0x2de33c48,
|
||||||
{Series: 100, Count: 79, Year: 439, Month: 12, Day: 6},
|
0x2d6fc34f,
|
||||||
{Series: 101, Count: 83, Year: 360, Month: 9, Day: 11},
|
0x2d2095d3,
|
||||||
{Series: 102, Count: 84, Year: 461, Month: 10, Day: 5},
|
0x2d85a2d4,
|
||||||
{Series: 103, Count: 82, Year: 472, Month: 9, Day: 3},
|
0x2d9091d2,
|
||||||
{Series: 104, Count: 72, Year: 483, Month: 8, Day: 4},
|
0x2d9b8248,
|
||||||
{Series: 105, Count: 73, Year: 566, Month: 8, Day: 16},
|
0x2dee8849,
|
||||||
{Series: 106, Count: 73, Year: 595, Month: 7, Day: 27},
|
0x2e0b7dc9,
|
||||||
{Series: 107, Count: 72, Year: 606, Month: 6, Day: 26},
|
0x2e166d48,
|
||||||
{Series: 108, Count: 72, Year: 689, Month: 7, Day: 8},
|
0x2e697448,
|
||||||
{Series: 109, Count: 71, Year: 736, Month: 6, Day: 27},
|
0x2e986dc7,
|
||||||
{Series: 110, Count: 72, Year: 747, Month: 5, Day: 28},
|
0x2ea35e48,
|
||||||
{Series: 111, Count: 71, Year: 830, Month: 6, Day: 10},
|
0x2ef66547,
|
||||||
{Series: 112, Count: 72, Year: 859, Month: 5, Day: 20},
|
0x2f135a48,
|
||||||
{Series: 113, Count: 71, Year: 888, Month: 4, Day: 29},
|
0x2f304ec7,
|
||||||
{Series: 114, Count: 71, Year: 971, Month: 5, Day: 13},
|
0x2f8356c7,
|
||||||
{Series: 115, Count: 72, Year: 1000, Month: 4, Day: 21},
|
0x2fa04ac8,
|
||||||
{Series: 116, Count: 73, Year: 993, Month: 3, Day: 11},
|
0x2f9935c9,
|
||||||
{Series: 117, Count: 71, Year: 1094, Month: 4, Day: 3},
|
0x2ffe41c7,
|
||||||
{Series: 118, Count: 73, Year: 1105, Month: 3, Day: 2},
|
0x30093149,
|
||||||
{Series: 119, Count: 82, Year: 935, Month: 10, Day: 14},
|
0x2f5fa752,
|
||||||
{Series: 120, Count: 83, Year: 1000, Month: 10, Day: 16},
|
0x2fa0a853,
|
||||||
{Series: 121, Count: 82, Year: 1047, Month: 10, Day: 6},
|
0x2fcfa352,
|
||||||
{Series: 122, Count: 74, Year: 1022, Month: 8, Day: 14},
|
0x2fb6874a,
|
||||||
{Series: 123, Count: 72, Year: 1087, Month: 8, Day: 16},
|
0x2ff78848,
|
||||||
{Series: 124, Count: 73, Year: 1152, Month: 8, Day: 17},
|
0x303888c9,
|
||||||
{Series: 125, Count: 72, Year: 1163, Month: 7, Day: 17},
|
0x304378c8,
|
||||||
{Series: 126, Count: 70, Year: 1228, Month: 7, Day: 18},
|
0x30847946,
|
||||||
{Series: 127, Count: 72, Year: 1275, Month: 7, Day: 9},
|
0x30b374c8,
|
||||||
{Series: 128, Count: 71, Year: 1304, Month: 6, Day: 18},
|
0x30d06947,
|
||||||
{Series: 129, Count: 71, Year: 1351, Month: 6, Day: 10},
|
0x30ff6547,
|
||||||
{Series: 130, Count: 71, Year: 1416, Month: 6, Day: 10},
|
0x31406547,
|
||||||
{Series: 131, Count: 72, Year: 1427, Month: 5, Day: 10},
|
0x314b5548,
|
||||||
{Series: 132, Count: 71, Year: 1492, Month: 5, Day: 12},
|
0x318c5647,
|
||||||
{Series: 133, Count: 71, Year: 1557, Month: 5, Day: 13},
|
0x31cd56c7,
|
||||||
{Series: 134, Count: 72, Year: 1550, Month: 4, Day: 1},
|
0x31c640c8,
|
||||||
{Series: 135, Count: 71, Year: 1615, Month: 4, Day: 13},
|
0x320746c7,
|
||||||
{Series: 136, Count: 72, Year: 1680, Month: 4, Day: 13},
|
0x324846c8,
|
||||||
{Series: 137, Count: 78, Year: 1564, Month: 12, Day: 17},
|
0x31d4c8ce,
|
||||||
{Series: 138, Count: 82, Year: 1521, Month: 10, Day: 15},
|
0x31a9a7d2,
|
||||||
{Series: 139, Count: 79, Year: 1658, Month: 12, Day: 9},
|
0x3232c4cf,
|
||||||
{Series: 140, Count: 77, Year: 1597, Month: 9, Day: 25},
|
0x31f59ccd,
|
||||||
{Series: 141, Count: 72, Year: 1608, Month: 8, Day: 25},
|
0x32008cc8,
|
||||||
{Series: 142, Count: 73, Year: 1709, Month: 9, Day: 19},
|
0x326599c9,
|
||||||
{Series: 143, Count: 72, Year: 1720, Month: 8, Day: 18},
|
0x32708948,
|
||||||
{Series: 144, Count: 71, Year: 1749, Month: 7, Day: 29},
|
0x328d7ec7,
|
||||||
{Series: 145, Count: 71, Year: 1832, Month: 8, Day: 11},
|
0x32e085c7,
|
||||||
{Series: 146, Count: 72, Year: 1843, Month: 7, Day: 11},
|
0x32eb75c8,
|
||||||
{Series: 147, Count: 70, Year: 1890, Month: 7, Day: 2},
|
0x331a7146,
|
||||||
{Series: 148, Count: 70, Year: 1973, Month: 7, Day: 15},
|
0x336d77c6,
|
||||||
{Series: 149, Count: 71, Year: 1984, Month: 6, Day: 13},
|
0x337866c7,
|
||||||
{Series: 150, Count: 71, Year: 2013, Month: 5, Day: 25},
|
0x33955cc7,
|
||||||
{Series: 151, Count: 71, Year: 2096, Month: 6, Day: 6},
|
0x33e86347,
|
||||||
{Series: 152, Count: 72, Year: 2107, Month: 5, Day: 7},
|
0x33f353c8,
|
||||||
{Series: 153, Count: 71, Year: 2136, Month: 4, Day: 16},
|
0x34104847,
|
||||||
{Series: 154, Count: 71, Year: 2237, Month: 5, Day: 10},
|
0x34755547,
|
||||||
{Series: 155, Count: 73, Year: 2212, Month: 3, Day: 18},
|
0x345c3949,
|
||||||
{Series: 156, Count: 81, Year: 2060, Month: 11, Day: 8},
|
0x33c4b451,
|
||||||
{Series: 157, Count: 73, Year: 2306, Month: 3, Day: 1},
|
0x34ba30c9,
|
||||||
{Series: 158, Count: 81, Year: 2154, Month: 10, Day: 21},
|
0x3422aad1,
|
||||||
{Series: 159, Count: 73, Year: 2147, Month: 9, Day: 9},
|
0x341b94c9,
|
||||||
{Series: 160, Count: 72, Year: 2248, Month: 10, Day: 3},
|
0x3480a1c8,
|
||||||
{Series: 161, Count: 73, Year: 2259, Month: 9, Day: 2},
|
0x348b9149,
|
||||||
{Series: 162, Count: 71, Year: 2288, Month: 8, Day: 12},
|
0x34a88647,
|
||||||
{Series: 163, Count: 70, Year: 2371, Month: 8, Day: 27},
|
0x34fb8dc6,
|
||||||
{Series: 164, Count: 71, Year: 2400, Month: 8, Day: 5},
|
0x351882c7,
|
||||||
{Series: 165, Count: 71, Year: 2411, Month: 7, Day: 6},
|
0x35237347,
|
||||||
{Series: 166, Count: 70, Year: 2494, Month: 7, Day: 18},
|
0x35767946,
|
||||||
{Series: 167, Count: 71, Year: 2541, Month: 7, Day: 9},
|
0x35a574c7,
|
||||||
{Series: 168, Count: 71, Year: 2552, Month: 6, Day: 8},
|
0x35b06447,
|
||||||
{Series: 169, Count: 70, Year: 2635, Month: 6, Day: 22},
|
0x36036b46,
|
||||||
{Series: 170, Count: 71, Year: 2664, Month: 6, Day: 1},
|
0x362060c7,
|
||||||
{Series: 171, Count: 71, Year: 2675, Month: 5, Day: 1},
|
0x362b50c7,
|
||||||
{Series: 172, Count: 70, Year: 2758, Month: 5, Day: 15},
|
0x367e57c6,
|
||||||
{Series: 173, Count: 72, Year: 2787, Month: 4, Day: 24},
|
0x369b4c48,
|
||||||
{Series: 174, Count: 79, Year: 2635, Month: 12, Day: 16},
|
0x3603c84f,
|
||||||
{Series: 175, Count: 74, Year: 2791, Month: 2, Day: 11},
|
0x369f25ca,
|
||||||
{Series: 176, Count: 79, Year: 2747, Month: 12, Day: 9},
|
0x3673c4cf,
|
||||||
{Series: 177, Count: 73, Year: 2704, Month: 10, Day: 5},
|
0x3648a2c9,
|
||||||
{Series: 178, Count: 70, Year: 2769, Month: 10, Day: 7},
|
0x3689a3c6,
|
||||||
{Series: 179, Count: 73, Year: 2816, Month: 9, Day: 27},
|
0x36b89dc9,
|
||||||
{Series: 180, Count: 71, Year: 2827, Month: 8, Day: 28},
|
0x36c38e47,
|
||||||
}
|
}
|
||||||
|
|||||||
+182
-182
@@ -2,186 +2,186 @@ package eclipse
|
|||||||
|
|
||||||
// Code generated by /tmp/generate_saros_tables.go; DO NOT EDIT.
|
// Code generated by /tmp/generate_saros_tables.go; DO NOT EDIT.
|
||||||
|
|
||||||
var solarSarosAnchors = [...]sarosAnchor{
|
var solarSarosAnchors = [...]sarosMagic{
|
||||||
{Series: 0, Count: 72, Year: -2955, Month: 5, Day: 23},
|
0x202d5bc8,
|
||||||
{Series: 1, Count: 72, Year: -2872, Month: 6, Day: 4},
|
0x20806248,
|
||||||
{Series: 2, Count: 73, Year: -2861, Month: 5, Day: 4},
|
0x208b5249,
|
||||||
{Series: 3, Count: 72, Year: -2814, Month: 4, Day: 24},
|
0x20ba4c48,
|
||||||
{Series: 4, Count: 72, Year: -2731, Month: 5, Day: 6},
|
0x210d5348,
|
||||||
{Series: 5, Count: 73, Year: -2720, Month: 4, Day: 4},
|
0x21184249,
|
||||||
{Series: 6, Count: 72, Year: -2673, Month: 3, Day: 27},
|
0x21473dc8,
|
||||||
{Series: 7, Count: 72, Year: -2590, Month: 4, Day: 8},
|
0x219a4448,
|
||||||
{Series: 8, Count: 73, Year: -2579, Month: 3, Day: 7},
|
0x21a533c9,
|
||||||
{Series: 9, Count: 74, Year: -2568, Month: 2, Day: 6},
|
0x21b0234a,
|
||||||
{Series: 10, Count: 73, Year: -2467, Month: 2, Day: 28},
|
0x22152e49,
|
||||||
{Series: 11, Count: 76, Year: -2492, Month: 1, Day: 6},
|
0x21fc134c,
|
||||||
{Series: 12, Count: 86, Year: -2662, Month: 8, Day: 20},
|
0x21528a56,
|
||||||
{Series: 13, Count: 85, Year: -2543, Month: 9, Day: 23},
|
0x21c99bd5,
|
||||||
{Series: 14, Count: 85, Year: -2550, Month: 8, Day: 11},
|
0x21c285d5,
|
||||||
{Series: 15, Count: 75, Year: -2557, Month: 7, Day: 1},
|
0x21bb70cb,
|
||||||
{Series: 16, Count: 85, Year: -2456, Month: 7, Day: 23},
|
0x22207bd5,
|
||||||
{Series: 17, Count: 74, Year: -2427, Month: 7, Day: 3},
|
0x223d71ca,
|
||||||
{Series: 18, Count: 73, Year: -2416, Month: 6, Day: 2},
|
0x22486149,
|
||||||
{Series: 19, Count: 73, Year: -2333, Month: 6, Day: 15},
|
0x229b67c9,
|
||||||
{Series: 20, Count: 72, Year: -2286, Month: 6, Day: 5},
|
0x22ca62c8,
|
||||||
{Series: 21, Count: 72, Year: -2275, Month: 5, Day: 5},
|
0x22d552c8,
|
||||||
{Series: 22, Count: 71, Year: -2174, Month: 5, Day: 28},
|
0x233a5e47,
|
||||||
{Series: 23, Count: 72, Year: -2145, Month: 5, Day: 7},
|
0x235753c8,
|
||||||
{Series: 24, Count: 72, Year: -2134, Month: 4, Day: 6},
|
0x23624348,
|
||||||
{Series: 25, Count: 71, Year: -2033, Month: 4, Day: 30},
|
0x23c74f47,
|
||||||
{Series: 26, Count: 72, Year: -2004, Month: 4, Day: 8},
|
0x23e44448,
|
||||||
{Series: 27, Count: 72, Year: -1993, Month: 3, Day: 9},
|
0x23ef34c8,
|
||||||
{Series: 28, Count: 72, Year: -1910, Month: 3, Day: 22},
|
0x24423b48,
|
||||||
{Series: 29, Count: 73, Year: -1881, Month: 3, Day: 1},
|
0x245f30c9,
|
||||||
{Series: 30, Count: 83, Year: -2051, Month: 10, Day: 12},
|
0x23b5a653,
|
||||||
{Series: 31, Count: 74, Year: -1805, Month: 1, Day: 31},
|
0x24ab1fca,
|
||||||
{Series: 32, Count: 84, Year: -1957, Month: 9, Day: 24},
|
0x24139c54,
|
||||||
{Series: 33, Count: 84, Year: -1982, Month: 8, Day: 2},
|
0x23fa8154,
|
||||||
{Series: 34, Count: 86, Year: -1917, Month: 8, Day: 4},
|
0x243b8256,
|
||||||
{Series: 35, Count: 84, Year: -1870, Month: 7, Day: 25},
|
0x246a7cd4,
|
||||||
{Series: 36, Count: 73, Year: -1859, Month: 6, Day: 23},
|
0x24756bc9,
|
||||||
{Series: 37, Count: 73, Year: -1794, Month: 6, Day: 25},
|
0x24b66cc9,
|
||||||
{Series: 38, Count: 73, Year: -1729, Month: 6, Day: 26},
|
0x24f76d49,
|
||||||
{Series: 39, Count: 72, Year: -1718, Month: 5, Day: 26},
|
0x25025d48,
|
||||||
{Series: 40, Count: 72, Year: -1653, Month: 5, Day: 28},
|
0x25435e48,
|
||||||
{Series: 41, Count: 72, Year: -1588, Month: 5, Day: 28},
|
0x25845e48,
|
||||||
{Series: 42, Count: 72, Year: -1577, Month: 4, Day: 28},
|
0x258f4e48,
|
||||||
{Series: 43, Count: 72, Year: -1512, Month: 4, Day: 29},
|
0x25d04ec8,
|
||||||
{Series: 44, Count: 72, Year: -1447, Month: 4, Day: 30},
|
0x26114f48,
|
||||||
{Series: 45, Count: 72, Year: -1436, Month: 3, Day: 30},
|
0x261c3f48,
|
||||||
{Series: 46, Count: 72, Year: -1371, Month: 4, Day: 1},
|
0x265d40c8,
|
||||||
{Series: 47, Count: 72, Year: -1306, Month: 4, Day: 2},
|
0x269e4148,
|
||||||
{Series: 48, Count: 74, Year: -1331, Month: 2, Day: 8},
|
0x2685244a,
|
||||||
{Series: 49, Count: 72, Year: -1248, Month: 2, Day: 22},
|
0x26d82b48,
|
||||||
{Series: 50, Count: 73, Year: -1201, Month: 2, Day: 11},
|
0x270725c9,
|
||||||
{Series: 51, Count: 85, Year: -1407, Month: 9, Day: 2},
|
0x26399155,
|
||||||
{Series: 52, Count: 86, Year: -1378, Month: 8, Day: 14},
|
0x26568756,
|
||||||
{Series: 53, Count: 84, Year: -1277, Month: 9, Day: 6},
|
0x26bb9354,
|
||||||
{Series: 54, Count: 74, Year: -1284, Month: 7, Day: 25},
|
0x26b47cca,
|
||||||
{Series: 55, Count: 73, Year: -1255, Month: 7, Day: 6},
|
0x26d17349,
|
||||||
{Series: 56, Count: 74, Year: -1172, Month: 7, Day: 17},
|
0x272478ca,
|
||||||
{Series: 57, Count: 73, Year: -1161, Month: 6, Day: 17},
|
0x272f68c9,
|
||||||
{Series: 58, Count: 72, Year: -1114, Month: 6, Day: 7},
|
0x275e63c8,
|
||||||
{Series: 59, Count: 72, Year: -1031, Month: 6, Day: 19},
|
0x27b169c8,
|
||||||
{Series: 60, Count: 72, Year: -1020, Month: 5, Day: 18},
|
0x27bc5948,
|
||||||
{Series: 61, Count: 71, Year: -973, Month: 5, Day: 10},
|
0x27eb5547,
|
||||||
{Series: 62, Count: 71, Year: -890, Month: 5, Day: 22},
|
0x283e5b47,
|
||||||
{Series: 63, Count: 72, Year: -879, Month: 4, Day: 20},
|
0x28494a48,
|
||||||
{Series: 64, Count: 71, Year: -832, Month: 4, Day: 11},
|
0x287845c7,
|
||||||
{Series: 65, Count: 71, Year: -749, Month: 4, Day: 24},
|
0x28cb4c47,
|
||||||
{Series: 66, Count: 73, Year: -756, Month: 3, Day: 12},
|
0x28c43649,
|
||||||
{Series: 67, Count: 72, Year: -709, Month: 3, Day: 4},
|
0x28f33248,
|
||||||
{Series: 68, Count: 72, Year: -626, Month: 3, Day: 16},
|
0x29463848,
|
||||||
{Series: 69, Count: 78, Year: -724, Month: 12, Day: 9},
|
0x28e4c4ce,
|
||||||
{Series: 70, Count: 84, Year: -821, Month: 9, Day: 5},
|
0x288392d4,
|
||||||
{Series: 71, Count: 82, Year: -684, Month: 10, Day: 19},
|
0x290ca9d2,
|
||||||
{Series: 72, Count: 83, Year: -727, Month: 8, Day: 16},
|
0x28e18853,
|
||||||
{Series: 73, Count: 72, Year: -698, Month: 7, Day: 27},
|
0x28fe7dc8,
|
||||||
{Series: 74, Count: 75, Year: -615, Month: 8, Day: 8},
|
0x2951844b,
|
||||||
{Series: 75, Count: 73, Year: -604, Month: 7, Day: 7},
|
0x295c73c9,
|
||||||
{Series: 76, Count: 72, Year: -575, Month: 6, Day: 18},
|
0x29796948,
|
||||||
{Series: 77, Count: 71, Year: -474, Month: 7, Day: 11},
|
0x29de75c7,
|
||||||
{Series: 78, Count: 72, Year: -463, Month: 6, Day: 9},
|
0x29e964c8,
|
||||||
{Series: 79, Count: 71, Year: -434, Month: 5, Day: 21},
|
0x2a065ac7,
|
||||||
{Series: 80, Count: 71, Year: -333, Month: 6, Day: 13},
|
0x2a6b66c7,
|
||||||
{Series: 81, Count: 72, Year: -322, Month: 5, Day: 12},
|
0x2a765648,
|
||||||
{Series: 82, Count: 71, Year: -293, Month: 4, Day: 22},
|
0x2a934b47,
|
||||||
{Series: 83, Count: 71, Year: -210, Month: 5, Day: 5},
|
0x2ae652c7,
|
||||||
{Series: 84, Count: 72, Year: -181, Month: 4, Day: 14},
|
0x2b034748,
|
||||||
{Series: 85, Count: 72, Year: -170, Month: 3, Day: 14},
|
0x2b0e3748,
|
||||||
{Series: 86, Count: 71, Year: -69, Month: 4, Day: 6},
|
0x2b734347,
|
||||||
{Series: 87, Count: 73, Year: -76, Month: 2, Day: 23},
|
0x2b6c2bc9,
|
||||||
{Series: 88, Count: 83, Year: -246, Month: 10, Day: 6},
|
0x2ac2a353,
|
||||||
{Series: 89, Count: 73, Year: 18, Month: 2, Day: 4},
|
0x2bca2249,
|
||||||
{Series: 90, Count: 83, Year: -134, Month: 9, Day: 28},
|
0x2b329e53,
|
||||||
{Series: 91, Count: 75, Year: -159, Month: 8, Day: 6},
|
0x2b19834b,
|
||||||
{Series: 92, Count: 74, Year: -76, Month: 8, Day: 19},
|
0x2b6c89ca,
|
||||||
{Series: 93, Count: 74, Year: -29, Month: 8, Day: 9},
|
0x2b9b84ca,
|
||||||
{Series: 94, Count: 72, Year: -18, Month: 7, Day: 9},
|
0x2ba674c8,
|
||||||
{Series: 95, Count: 71, Year: 47, Month: 7, Day: 11},
|
0x2be775c7,
|
||||||
{Series: 96, Count: 72, Year: 94, Month: 7, Day: 1},
|
0x2c1670c8,
|
||||||
{Series: 97, Count: 71, Year: 123, Month: 6, Day: 11},
|
0x2c3365c7,
|
||||||
{Series: 98, Count: 71, Year: 188, Month: 6, Day: 12},
|
0x2c746647,
|
||||||
{Series: 99, Count: 72, Year: 235, Month: 6, Day: 3},
|
0x2ca361c8,
|
||||||
{Series: 100, Count: 71, Year: 264, Month: 5, Day: 13},
|
0x2cc056c7,
|
||||||
{Series: 101, Count: 71, Year: 329, Month: 5, Day: 15},
|
0x2d0157c7,
|
||||||
{Series: 102, Count: 71, Year: 376, Month: 5, Day: 5},
|
0x2d3052c7,
|
||||||
{Series: 103, Count: 72, Year: 387, Month: 4, Day: 4},
|
0x2d3b4248,
|
||||||
{Series: 104, Count: 70, Year: 470, Month: 4, Day: 17},
|
0x2d8e48c6,
|
||||||
{Series: 105, Count: 72, Year: 499, Month: 3, Day: 27},
|
0x2dab3dc8,
|
||||||
{Series: 106, Count: 75, Year: 456, Month: 1, Day: 23},
|
0x2d801bcb,
|
||||||
{Series: 107, Count: 72, Year: 557, Month: 2, Day: 15},
|
0x2de527c8,
|
||||||
{Series: 108, Count: 76, Year: 550, Month: 1, Day: 4},
|
0x2dde124c,
|
||||||
{Series: 109, Count: 81, Year: 416, Month: 9, Day: 7},
|
0x2d5893d1,
|
||||||
{Series: 110, Count: 72, Year: 463, Month: 8, Day: 30},
|
0x2d878f48,
|
||||||
{Series: 111, Count: 79, Year: 528, Month: 8, Day: 30},
|
0x2dc88f4f,
|
||||||
{Series: 112, Count: 72, Year: 539, Month: 7, Day: 31},
|
0x2dd37fc8,
|
||||||
{Series: 113, Count: 71, Year: 586, Month: 7, Day: 22},
|
0x2e027b47,
|
||||||
{Series: 114, Count: 72, Year: 651, Month: 7, Day: 23},
|
0x2e437bc8,
|
||||||
{Series: 115, Count: 72, Year: 662, Month: 6, Day: 21},
|
0x2e4e6ac8,
|
||||||
{Series: 116, Count: 70, Year: 727, Month: 6, Day: 23},
|
0x2e8f6bc6,
|
||||||
{Series: 117, Count: 71, Year: 792, Month: 6, Day: 24},
|
0x2ed06c47,
|
||||||
{Series: 118, Count: 72, Year: 803, Month: 5, Day: 24},
|
0x2edb5c48,
|
||||||
{Series: 119, Count: 71, Year: 850, Month: 5, Day: 15},
|
0x2f0a57c7,
|
||||||
{Series: 120, Count: 71, Year: 933, Month: 5, Day: 27},
|
0x2f5d5dc7,
|
||||||
{Series: 121, Count: 71, Year: 944, Month: 4, Day: 25},
|
0x2f684cc7,
|
||||||
{Series: 122, Count: 70, Year: 991, Month: 4, Day: 17},
|
0x2f9748c6,
|
||||||
{Series: 123, Count: 70, Year: 1074, Month: 4, Day: 29},
|
0x2fea4ec6,
|
||||||
{Series: 124, Count: 73, Year: 1049, Month: 3, Day: 6},
|
0x2fd13349,
|
||||||
{Series: 125, Count: 73, Year: 1060, Month: 2, Day: 4},
|
0x2fdc2249,
|
||||||
{Series: 126, Count: 72, Year: 1179, Month: 3, Day: 10},
|
0x30533548,
|
||||||
{Series: 127, Count: 82, Year: 991, Month: 10, Day: 10},
|
0x2f97a552,
|
||||||
{Series: 128, Count: 73, Year: 984, Month: 8, Day: 29},
|
0x2f908ec9,
|
||||||
{Series: 129, Count: 80, Year: 1103, Month: 10, Day: 3},
|
0x3007a1d0,
|
||||||
{Series: 130, Count: 73, Year: 1096, Month: 8, Day: 20},
|
0x30008a49,
|
||||||
{Series: 131, Count: 70, Year: 1125, Month: 8, Day: 1},
|
0x301d80c6,
|
||||||
{Series: 132, Count: 71, Year: 1208, Month: 8, Day: 13},
|
0x307086c7,
|
||||||
{Series: 133, Count: 72, Year: 1219, Month: 7, Day: 13},
|
0x307b76c8,
|
||||||
{Series: 134, Count: 71, Year: 1248, Month: 6, Day: 22},
|
0x30986b47,
|
||||||
{Series: 135, Count: 71, Year: 1331, Month: 7, Day: 5},
|
0x30eb72c7,
|
||||||
{Series: 136, Count: 71, Year: 1360, Month: 6, Day: 14},
|
0x31086747,
|
||||||
{Series: 137, Count: 70, Year: 1389, Month: 5, Day: 25},
|
0x31255cc6,
|
||||||
{Series: 138, Count: 70, Year: 1472, Month: 6, Day: 6},
|
0x31786346,
|
||||||
{Series: 139, Count: 71, Year: 1501, Month: 5, Day: 17},
|
0x319558c7,
|
||||||
{Series: 140, Count: 71, Year: 1512, Month: 4, Day: 16},
|
0x31a04847,
|
||||||
{Series: 141, Count: 70, Year: 1613, Month: 5, Day: 19},
|
0x320559c6,
|
||||||
{Series: 142, Count: 72, Year: 1624, Month: 4, Day: 17},
|
0x321048c8,
|
||||||
{Series: 143, Count: 72, Year: 1617, Month: 3, Day: 7},
|
0x320933c8,
|
||||||
{Series: 144, Count: 70, Year: 1736, Month: 4, Day: 11},
|
0x328045c6,
|
||||||
{Series: 145, Count: 77, Year: 1639, Month: 1, Day: 4},
|
0x321f124d,
|
||||||
{Series: 146, Count: 76, Year: 1541, Month: 9, Day: 19},
|
0x31bd99cc,
|
||||||
{Series: 147, Count: 80, Year: 1624, Month: 10, Day: 12},
|
0x3210a650,
|
||||||
{Series: 148, Count: 75, Year: 1653, Month: 9, Day: 21},
|
0x322d9acb,
|
||||||
{Series: 149, Count: 71, Year: 1664, Month: 8, Day: 21},
|
0x32388ac7,
|
||||||
{Series: 150, Count: 71, Year: 1729, Month: 8, Day: 24},
|
0x32798c47,
|
||||||
{Series: 151, Count: 72, Year: 1776, Month: 8, Day: 14},
|
0x32a88748,
|
||||||
{Series: 152, Count: 70, Year: 1805, Month: 7, Day: 26},
|
0x32c57d46,
|
||||||
{Series: 153, Count: 70, Year: 1870, Month: 7, Day: 28},
|
0x33067e46,
|
||||||
{Series: 154, Count: 71, Year: 1917, Month: 7, Day: 19},
|
0x333579c7,
|
||||||
{Series: 155, Count: 71, Year: 1928, Month: 6, Day: 17},
|
0x334068c7,
|
||||||
{Series: 156, Count: 69, Year: 2011, Month: 7, Day: 1},
|
0x339370c5,
|
||||||
{Series: 157, Count: 70, Year: 2058, Month: 6, Day: 21},
|
0x33c26ac6,
|
||||||
{Series: 158, Count: 70, Year: 2069, Month: 5, Day: 20},
|
0x33cd5a46,
|
||||||
{Series: 159, Count: 70, Year: 2134, Month: 5, Day: 23},
|
0x340e5bc6,
|
||||||
{Series: 160, Count: 71, Year: 2181, Month: 5, Day: 13},
|
0x343d56c7,
|
||||||
{Series: 161, Count: 72, Year: 2174, Month: 4, Day: 1},
|
0x343640c8,
|
||||||
{Series: 162, Count: 70, Year: 2257, Month: 4, Day: 15},
|
0x348947c6,
|
||||||
{Series: 163, Count: 72, Year: 2286, Month: 3, Day: 25},
|
0x34a63cc8,
|
||||||
{Series: 164, Count: 80, Year: 2098, Month: 10, Day: 24},
|
0x33eaac50,
|
||||||
{Series: 165, Count: 72, Year: 2145, Month: 10, Day: 16},
|
0x3419a848,
|
||||||
{Series: 166, Count: 77, Year: 2228, Month: 10, Day: 29},
|
0x346caecd,
|
||||||
{Series: 167, Count: 72, Year: 2203, Month: 9, Day: 6},
|
0x34539348,
|
||||||
{Series: 168, Count: 70, Year: 2250, Month: 8, Day: 28},
|
0x34828e46,
|
||||||
{Series: 169, Count: 71, Year: 2333, Month: 9, Day: 10},
|
0x34d59547,
|
||||||
{Series: 170, Count: 71, Year: 2344, Month: 8, Day: 9},
|
0x34e084c7,
|
||||||
{Series: 171, Count: 69, Year: 2391, Month: 8, Day: 1},
|
0x350f80c5,
|
||||||
{Series: 172, Count: 70, Year: 2474, Month: 8, Day: 13},
|
0x356286c6,
|
||||||
{Series: 173, Count: 70, Year: 2485, Month: 7, Day: 12},
|
0x356d7646,
|
||||||
{Series: 174, Count: 69, Year: 2532, Month: 7, Day: 4},
|
0x359c7245,
|
||||||
{Series: 175, Count: 70, Year: 2597, Month: 7, Day: 5},
|
0x35dd72c6,
|
||||||
{Series: 176, Count: 71, Year: 2608, Month: 6, Day: 4},
|
0x35e86247,
|
||||||
{Series: 177, Count: 69, Year: 2655, Month: 5, Day: 27},
|
0x36175dc5,
|
||||||
{Series: 178, Count: 70, Year: 2738, Month: 6, Day: 9},
|
0x366a64c6,
|
||||||
{Series: 179, Count: 71, Year: 2731, Month: 4, Day: 28},
|
0x36634e47,
|
||||||
{Series: 180, Count: 70, Year: 2760, Month: 4, Day: 8},
|
0x36804446,
|
||||||
}
|
}
|
||||||
|
|||||||
+13
-17
@@ -117,10 +117,10 @@ func TestSolarPathAndFootprintsCarrySaros(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestSarosAnchorSanity(t *testing.T) {
|
func TestSarosAnchorSanity(t *testing.T) {
|
||||||
assertSarosAnchorTable(t, solarSarosAnchors[:], true)
|
assertSarosAnchorTable(t, solarSarosAnchors[:], 0)
|
||||||
assertSarosAnchorTable(t, lunarSarosAnchors[:], false)
|
assertSarosAnchorTable(t, lunarSarosAnchors[:], 1)
|
||||||
assertSarosHeadOverrides(t, solarSarosHeadOverrides[:], solarSarosAnchors[:])
|
assertSarosHeadOverrides(t, solarSarosHeadOverrides[:], solarSarosAnchors[:], 0)
|
||||||
assertSarosHeadOverrides(t, lunarSarosHeadOverrides[:], lunarSarosAnchors[:])
|
assertSarosHeadOverrides(t, lunarSarosHeadOverrides[:], lunarSarosAnchors[:], 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
func assertSarosInfo(t *testing.T, has bool, got SarosInfo, wantSeries, wantMember, wantCount int) {
|
func assertSarosInfo(t *testing.T, has bool, got SarosInfo, wantSeries, wantMember, wantCount int) {
|
||||||
@@ -141,14 +141,15 @@ func assertSarosInfo(t *testing.T, has bool, got SarosInfo, wantSeries, wantMemb
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func assertSarosAnchorTable(t *testing.T, anchors []sarosAnchor, solar bool) {
|
func assertSarosAnchorTable(t *testing.T, anchors []sarosMagic, seriesBase int) {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
if len(anchors) == 0 {
|
if len(anchors) == 0 {
|
||||||
t.Fatal("expected non-empty Saros anchor table")
|
t.Fatal("expected non-empty Saros anchor table")
|
||||||
}
|
}
|
||||||
seenDates := make(map[[3]int]int, len(anchors))
|
seenDates := make(map[[3]int]int, len(anchors))
|
||||||
lastSeries := int(anchors[0].Series) - 1
|
lastSeries := seriesBase - 1
|
||||||
for _, anchor := range anchors {
|
for index, magic := range anchors {
|
||||||
|
anchor := decodeSarosMagic(magic, seriesBase+index)
|
||||||
series := int(anchor.Series)
|
series := int(anchor.Series)
|
||||||
if series <= lastSeries {
|
if series <= lastSeries {
|
||||||
t.Fatalf("series not strictly increasing: prev=%d current=%d", lastSeries, series)
|
t.Fatalf("series not strictly increasing: prev=%d current=%d", lastSeries, series)
|
||||||
@@ -163,25 +164,20 @@ func assertSarosAnchorTable(t *testing.T, anchors []sarosAnchor, solar bool) {
|
|||||||
}
|
}
|
||||||
seenDates[dateKey] = series
|
seenDates[dateKey] = series
|
||||||
}
|
}
|
||||||
if solar {
|
if got := int(decodeSarosMagic(anchors[0], seriesBase).Series); got != seriesBase {
|
||||||
if got := int(anchors[0].Series); got != 0 {
|
t.Fatalf("unexpected first series: got %d want %d", got, seriesBase)
|
||||||
t.Fatalf("unexpected first solar series: got %d want 0", got)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if got := int(anchors[0].Series); got != 1 {
|
|
||||||
t.Fatalf("unexpected first lunar series: got %d want 1", got)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func assertSarosHeadOverrides(t *testing.T, overrides []sarosHeadOverride, anchors []sarosAnchor) {
|
func assertSarosHeadOverrides(t *testing.T, overrides []sarosHeadOverride, anchors []sarosMagic, seriesBase int) {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
if len(overrides) == 0 {
|
if len(overrides) == 0 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
seenHeads := make(map[[3]int]int, len(overrides))
|
seenHeads := make(map[[3]int]int, len(overrides))
|
||||||
anchorSeries := make(map[int]int, len(anchors))
|
anchorSeries := make(map[int]int, len(anchors))
|
||||||
for _, anchor := range anchors {
|
for index, magic := range anchors {
|
||||||
|
anchor := decodeSarosMagic(magic, seriesBase+index)
|
||||||
anchorSeries[int(anchor.Series)] = int(anchor.Count)
|
anchorSeries[int(anchor.Series)] = int(anchor.Count)
|
||||||
}
|
}
|
||||||
for _, override := range overrides {
|
for _, override := range overrides {
|
||||||
|
|||||||
@@ -0,0 +1,36 @@
|
|||||||
|
package eclipse
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math"
|
||||||
|
|
||||||
|
"b612.me/astro/basic"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
eclipseSeasonNodeDistanceLimitDeg = 35.0
|
||||||
|
eclipseSeasonMaxSearchStep = 4
|
||||||
|
)
|
||||||
|
|
||||||
|
func nextEclipseSearchCandidateTT(candidateTT float64, phaseType, direction int, synodicMonthDays float64) float64 {
|
||||||
|
step := eclipseSearchStep(candidateTT, direction, synodicMonthDays)
|
||||||
|
return basic.CalcMoonSHByJDE(candidateTT+float64(direction*step)*synodicMonthDays, phaseType)
|
||||||
|
}
|
||||||
|
|
||||||
|
func eclipseSearchStep(candidateTT float64, direction int, synodicMonthDays float64) int {
|
||||||
|
step := 1
|
||||||
|
for nextStep := 2; nextStep <= eclipseSeasonMaxSearchStep; nextStep++ {
|
||||||
|
skippedTT := candidateTT + float64(direction*(nextStep-1))*synodicMonthDays
|
||||||
|
if eclipseNodeDistance(skippedTT) < eclipseSeasonNodeDistanceLimitDeg {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
step = nextStep
|
||||||
|
}
|
||||||
|
return step
|
||||||
|
}
|
||||||
|
|
||||||
|
func eclipseNodeDistance(ttJDE float64) float64 {
|
||||||
|
argument := normalizeDegree360(basic.MoonLonX(ttJDE))
|
||||||
|
toAscending := math.Min(argument, 360-argument)
|
||||||
|
toDescending := math.Abs(argument - 180)
|
||||||
|
return math.Min(toAscending, toDescending)
|
||||||
|
}
|
||||||
@@ -0,0 +1,73 @@
|
|||||||
|
package eclipse
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"b612.me/astro/basic"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestEclipseSearchStepDoesNotSkipPotentialCandidates(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
phaseType int
|
||||||
|
synodicMonthDays float64
|
||||||
|
potential func(float64) bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "solar",
|
||||||
|
phaseType: 0,
|
||||||
|
synodicMonthDays: solarEclipseSynodicMonthDays,
|
||||||
|
potential: isPotentialSolarEclipse,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "lunar",
|
||||||
|
phaseType: 1,
|
||||||
|
synodicMonthDays: lunarEclipseSynodicMonthDays,
|
||||||
|
potential: isPotentialLunarEclipse,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
candidates := eclipseSearchTestCandidates(1600, 800, tc.phaseType, tc.synodicMonthDays)
|
||||||
|
|
||||||
|
for index, candidateTT := range candidates {
|
||||||
|
for _, direction := range []int{-1, 1} {
|
||||||
|
step := eclipseSearchStep(candidateTT, direction, tc.synodicMonthDays)
|
||||||
|
for offset := 1; offset < step; offset++ {
|
||||||
|
skippedIndex := index + direction*offset
|
||||||
|
if skippedIndex < 0 || skippedIndex >= len(candidates) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if tc.potential(candidates[skippedIndex]) {
|
||||||
|
t.Fatalf(
|
||||||
|
"%s skip crosses potential candidate: index=%d direction=%d step=%d offset=%d jd=%.8f",
|
||||||
|
tc.name,
|
||||||
|
index,
|
||||||
|
direction,
|
||||||
|
step,
|
||||||
|
offset,
|
||||||
|
candidates[skippedIndex],
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func eclipseSearchTestCandidates(startYear, years, phaseType int, synodicMonthDays float64) []float64 {
|
||||||
|
startTT := basic.Date2JDE(time.Date(startYear, 1, 1, 0, 0, 0, 0, time.UTC))
|
||||||
|
endTT := basic.Date2JDE(time.Date(startYear+years, 1, 1, 0, 0, 0, 0, time.UTC))
|
||||||
|
candidateTT := basic.CalcMoonSHByJDE(startTT, phaseType)
|
||||||
|
candidates := make([]float64, 0, years*13)
|
||||||
|
for candidateTT < endTT {
|
||||||
|
if candidateTT >= startTT {
|
||||||
|
candidates = append(candidates, candidateTT)
|
||||||
|
}
|
||||||
|
candidateTT = basic.CalcMoonSHByJDE(candidateTT+synodicMonthDays, phaseType)
|
||||||
|
}
|
||||||
|
return candidates
|
||||||
|
}
|
||||||
+8
-1
@@ -11,6 +11,7 @@ const (
|
|||||||
solarEclipseSynodicMonthDays = 29.530588853
|
solarEclipseSynodicMonthDays = 29.530588853
|
||||||
solarEclipseSearchLimit = 36
|
solarEclipseSearchLimit = 36
|
||||||
solarEclipseSearchEpsilonDay = 1e-8
|
solarEclipseSearchEpsilonDay = 1e-8
|
||||||
|
solarEclipseLatitudeLimitDeg = 2.0
|
||||||
)
|
)
|
||||||
|
|
||||||
type solarEclipseCalculator func(float64) basic.SolarEclipseResult
|
type solarEclipseCalculator func(float64) basic.SolarEclipseResult
|
||||||
@@ -236,16 +237,22 @@ func searchSolarEclipse(
|
|||||||
candidateTT := basic.CalcMoonSHByJDE(targetTT, 0)
|
candidateTT := basic.CalcMoonSHByJDE(targetTT, 0)
|
||||||
|
|
||||||
for i := 0; i < solarEclipseSearchLimit; i++ {
|
for i := 0; i < solarEclipseSearchLimit; i++ {
|
||||||
|
if isPotentialSolarEclipse(candidateTT) {
|
||||||
result := calculator(candidateTT)
|
result := calculator(candidateTT)
|
||||||
if result.Type != basic.SolarEclipseNone && solarEclipseMatchesDirection(result.GreatestEclipse, targetTT, direction, includeCurrent) {
|
if result.Type != basic.SolarEclipseNone && solarEclipseMatchesDirection(result.GreatestEclipse, targetTT, direction, includeCurrent) {
|
||||||
return solarEclipseInfoFromBasic(result, date.Location()), true
|
return solarEclipseInfoFromBasic(result, date.Location()), true
|
||||||
}
|
}
|
||||||
candidateTT = basic.CalcMoonSHByJDE(candidateTT+float64(direction)*solarEclipseSynodicMonthDays, 0)
|
}
|
||||||
|
candidateTT = nextEclipseSearchCandidateTT(candidateTT, 0, direction, solarEclipseSynodicMonthDays)
|
||||||
}
|
}
|
||||||
|
|
||||||
return SolarEclipseInfo{}, false
|
return SolarEclipseInfo{}, false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func isPotentialSolarEclipse(newMoonTT float64) bool {
|
||||||
|
return math.Abs(basic.HMoonTrueBo(newMoonTT)) <= solarEclipseLatitudeLimitDeg
|
||||||
|
}
|
||||||
|
|
||||||
func solarEclipseMatchesDirection(greatestTT, targetTT float64, direction int, includeCurrent bool) bool {
|
func solarEclipseMatchesDirection(greatestTT, targetTT float64, direction int, includeCurrent bool) bool {
|
||||||
delta := greatestTT - targetTT
|
delta := greatestTT - targetTT
|
||||||
if math.Abs(delta) <= solarEclipseSearchEpsilonDay {
|
if math.Abs(delta) <= solarEclipseSearchEpsilonDay {
|
||||||
|
|||||||
+126
-6
@@ -217,6 +217,18 @@ func LastGeometricLocalSolarEclipseIAUSingleK(date time.Time, lon, lat, height f
|
|||||||
return info
|
return info
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// LastLocalTotalSolarEclipse 上次站心日全食 / previous local total solar eclipse.
|
||||||
|
// Previous visible local total solar eclipse, using NASA bulletin Split-K by default.
|
||||||
|
func LastLocalTotalSolarEclipse(date time.Time, lon, lat, height float64) (LocalSolarEclipseInfo, bool) {
|
||||||
|
return searchLocalTotalSolarEclipse(date, lon, lat, height, -1, true, localSolarEclipseNASABulletinSplitK, localSolarEclipseQueryVisible)
|
||||||
|
}
|
||||||
|
|
||||||
|
// LastLocalAnnularSolarEclipse 上次站心日环食 / previous local annular solar eclipse.
|
||||||
|
// Previous visible local annular solar eclipse, using NASA bulletin Split-K by default.
|
||||||
|
func LastLocalAnnularSolarEclipse(date time.Time, lon, lat, height float64) (LocalSolarEclipseInfo, bool) {
|
||||||
|
return searchLocalAnnularSolarEclipse(date, lon, lat, height, -1, true, localSolarEclipseNASABulletinSplitK, localSolarEclipseQueryVisible)
|
||||||
|
}
|
||||||
|
|
||||||
// NextLocalSolarEclipse 下次站心日食 / next local solar eclipse.
|
// NextLocalSolarEclipse 下次站心日食 / next local solar eclipse.
|
||||||
// Next visible local solar eclipse, using NASA bulletin Split-K by default.
|
// Next visible local solar eclipse, using NASA bulletin Split-K by default.
|
||||||
func NextLocalSolarEclipse(date time.Time, lon, lat, height float64) LocalSolarEclipseInfo {
|
func NextLocalSolarEclipse(date time.Time, lon, lat, height float64) LocalSolarEclipseInfo {
|
||||||
@@ -257,6 +269,18 @@ func NextGeometricLocalSolarEclipseIAUSingleK(date time.Time, lon, lat, height f
|
|||||||
return info
|
return info
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NextLocalTotalSolarEclipse 下次站心日全食 / next local total solar eclipse.
|
||||||
|
// Next visible local total solar eclipse, using NASA bulletin Split-K by default.
|
||||||
|
func NextLocalTotalSolarEclipse(date time.Time, lon, lat, height float64) (LocalSolarEclipseInfo, bool) {
|
||||||
|
return searchLocalTotalSolarEclipse(date, lon, lat, height, 1, false, localSolarEclipseNASABulletinSplitK, localSolarEclipseQueryVisible)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NextLocalAnnularSolarEclipse 下次站心日环食 / next local annular solar eclipse.
|
||||||
|
// Next visible local annular solar eclipse, using NASA bulletin Split-K by default.
|
||||||
|
func NextLocalAnnularSolarEclipse(date time.Time, lon, lat, height float64) (LocalSolarEclipseInfo, bool) {
|
||||||
|
return searchLocalAnnularSolarEclipse(date, lon, lat, height, 1, false, localSolarEclipseNASABulletinSplitK, localSolarEclipseQueryVisible)
|
||||||
|
}
|
||||||
|
|
||||||
// ClosestLocalSolarEclipse 最近一次站心日食 / closest local solar eclipse.
|
// ClosestLocalSolarEclipse 最近一次站心日食 / closest local solar eclipse.
|
||||||
// Closest visible local solar eclipse, using NASA bulletin Split-K by default.
|
// Closest visible local solar eclipse, using NASA bulletin Split-K by default.
|
||||||
func ClosestLocalSolarEclipse(date time.Time, lon, lat, height float64) LocalSolarEclipseInfo {
|
func ClosestLocalSolarEclipse(date time.Time, lon, lat, height float64) LocalSolarEclipseInfo {
|
||||||
@@ -301,6 +325,22 @@ func ClosestGeometricLocalSolarEclipseIAUSingleK(date time.Time, lon, lat, heigh
|
|||||||
return closestLocalSolarEclipse(date, last, hasLast, next, hasNext)
|
return closestLocalSolarEclipse(date, last, hasLast, next, hasNext)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ClosestLocalTotalSolarEclipse 最近一次站心日全食 / closest local total solar eclipse.
|
||||||
|
// Closest visible local total solar eclipse, using NASA bulletin Split-K by default.
|
||||||
|
func ClosestLocalTotalSolarEclipse(date time.Time, lon, lat, height float64) (LocalSolarEclipseInfo, bool) {
|
||||||
|
last, hasLast := searchLocalTotalSolarEclipse(date, lon, lat, height, -1, true, localSolarEclipseNASABulletinSplitK, localSolarEclipseQueryVisible)
|
||||||
|
next, hasNext := searchLocalTotalSolarEclipse(date, lon, lat, height, 1, false, localSolarEclipseNASABulletinSplitK, localSolarEclipseQueryVisible)
|
||||||
|
return closestLocalSolarEclipseResult(date, last, hasLast, next, hasNext)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClosestLocalAnnularSolarEclipse 最近一次站心日环食 / closest local annular solar eclipse.
|
||||||
|
// Closest visible local annular solar eclipse, using NASA bulletin Split-K by default.
|
||||||
|
func ClosestLocalAnnularSolarEclipse(date time.Time, lon, lat, height float64) (LocalSolarEclipseInfo, bool) {
|
||||||
|
last, hasLast := searchLocalAnnularSolarEclipse(date, lon, lat, height, -1, true, localSolarEclipseNASABulletinSplitK, localSolarEclipseQueryVisible)
|
||||||
|
next, hasNext := searchLocalAnnularSolarEclipse(date, lon, lat, height, 1, false, localSolarEclipseNASABulletinSplitK, localSolarEclipseQueryVisible)
|
||||||
|
return closestLocalSolarEclipseResult(date, last, hasLast, next, hasNext)
|
||||||
|
}
|
||||||
|
|
||||||
func closestLocalSolarEclipse(
|
func closestLocalSolarEclipse(
|
||||||
date time.Time,
|
date time.Time,
|
||||||
last LocalSolarEclipseInfo,
|
last LocalSolarEclipseInfo,
|
||||||
@@ -308,21 +348,32 @@ func closestLocalSolarEclipse(
|
|||||||
next LocalSolarEclipseInfo,
|
next LocalSolarEclipseInfo,
|
||||||
hasNext bool,
|
hasNext bool,
|
||||||
) LocalSolarEclipseInfo {
|
) LocalSolarEclipseInfo {
|
||||||
|
info, _ := closestLocalSolarEclipseResult(date, last, hasLast, next, hasNext)
|
||||||
|
return info
|
||||||
|
}
|
||||||
|
|
||||||
|
func closestLocalSolarEclipseResult(
|
||||||
|
date time.Time,
|
||||||
|
last LocalSolarEclipseInfo,
|
||||||
|
hasLast bool,
|
||||||
|
next LocalSolarEclipseInfo,
|
||||||
|
hasNext bool,
|
||||||
|
) (LocalSolarEclipseInfo, bool) {
|
||||||
switch {
|
switch {
|
||||||
case hasLast && !hasNext:
|
case hasLast && !hasNext:
|
||||||
return last
|
return last, true
|
||||||
case !hasLast && hasNext:
|
case !hasLast && hasNext:
|
||||||
return next
|
return next, true
|
||||||
case !hasLast && !hasNext:
|
case !hasLast && !hasNext:
|
||||||
return LocalSolarEclipseInfo{}
|
return LocalSolarEclipseInfo{}, false
|
||||||
}
|
}
|
||||||
|
|
||||||
lastDistance := math.Abs(date.Sub(last.GreatestEclipse).Seconds())
|
lastDistance := math.Abs(date.Sub(last.GreatestEclipse).Seconds())
|
||||||
nextDistance := math.Abs(next.GreatestEclipse.Sub(date).Seconds())
|
nextDistance := math.Abs(next.GreatestEclipse.Sub(date).Seconds())
|
||||||
if lastDistance <= nextDistance {
|
if lastDistance <= nextDistance {
|
||||||
return last
|
return last, true
|
||||||
}
|
}
|
||||||
return next
|
return next, true
|
||||||
}
|
}
|
||||||
|
|
||||||
func searchLocalSolarEclipse(
|
func searchLocalSolarEclipse(
|
||||||
@@ -350,7 +401,69 @@ func searchLocalSolarEclipse(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
candidateTT = basic.CalcMoonSHByJDE(candidateTT+float64(direction)*localSolarEclipseSynodicMonthDays, 0)
|
candidateTT = nextEclipseSearchCandidateTT(candidateTT, 0, direction, localSolarEclipseSynodicMonthDays)
|
||||||
|
}
|
||||||
|
|
||||||
|
return LocalSolarEclipseInfo{}, false
|
||||||
|
}
|
||||||
|
|
||||||
|
func searchLocalTotalSolarEclipse(
|
||||||
|
date time.Time,
|
||||||
|
lon, lat, height float64,
|
||||||
|
direction int,
|
||||||
|
includeCurrent bool,
|
||||||
|
calculator localSolarEclipseCalculator,
|
||||||
|
mode localSolarEclipseQueryMode,
|
||||||
|
) (LocalSolarEclipseInfo, bool) {
|
||||||
|
targetTT := solarEclipseTimeToTTJDE(date)
|
||||||
|
candidateTT := basic.CalcMoonSHByJDE(targetTT, 0)
|
||||||
|
|
||||||
|
for i := 0; i < localSolarEclipseSearchLimit; i++ {
|
||||||
|
if isPotentialLocalSolarEclipse(candidateTT) {
|
||||||
|
globalResult := calculator.global(candidateTT)
|
||||||
|
if globalResult.HasTotal || globalResult.HasHybrid {
|
||||||
|
result := calculator.local(globalResult.GreatestEclipse, lon, lat, height)
|
||||||
|
if result.HasTotal {
|
||||||
|
info := localSolarEclipseInfoFromBasic(result, lon, lat, height, date.Location())
|
||||||
|
if (mode != localSolarEclipseQueryVisible || localCentralSolarEclipseVisible(info)) &&
|
||||||
|
localSolarEclipseMatchesDirection(result.GreatestEclipse, targetTT, direction, includeCurrent) {
|
||||||
|
return info, true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
candidateTT = nextEclipseSearchCandidateTT(candidateTT, 0, direction, localSolarEclipseSynodicMonthDays)
|
||||||
|
}
|
||||||
|
|
||||||
|
return LocalSolarEclipseInfo{}, false
|
||||||
|
}
|
||||||
|
|
||||||
|
func searchLocalAnnularSolarEclipse(
|
||||||
|
date time.Time,
|
||||||
|
lon, lat, height float64,
|
||||||
|
direction int,
|
||||||
|
includeCurrent bool,
|
||||||
|
calculator localSolarEclipseCalculator,
|
||||||
|
mode localSolarEclipseQueryMode,
|
||||||
|
) (LocalSolarEclipseInfo, bool) {
|
||||||
|
targetTT := solarEclipseTimeToTTJDE(date)
|
||||||
|
candidateTT := basic.CalcMoonSHByJDE(targetTT, 0)
|
||||||
|
|
||||||
|
for i := 0; i < localSolarEclipseSearchLimit; i++ {
|
||||||
|
if isPotentialLocalSolarEclipse(candidateTT) {
|
||||||
|
globalResult := calculator.global(candidateTT)
|
||||||
|
if globalResult.HasAnnular || globalResult.HasHybrid {
|
||||||
|
result := calculator.local(globalResult.GreatestEclipse, lon, lat, height)
|
||||||
|
if result.HasAnnular && !result.HasTotal {
|
||||||
|
info := localSolarEclipseInfoFromBasic(result, lon, lat, height, date.Location())
|
||||||
|
if (mode != localSolarEclipseQueryVisible || localCentralSolarEclipseVisible(info)) &&
|
||||||
|
localSolarEclipseMatchesDirection(result.GreatestEclipse, targetTT, direction, includeCurrent) {
|
||||||
|
return info, true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
candidateTT = nextEclipseSearchCandidateTT(candidateTT, 0, direction, localSolarEclipseSynodicMonthDays)
|
||||||
}
|
}
|
||||||
|
|
||||||
return LocalSolarEclipseInfo{}, false
|
return LocalSolarEclipseInfo{}, false
|
||||||
@@ -501,6 +614,13 @@ func localSolarEclipseVisible(info LocalSolarEclipseInfo) bool {
|
|||||||
return localSolarEclipseVisibleDuring(info, eventStart, eventEnd)
|
return localSolarEclipseVisibleDuring(info, eventStart, eventEnd)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func localCentralSolarEclipseVisible(info LocalSolarEclipseInfo) bool {
|
||||||
|
if !info.HasCentral || info.CentralStart.IsZero() || info.CentralEnd.IsZero() {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return localSolarEclipseVisibleDuring(info, info.CentralStart, info.CentralEnd)
|
||||||
|
}
|
||||||
|
|
||||||
func localSolarEclipseVisibleOnDate(info LocalSolarEclipseInfo, dayStart, dayEnd time.Time) bool {
|
func localSolarEclipseVisibleOnDate(info LocalSolarEclipseInfo, dayStart, dayEnd time.Time) bool {
|
||||||
eventStart, eventEnd, ok := localSolarEclipseRange(info)
|
eventStart, eventEnd, ok := localSolarEclipseRange(info)
|
||||||
if !ok {
|
if !ok {
|
||||||
|
|||||||
@@ -129,6 +129,116 @@ func TestLocalSolarEclipseSearchSkipsInvisibleCurrentCandidate(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestLocalTotalSolarEclipseSearch(t *testing.T) {
|
||||||
|
loc := time.UTC
|
||||||
|
lon, lat, height := -104.1, 25.3, 0.0
|
||||||
|
date := time.Date(2024, 4, 7, 0, 0, 0, 0, loc)
|
||||||
|
|
||||||
|
next, ok := NextLocalTotalSolarEclipse(date, lon, lat, height)
|
||||||
|
if !ok {
|
||||||
|
t.Fatal("expected to find next local total solar eclipse")
|
||||||
|
}
|
||||||
|
if next.Type != SolarEclipseTotal || !next.HasTotal {
|
||||||
|
t.Fatalf("unexpected next total eclipse: %+v", next)
|
||||||
|
}
|
||||||
|
assertSolarTimeClose(t, "NextLocalTotalSolarEclipse", next.GreatestEclipse, time.Date(2024, 4, 8, 18, 17, 15, 0, loc), time.Minute)
|
||||||
|
assertSolarDurationClose(t, "NextLocalTotalSolarEclipse duration", next.CentralEnd.Sub(next.CentralStart), 4*time.Minute+28*time.Second, 5*time.Second)
|
||||||
|
|
||||||
|
last, ok := LastLocalTotalSolarEclipse(next.GreatestEclipse, lon, lat, height)
|
||||||
|
if !ok {
|
||||||
|
t.Fatal("expected to find previous local total solar eclipse")
|
||||||
|
}
|
||||||
|
if last.Type != SolarEclipseTotal || !last.HasTotal {
|
||||||
|
t.Fatalf("unexpected last total eclipse: %+v", last)
|
||||||
|
}
|
||||||
|
assertSolarTimeClose(t, "LastLocalTotalSolarEclipse", last.GreatestEclipse, next.GreatestEclipse, time.Second)
|
||||||
|
assertSolarDurationClose(t, "LastLocalTotalSolarEclipse duration", last.CentralEnd.Sub(last.CentralStart), 4*time.Minute+28*time.Second, 5*time.Second)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLocalTotalSolarEclipseClosest(t *testing.T) {
|
||||||
|
loc := time.UTC
|
||||||
|
lon, lat, height := -104.1, 25.3, 0.0
|
||||||
|
date := time.Date(2024, 4, 8, 12, 0, 0, 0, loc)
|
||||||
|
|
||||||
|
info, ok := ClosestLocalTotalSolarEclipse(date, lon, lat, height)
|
||||||
|
if !ok {
|
||||||
|
t.Fatal("expected to find closest local total solar eclipse")
|
||||||
|
}
|
||||||
|
if info.Type != SolarEclipseTotal || !info.HasTotal {
|
||||||
|
t.Fatalf("unexpected closest total eclipse: %+v", info)
|
||||||
|
}
|
||||||
|
assertSolarTimeClose(t, "ClosestLocalTotalSolarEclipse", info.GreatestEclipse, time.Date(2024, 4, 8, 18, 17, 15, 0, loc), time.Minute)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLocalAnnularSolarEclipseSearch(t *testing.T) {
|
||||||
|
loc := time.UTC
|
||||||
|
lon, lat, height := -114.5, -22.0, 0.0
|
||||||
|
date := time.Date(2024, 10, 1, 0, 0, 0, 0, loc)
|
||||||
|
|
||||||
|
next, ok := NextLocalAnnularSolarEclipse(date, lon, lat, height)
|
||||||
|
if !ok {
|
||||||
|
t.Fatal("expected to find next local annular solar eclipse")
|
||||||
|
}
|
||||||
|
if next.Type != SolarEclipseAnnular || !next.HasAnnular || next.HasTotal {
|
||||||
|
t.Fatalf("unexpected next annular eclipse: %+v", next)
|
||||||
|
}
|
||||||
|
assertSolarTimeClose(t, "NextLocalAnnularSolarEclipse", next.GreatestEclipse, time.Date(2024, 10, 2, 18, 44, 59, 0, loc), time.Minute)
|
||||||
|
assertSolarDurationClose(t, "NextLocalAnnularSolarEclipse duration", next.CentralEnd.Sub(next.CentralStart), 7*time.Minute+25*time.Second, 5*time.Second)
|
||||||
|
|
||||||
|
last, ok := LastLocalAnnularSolarEclipse(next.GreatestEclipse, lon, lat, height)
|
||||||
|
if !ok {
|
||||||
|
t.Fatal("expected to find previous local annular solar eclipse")
|
||||||
|
}
|
||||||
|
if last.Type != SolarEclipseAnnular || !last.HasAnnular || last.HasTotal {
|
||||||
|
t.Fatalf("unexpected last annular eclipse: %+v", last)
|
||||||
|
}
|
||||||
|
assertSolarTimeClose(t, "LastLocalAnnularSolarEclipse", last.GreatestEclipse, next.GreatestEclipse, time.Second)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLocalAnnularSolarEclipseClosest(t *testing.T) {
|
||||||
|
loc := time.UTC
|
||||||
|
lon, lat, height := -114.5, -22.0, 0.0
|
||||||
|
date := time.Date(2024, 10, 2, 12, 0, 0, 0, loc)
|
||||||
|
|
||||||
|
info, ok := ClosestLocalAnnularSolarEclipse(date, lon, lat, height)
|
||||||
|
if !ok {
|
||||||
|
t.Fatal("expected to find closest local annular solar eclipse")
|
||||||
|
}
|
||||||
|
if info.Type != SolarEclipseAnnular || !info.HasAnnular || info.HasTotal {
|
||||||
|
t.Fatalf("unexpected closest annular eclipse: %+v", info)
|
||||||
|
}
|
||||||
|
assertSolarTimeClose(t, "ClosestLocalAnnularSolarEclipse", info.GreatestEclipse, time.Date(2024, 10, 2, 18, 44, 59, 0, loc), time.Minute)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLocalCentralSolarEclipseVisibleRequiresCentralPhaseVisibility(t *testing.T) {
|
||||||
|
info := LocalSolarEclipseInfo{
|
||||||
|
Type: SolarEclipseTotal,
|
||||||
|
Longitude: 0,
|
||||||
|
Latitude: 0,
|
||||||
|
PartialStart: time.Date(2024, 1, 1, 9, 0, 0, 0, time.UTC),
|
||||||
|
PartialEnd: time.Date(2024, 1, 1, 15, 0, 0, 0, time.UTC),
|
||||||
|
CentralStart: time.Date(2024, 1, 1, 0, 0, 0, 0, time.UTC),
|
||||||
|
CentralEnd: time.Date(2024, 1, 1, 1, 0, 0, 0, time.UTC),
|
||||||
|
HasPartial: true,
|
||||||
|
HasCentral: true,
|
||||||
|
HasTotal: true,
|
||||||
|
VisibleAtGreatest: false,
|
||||||
|
}
|
||||||
|
if !localSolarEclipseVisible(info) {
|
||||||
|
t.Fatalf("expected partial phase to be visible")
|
||||||
|
}
|
||||||
|
if localCentralSolarEclipseVisible(info) {
|
||||||
|
t.Fatalf("expected central phase below horizon to be rejected")
|
||||||
|
}
|
||||||
|
|
||||||
|
info.Type = SolarEclipseAnnular
|
||||||
|
info.HasTotal = false
|
||||||
|
info.HasAnnular = true
|
||||||
|
if localCentralSolarEclipseVisible(info) {
|
||||||
|
t.Fatalf("expected annular central phase below horizon to be rejected")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestLocalSolarEclipseInfoKeepsLocation(t *testing.T) {
|
func TestLocalSolarEclipseInfoKeepsLocation(t *testing.T) {
|
||||||
loc := time.FixedZone("UTC+08", 8*3600)
|
loc := time.FixedZone("UTC+08", 8*3600)
|
||||||
lon, lat, height := -104.1, 25.3, 1234.0
|
lon, lat, height := -104.1, 25.3, 1234.0
|
||||||
@@ -354,3 +464,14 @@ func assertSameLocalSolarEclipse(t *testing.T, name string, got, want LocalSolar
|
|||||||
}
|
}
|
||||||
assertSolarTimeClose(t, name+".GreatestEclipse", got.GreatestEclipse, want.GreatestEclipse, tolerance)
|
assertSolarTimeClose(t, name+".GreatestEclipse", got.GreatestEclipse, want.GreatestEclipse, tolerance)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func assertSolarDurationClose(t *testing.T, name string, got, want, tolerance time.Duration) {
|
||||||
|
t.Helper()
|
||||||
|
diff := got - want
|
||||||
|
if diff < 0 {
|
||||||
|
diff = -diff
|
||||||
|
}
|
||||||
|
if diff > tolerance {
|
||||||
|
t.Fatalf("%s mismatch: got %v want %v diff=%v", name, got, want, diff)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,100 @@
|
|||||||
|
package astro_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"b612.me/astro/basic"
|
||||||
|
"b612.me/astro/jupiter"
|
||||||
|
"b612.me/astro/mars"
|
||||||
|
"b612.me/astro/mercury"
|
||||||
|
"b612.me/astro/neptune"
|
||||||
|
"b612.me/astro/saturn"
|
||||||
|
"b612.me/astro/uranus"
|
||||||
|
"b612.me/astro/venus"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestPublicPlanetEventBoundaryIncludesCurrent(t *testing.T) {
|
||||||
|
type eventFuncs struct {
|
||||||
|
last func(time.Time) time.Time
|
||||||
|
next func(time.Time) time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
cases := []struct {
|
||||||
|
name string
|
||||||
|
eventUT float64
|
||||||
|
funcs eventFuncs
|
||||||
|
}{
|
||||||
|
{name: "MercuryConjunction", eventUT: basic.NextMercuryConjunction(eventBoundaryTT(2026)), funcs: eventFuncs{last: mercury.LastConjunction, next: mercury.NextConjunction}},
|
||||||
|
{name: "MercuryInferior", eventUT: basic.NextMercuryInferiorConjunctionInclusive(eventBoundaryTT(2026)), funcs: eventFuncs{last: mercury.LastInferiorConjunction, next: mercury.NextInferiorConjunction}},
|
||||||
|
{name: "MercurySuperior", eventUT: basic.NextMercurySuperiorConjunctionInclusive(eventBoundaryTT(2026)), funcs: eventFuncs{last: mercury.LastSuperiorConjunction, next: mercury.NextSuperiorConjunction}},
|
||||||
|
{name: "MercuryRetrograde", eventUT: basic.NextMercuryRetrogradeInclusive(eventBoundaryTT(2026)), funcs: eventFuncs{last: mercury.LastRetrograde, next: mercury.NextRetrograde}},
|
||||||
|
{name: "MercuryP2R", eventUT: basic.NextMercuryProgradeToRetrogradeInclusive(eventBoundaryTT(2026)), funcs: eventFuncs{last: mercury.LastProgradeToRetrograde, next: mercury.NextProgradeToRetrograde}},
|
||||||
|
{name: "MercuryR2P", eventUT: basic.NextMercuryRetrogradeToProgradeInclusive(eventBoundaryTT(2026)), funcs: eventFuncs{last: mercury.LastRetrogradeToPrograde, next: mercury.NextRetrogradeToPrograde}},
|
||||||
|
{name: "MercuryGreatestElongation", eventUT: basic.NextMercuryGreatestElongationInclusive(eventBoundaryTT(2026)), funcs: eventFuncs{last: mercury.LastGreatestElongation, next: mercury.NextGreatestElongation}},
|
||||||
|
{name: "MercuryGreatestElongationEast", eventUT: basic.NextMercuryGreatestElongationEastInclusive(eventBoundaryTT(2026)), funcs: eventFuncs{last: mercury.LastGreatestElongationEast, next: mercury.NextGreatestElongationEast}},
|
||||||
|
{name: "MercuryGreatestElongationWest", eventUT: basic.NextMercuryGreatestElongationWestInclusive(eventBoundaryTT(2026)), funcs: eventFuncs{last: mercury.LastGreatestElongationWest, next: mercury.NextGreatestElongationWest}},
|
||||||
|
|
||||||
|
{name: "VenusConjunction", eventUT: basic.NextVenusConjunction(eventBoundaryTT(2026)), funcs: eventFuncs{last: venus.LastConjunction, next: venus.NextConjunction}},
|
||||||
|
{name: "VenusInferior", eventUT: basic.NextVenusInferiorConjunctionInclusive(eventBoundaryTT(2026)), funcs: eventFuncs{last: venus.LastInferiorConjunction, next: venus.NextInferiorConjunction}},
|
||||||
|
{name: "VenusSuperior", eventUT: basic.NextVenusSuperiorConjunctionInclusive(eventBoundaryTT(2026)), funcs: eventFuncs{last: venus.LastSuperiorConjunction, next: venus.NextSuperiorConjunction}},
|
||||||
|
{name: "VenusRetrograde", eventUT: basic.NextVenusRetrogradeInclusive(eventBoundaryTT(2026)), funcs: eventFuncs{last: venus.LastRetrograde, next: venus.NextRetrograde}},
|
||||||
|
{name: "VenusP2R", eventUT: basic.NextVenusProgradeToRetrogradeInclusive(eventBoundaryTT(2026)), funcs: eventFuncs{last: venus.LastProgradeToRetrograde, next: venus.NextProgradeToRetrograde}},
|
||||||
|
{name: "VenusR2P", eventUT: basic.NextVenusRetrogradeToProgradeInclusive(eventBoundaryTT(2026)), funcs: eventFuncs{last: venus.LastRetrogradeToPrograde, next: venus.NextRetrogradeToPrograde}},
|
||||||
|
{name: "VenusGreatestElongation", eventUT: basic.NextVenusGreatestElongationInclusive(eventBoundaryTT(2026)), funcs: eventFuncs{last: venus.LastGreatestElongation, next: venus.NextGreatestElongation}},
|
||||||
|
{name: "VenusGreatestElongationEast", eventUT: basic.NextVenusGreatestElongationEastInclusive(eventBoundaryTT(2026)), funcs: eventFuncs{last: venus.LastGreatestElongationEast, next: venus.NextGreatestElongationEast}},
|
||||||
|
{name: "VenusGreatestElongationWest", eventUT: basic.NextVenusGreatestElongationWestInclusive(eventBoundaryTT(2026)), funcs: eventFuncs{last: venus.LastGreatestElongationWest, next: venus.NextGreatestElongationWest}},
|
||||||
|
|
||||||
|
{name: "MarsConjunction", eventUT: basic.NextMarsConjunction(eventBoundaryTT(2026)), funcs: eventFuncs{last: mars.LastConjunction, next: mars.NextConjunction}},
|
||||||
|
{name: "MarsOpposition", eventUT: basic.NextMarsOpposition(eventBoundaryTT(2026)), funcs: eventFuncs{last: mars.LastOpposition, next: mars.NextOpposition}},
|
||||||
|
{name: "MarsP2R", eventUT: basic.NextMarsProgradeToRetrograde(eventBoundaryTT(2026)), funcs: eventFuncs{last: mars.LastProgradeToRetrograde, next: mars.NextProgradeToRetrograde}},
|
||||||
|
{name: "MarsR2P", eventUT: basic.NextMarsRetrogradeToPrograde(eventBoundaryTT(2025)), funcs: eventFuncs{last: mars.LastRetrogradeToPrograde, next: mars.NextRetrogradeToPrograde}},
|
||||||
|
{name: "MarsEasternQuadrature", eventUT: basic.NextMarsEasternQuadrature(eventBoundaryTT(2026)), funcs: eventFuncs{last: mars.LastEasternQuadrature, next: mars.NextEasternQuadrature}},
|
||||||
|
{name: "MarsWesternQuadrature", eventUT: basic.NextMarsWesternQuadrature(eventBoundaryTT(2026)), funcs: eventFuncs{last: mars.LastWesternQuadrature, next: mars.NextWesternQuadrature}},
|
||||||
|
|
||||||
|
{name: "JupiterConjunction", eventUT: basic.NextJupiterConjunction(eventBoundaryTT(2026)), funcs: eventFuncs{last: jupiter.LastConjunction, next: jupiter.NextConjunction}},
|
||||||
|
{name: "JupiterOpposition", eventUT: basic.NextJupiterOpposition(eventBoundaryTT(2026)), funcs: eventFuncs{last: jupiter.LastOpposition, next: jupiter.NextOpposition}},
|
||||||
|
{name: "JupiterP2R", eventUT: basic.NextJupiterProgradeToRetrograde(eventBoundaryTT(2026)), funcs: eventFuncs{last: jupiter.LastProgradeToRetrograde, next: jupiter.NextProgradeToRetrograde}},
|
||||||
|
{name: "JupiterR2P", eventUT: basic.NextJupiterRetrogradeToPrograde(eventBoundaryTT(2026)), funcs: eventFuncs{last: jupiter.LastRetrogradeToPrograde, next: jupiter.NextRetrogradeToPrograde}},
|
||||||
|
{name: "JupiterEasternQuadrature", eventUT: basic.NextJupiterEasternQuadrature(eventBoundaryTT(2026)), funcs: eventFuncs{last: jupiter.LastEasternQuadrature, next: jupiter.NextEasternQuadrature}},
|
||||||
|
{name: "JupiterWesternQuadrature", eventUT: basic.NextJupiterWesternQuadrature(eventBoundaryTT(2026)), funcs: eventFuncs{last: jupiter.LastWesternQuadrature, next: jupiter.NextWesternQuadrature}},
|
||||||
|
|
||||||
|
{name: "SaturnOpposition", eventUT: basic.NextSaturnOpposition(eventBoundaryTT(2025)), funcs: eventFuncs{last: saturn.LastOpposition, next: saturn.NextOpposition}},
|
||||||
|
{name: "SaturnP2R", eventUT: basic.NextSaturnProgradeToRetrograde(eventBoundaryTT(2025)), funcs: eventFuncs{last: saturn.LastProgradeToRetrograde, next: saturn.NextProgradeToRetrograde}},
|
||||||
|
{name: "SaturnR2P", eventUT: basic.NextSaturnRetrogradeToPrograde(eventBoundaryTT(2025)), funcs: eventFuncs{last: saturn.LastRetrogradeToPrograde, next: saturn.NextRetrogradeToPrograde}},
|
||||||
|
{name: "SaturnEasternQuadrature", eventUT: basic.NextSaturnEasternQuadrature(eventBoundaryTT(2025)), funcs: eventFuncs{last: saturn.LastEasternQuadrature, next: saturn.NextEasternQuadrature}},
|
||||||
|
{name: "SaturnWesternQuadrature", eventUT: basic.NextSaturnWesternQuadrature(eventBoundaryTT(2025)), funcs: eventFuncs{last: saturn.LastWesternQuadrature, next: saturn.NextWesternQuadrature}},
|
||||||
|
|
||||||
|
{name: "UranusOpposition", eventUT: basic.NextUranusOpposition(eventBoundaryTT(2025)), funcs: eventFuncs{last: uranus.LastOpposition, next: uranus.NextOpposition}},
|
||||||
|
{name: "UranusP2R", eventUT: basic.NextUranusProgradeToRetrograde(eventBoundaryTT(2025)), funcs: eventFuncs{last: uranus.LastProgradeToRetrograde, next: uranus.NextProgradeToRetrograde}},
|
||||||
|
{name: "UranusR2P", eventUT: basic.NextUranusRetrogradeToPrograde(eventBoundaryTT(2025)), funcs: eventFuncs{last: uranus.LastRetrogradeToPrograde, next: uranus.NextRetrogradeToPrograde}},
|
||||||
|
{name: "UranusEasternQuadrature", eventUT: basic.NextUranusEasternQuadrature(eventBoundaryTT(2025)), funcs: eventFuncs{last: uranus.LastEasternQuadrature, next: uranus.NextEasternQuadrature}},
|
||||||
|
{name: "UranusWesternQuadrature", eventUT: basic.NextUranusWesternQuadrature(eventBoundaryTT(2025)), funcs: eventFuncs{last: uranus.LastWesternQuadrature, next: uranus.NextWesternQuadrature}},
|
||||||
|
|
||||||
|
{name: "NeptuneOpposition", eventUT: basic.NextNeptuneOpposition(eventBoundaryTT(2026)), funcs: eventFuncs{last: neptune.LastOpposition, next: neptune.NextOpposition}},
|
||||||
|
{name: "NeptuneP2R", eventUT: basic.NextNeptuneProgradeToRetrograde(eventBoundaryTT(2026)), funcs: eventFuncs{last: neptune.LastProgradeToRetrograde, next: neptune.NextProgradeToRetrograde}},
|
||||||
|
{name: "NeptuneR2P", eventUT: basic.NextNeptuneRetrogradeToPrograde(eventBoundaryTT(2026)), funcs: eventFuncs{last: neptune.LastRetrogradeToPrograde, next: neptune.NextRetrogradeToPrograde}},
|
||||||
|
{name: "NeptuneEasternQuadrature", eventUT: basic.NextNeptuneEasternQuadrature(eventBoundaryTT(2026)), funcs: eventFuncs{last: neptune.LastEasternQuadrature, next: neptune.NextEasternQuadrature}},
|
||||||
|
{name: "NeptuneWesternQuadrature", eventUT: basic.NextNeptuneWesternQuadrature(eventBoundaryTT(2026)), funcs: eventFuncs{last: neptune.LastWesternQuadrature, next: neptune.NextWesternQuadrature}},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range cases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
eventTime := basic.JDE2DateByZone(tc.eventUT, time.UTC, false)
|
||||||
|
assertSameEventTime(t, "last", tc.funcs.last(eventTime), eventTime)
|
||||||
|
assertSameEventTime(t, "next", tc.funcs.next(eventTime), eventTime)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func eventBoundaryTT(year int) float64 {
|
||||||
|
return basic.TD2UT(basic.Date2JDE(time.Date(year, 1, 1, 0, 0, 0, 0, time.UTC)), true)
|
||||||
|
}
|
||||||
|
|
||||||
|
func assertSameEventTime(t *testing.T, name string, got, want time.Time) {
|
||||||
|
t.Helper()
|
||||||
|
if math.Abs(got.Sub(want).Seconds()) > 1.0 {
|
||||||
|
t.Fatalf("%s boundary mismatch: got %s want %s delta %.3fs", name, got, want, got.Sub(want).Seconds())
|
||||||
|
}
|
||||||
|
}
|
||||||
+12
-24
@@ -207,8 +207,7 @@ func SetTime(date time.Time, lon, lat, height float64, aero bool) (time.Time, er
|
|||||||
|
|
||||||
// LastConjunction 上一次合日 / previous conjunction with the Sun.
|
// LastConjunction 上一次合日 / previous conjunction with the Sun.
|
||||||
//
|
//
|
||||||
// 返回 date 之前最近一次与太阳的合日时刻,结果保持 date 的时区。
|
// 返回 date 当前或之前最近一次与太阳的合日时刻,结果保持 date 的时区。
|
||||||
// Returns the most recent conjunction with the Sun relative to date, keeping date's time zone.
|
|
||||||
func LastConjunction(date time.Time) time.Time {
|
func LastConjunction(date time.Time) time.Time {
|
||||||
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
|
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
|
||||||
return basic.JDE2DateByZone(basic.LastJupiterConjunction(jde), date.Location(), false)
|
return basic.JDE2DateByZone(basic.LastJupiterConjunction(jde), date.Location(), false)
|
||||||
@@ -216,8 +215,7 @@ func LastConjunction(date time.Time) time.Time {
|
|||||||
|
|
||||||
// NextConjunction 下一次合日 / next conjunction with the Sun.
|
// NextConjunction 下一次合日 / next conjunction with the Sun.
|
||||||
//
|
//
|
||||||
// 返回 date 之后最近一次与太阳的合日时刻,结果保持 date 的时区。
|
// 返回 date 当前或之后最近一次与太阳的合日时刻,结果保持 date 的时区。
|
||||||
// Returns the next conjunction with the Sun relative to date, keeping date's time zone.
|
|
||||||
func NextConjunction(date time.Time) time.Time {
|
func NextConjunction(date time.Time) time.Time {
|
||||||
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
|
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
|
||||||
return basic.JDE2DateByZone(basic.NextJupiterConjunction(jde), date.Location(), false)
|
return basic.JDE2DateByZone(basic.NextJupiterConjunction(jde), date.Location(), false)
|
||||||
@@ -225,8 +223,7 @@ func NextConjunction(date time.Time) time.Time {
|
|||||||
|
|
||||||
// LastOpposition 上一次冲日 / previous opposition.
|
// LastOpposition 上一次冲日 / previous opposition.
|
||||||
//
|
//
|
||||||
// 返回 date 之前最近一次冲日时刻,结果保持 date 的时区。
|
// 返回 date 当前或之前最近一次冲日时刻,结果保持 date 的时区。
|
||||||
// Returns the most recent opposition relative to date, keeping date's time zone.
|
|
||||||
func LastOpposition(date time.Time) time.Time {
|
func LastOpposition(date time.Time) time.Time {
|
||||||
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
|
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
|
||||||
return basic.JDE2DateByZone(basic.LastJupiterOpposition(jde), date.Location(), false)
|
return basic.JDE2DateByZone(basic.LastJupiterOpposition(jde), date.Location(), false)
|
||||||
@@ -234,8 +231,7 @@ func LastOpposition(date time.Time) time.Time {
|
|||||||
|
|
||||||
// NextOpposition 下一次冲日 / next opposition.
|
// NextOpposition 下一次冲日 / next opposition.
|
||||||
//
|
//
|
||||||
// 返回 date 之后最近一次冲日时刻,结果保持 date 的时区。
|
// 返回 date 当前或之后最近一次冲日时刻,结果保持 date 的时区。
|
||||||
// Returns the next opposition relative to date, keeping date's time zone.
|
|
||||||
func NextOpposition(date time.Time) time.Time {
|
func NextOpposition(date time.Time) time.Time {
|
||||||
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
|
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
|
||||||
return basic.JDE2DateByZone(basic.NextJupiterOpposition(jde), date.Location(), false)
|
return basic.JDE2DateByZone(basic.NextJupiterOpposition(jde), date.Location(), false)
|
||||||
@@ -243,8 +239,7 @@ func NextOpposition(date time.Time) time.Time {
|
|||||||
|
|
||||||
// LastProgradeToRetrograde 上一次顺行转逆行留 / previous station from prograde to retrograde.
|
// LastProgradeToRetrograde 上一次顺行转逆行留 / previous station from prograde to retrograde.
|
||||||
//
|
//
|
||||||
// 返回 date 之前最近一次由顺行转为逆行的留时刻,结果保持 date 的时区。
|
// 返回 date 当前或之前最近一次由顺行转为逆行的留时刻,结果保持 date 的时区。
|
||||||
// Returns the most recent stationary point where motion changes from prograde to retrograde relative to date, keeping date's time zone.
|
|
||||||
func LastProgradeToRetrograde(date time.Time) time.Time {
|
func LastProgradeToRetrograde(date time.Time) time.Time {
|
||||||
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
|
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
|
||||||
return basic.JDE2DateByZone(basic.LastJupiterProgradeToRetrograde(jde), date.Location(), false)
|
return basic.JDE2DateByZone(basic.LastJupiterProgradeToRetrograde(jde), date.Location(), false)
|
||||||
@@ -252,8 +247,7 @@ func LastProgradeToRetrograde(date time.Time) time.Time {
|
|||||||
|
|
||||||
// NextProgradeToRetrograde 下一次顺行转逆行留 / next station from prograde to retrograde.
|
// NextProgradeToRetrograde 下一次顺行转逆行留 / next station from prograde to retrograde.
|
||||||
//
|
//
|
||||||
// 返回 date 之后最近一次由顺行转为逆行的留时刻,结果保持 date 的时区。
|
// 返回 date 当前或之后最近一次由顺行转为逆行的留时刻,结果保持 date 的时区。
|
||||||
// Returns the next stationary point where motion changes from prograde to retrograde relative to date, keeping date's time zone.
|
|
||||||
func NextProgradeToRetrograde(date time.Time) time.Time {
|
func NextProgradeToRetrograde(date time.Time) time.Time {
|
||||||
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
|
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
|
||||||
return basic.JDE2DateByZone(basic.NextJupiterProgradeToRetrograde(jde), date.Location(), false)
|
return basic.JDE2DateByZone(basic.NextJupiterProgradeToRetrograde(jde), date.Location(), false)
|
||||||
@@ -261,8 +255,7 @@ func NextProgradeToRetrograde(date time.Time) time.Time {
|
|||||||
|
|
||||||
// LastRetrogradeToPrograde 上一次逆行转顺行留 / previous station from retrograde to prograde.
|
// LastRetrogradeToPrograde 上一次逆行转顺行留 / previous station from retrograde to prograde.
|
||||||
//
|
//
|
||||||
// 返回 date 之前最近一次由逆行转为顺行的留时刻,结果保持 date 的时区。
|
// 返回 date 当前或之前最近一次由逆行转为顺行的留时刻,结果保持 date 的时区。
|
||||||
// Returns the most recent stationary point where motion changes from retrograde to prograde relative to date, keeping date's time zone.
|
|
||||||
func LastRetrogradeToPrograde(date time.Time) time.Time {
|
func LastRetrogradeToPrograde(date time.Time) time.Time {
|
||||||
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
|
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
|
||||||
return basic.JDE2DateByZone(basic.LastJupiterRetrogradeToPrograde(jde), date.Location(), false)
|
return basic.JDE2DateByZone(basic.LastJupiterRetrogradeToPrograde(jde), date.Location(), false)
|
||||||
@@ -270,8 +263,7 @@ func LastRetrogradeToPrograde(date time.Time) time.Time {
|
|||||||
|
|
||||||
// NextRetrogradeToPrograde 下一次逆行转顺行留 / next station from retrograde to prograde.
|
// NextRetrogradeToPrograde 下一次逆行转顺行留 / next station from retrograde to prograde.
|
||||||
//
|
//
|
||||||
// 返回 date 之后最近一次由逆行转为顺行的留时刻,结果保持 date 的时区。
|
// 返回 date 当前或之后最近一次由逆行转为顺行的留时刻,结果保持 date 的时区。
|
||||||
// Returns the next stationary point where motion changes from retrograde to prograde relative to date, keeping date's time zone.
|
|
||||||
func NextRetrogradeToPrograde(date time.Time) time.Time {
|
func NextRetrogradeToPrograde(date time.Time) time.Time {
|
||||||
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
|
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
|
||||||
return basic.JDE2DateByZone(basic.NextJupiterRetrogradeToPrograde(jde), date.Location(), false)
|
return basic.JDE2DateByZone(basic.NextJupiterRetrogradeToPrograde(jde), date.Location(), false)
|
||||||
@@ -279,8 +271,7 @@ func NextRetrogradeToPrograde(date time.Time) time.Time {
|
|||||||
|
|
||||||
// LastEasternQuadrature 上一次东方照 / previous eastern quadrature.
|
// LastEasternQuadrature 上一次东方照 / previous eastern quadrature.
|
||||||
//
|
//
|
||||||
// 返回 date 之前最近一次东方照时刻,结果保持 date 的时区。
|
// 返回 date 当前或之前最近一次东方照时刻,结果保持 date 的时区。
|
||||||
// Returns the most recent eastern quadrature relative to date, keeping date's time zone.
|
|
||||||
func LastEasternQuadrature(date time.Time) time.Time {
|
func LastEasternQuadrature(date time.Time) time.Time {
|
||||||
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
|
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
|
||||||
return basic.JDE2DateByZone(basic.LastJupiterEasternQuadrature(jde), date.Location(), false)
|
return basic.JDE2DateByZone(basic.LastJupiterEasternQuadrature(jde), date.Location(), false)
|
||||||
@@ -288,8 +279,7 @@ func LastEasternQuadrature(date time.Time) time.Time {
|
|||||||
|
|
||||||
// NextEasternQuadrature 下一次东方照 / next eastern quadrature.
|
// NextEasternQuadrature 下一次东方照 / next eastern quadrature.
|
||||||
//
|
//
|
||||||
// 返回 date 之后最近一次东方照时刻,结果保持 date 的时区。
|
// 返回 date 当前或之后最近一次东方照时刻,结果保持 date 的时区。
|
||||||
// Returns the next eastern quadrature relative to date, keeping date's time zone.
|
|
||||||
func NextEasternQuadrature(date time.Time) time.Time {
|
func NextEasternQuadrature(date time.Time) time.Time {
|
||||||
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
|
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
|
||||||
return basic.JDE2DateByZone(basic.NextJupiterEasternQuadrature(jde), date.Location(), false)
|
return basic.JDE2DateByZone(basic.NextJupiterEasternQuadrature(jde), date.Location(), false)
|
||||||
@@ -297,8 +287,7 @@ func NextEasternQuadrature(date time.Time) time.Time {
|
|||||||
|
|
||||||
// LastWesternQuadrature 上一次西方照 / previous western quadrature.
|
// LastWesternQuadrature 上一次西方照 / previous western quadrature.
|
||||||
//
|
//
|
||||||
// 返回 date 之前最近一次西方照时刻,结果保持 date 的时区。
|
// 返回 date 当前或之前最近一次西方照时刻,结果保持 date 的时区。
|
||||||
// Returns the most recent western quadrature relative to date, keeping date's time zone.
|
|
||||||
func LastWesternQuadrature(date time.Time) time.Time {
|
func LastWesternQuadrature(date time.Time) time.Time {
|
||||||
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
|
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
|
||||||
return basic.JDE2DateByZone(basic.LastJupiterWesternQuadrature(jde), date.Location(), false)
|
return basic.JDE2DateByZone(basic.LastJupiterWesternQuadrature(jde), date.Location(), false)
|
||||||
@@ -306,8 +295,7 @@ func LastWesternQuadrature(date time.Time) time.Time {
|
|||||||
|
|
||||||
// NextWesternQuadrature 下一次西方照 / next western quadrature.
|
// NextWesternQuadrature 下一次西方照 / next western quadrature.
|
||||||
//
|
//
|
||||||
// 返回 date 之后最近一次西方照时刻,结果保持 date 的时区。
|
// 返回 date 当前或之后最近一次西方照时刻,结果保持 date 的时区。
|
||||||
// Returns the next western quadrature relative to date, keeping date's time zone.
|
|
||||||
func NextWesternQuadrature(date time.Time) time.Time {
|
func NextWesternQuadrature(date time.Time) time.Time {
|
||||||
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
|
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
|
||||||
return basic.JDE2DateByZone(basic.NextJupiterWesternQuadrature(jde), date.Location(), false)
|
return basic.JDE2DateByZone(basic.NextJupiterWesternQuadrature(jde), date.Location(), false)
|
||||||
|
|||||||
+12
-24
@@ -207,8 +207,7 @@ func SetTime(date time.Time, lon, lat, height float64, aero bool) (time.Time, er
|
|||||||
|
|
||||||
// LastConjunction 上一次合日 / previous conjunction with the Sun.
|
// LastConjunction 上一次合日 / previous conjunction with the Sun.
|
||||||
//
|
//
|
||||||
// 返回 date 之前最近一次与太阳的合日时刻,结果保持 date 的时区。
|
// 返回 date 当前或之前最近一次与太阳的合日时刻,结果保持 date 的时区。
|
||||||
// Returns the most recent conjunction with the Sun relative to date, keeping date's time zone.
|
|
||||||
func LastConjunction(date time.Time) time.Time {
|
func LastConjunction(date time.Time) time.Time {
|
||||||
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
|
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
|
||||||
return basic.JDE2DateByZone(basic.LastMarsConjunction(jde), date.Location(), false)
|
return basic.JDE2DateByZone(basic.LastMarsConjunction(jde), date.Location(), false)
|
||||||
@@ -216,8 +215,7 @@ func LastConjunction(date time.Time) time.Time {
|
|||||||
|
|
||||||
// NextConjunction 下一次合日 / next conjunction with the Sun.
|
// NextConjunction 下一次合日 / next conjunction with the Sun.
|
||||||
//
|
//
|
||||||
// 返回 date 之后最近一次与太阳的合日时刻,结果保持 date 的时区。
|
// 返回 date 当前或之后最近一次与太阳的合日时刻,结果保持 date 的时区。
|
||||||
// Returns the next conjunction with the Sun relative to date, keeping date's time zone.
|
|
||||||
func NextConjunction(date time.Time) time.Time {
|
func NextConjunction(date time.Time) time.Time {
|
||||||
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
|
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
|
||||||
return basic.JDE2DateByZone(basic.NextMarsConjunction(jde), date.Location(), false)
|
return basic.JDE2DateByZone(basic.NextMarsConjunction(jde), date.Location(), false)
|
||||||
@@ -225,8 +223,7 @@ func NextConjunction(date time.Time) time.Time {
|
|||||||
|
|
||||||
// LastOpposition 上一次冲日 / previous opposition.
|
// LastOpposition 上一次冲日 / previous opposition.
|
||||||
//
|
//
|
||||||
// 返回 date 之前最近一次冲日时刻,结果保持 date 的时区。
|
// 返回 date 当前或之前最近一次冲日时刻,结果保持 date 的时区。
|
||||||
// Returns the most recent opposition relative to date, keeping date's time zone.
|
|
||||||
func LastOpposition(date time.Time) time.Time {
|
func LastOpposition(date time.Time) time.Time {
|
||||||
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
|
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
|
||||||
return basic.JDE2DateByZone(basic.LastMarsOpposition(jde), date.Location(), false)
|
return basic.JDE2DateByZone(basic.LastMarsOpposition(jde), date.Location(), false)
|
||||||
@@ -234,8 +231,7 @@ func LastOpposition(date time.Time) time.Time {
|
|||||||
|
|
||||||
// NextOpposition 下一次冲日 / next opposition.
|
// NextOpposition 下一次冲日 / next opposition.
|
||||||
//
|
//
|
||||||
// 返回 date 之后最近一次冲日时刻,结果保持 date 的时区。
|
// 返回 date 当前或之后最近一次冲日时刻,结果保持 date 的时区。
|
||||||
// Returns the next opposition relative to date, keeping date's time zone.
|
|
||||||
func NextOpposition(date time.Time) time.Time {
|
func NextOpposition(date time.Time) time.Time {
|
||||||
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
|
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
|
||||||
return basic.JDE2DateByZone(basic.NextMarsOpposition(jde), date.Location(), false)
|
return basic.JDE2DateByZone(basic.NextMarsOpposition(jde), date.Location(), false)
|
||||||
@@ -243,8 +239,7 @@ func NextOpposition(date time.Time) time.Time {
|
|||||||
|
|
||||||
// LastProgradeToRetrograde 上一次顺行转逆行留 / previous station from prograde to retrograde.
|
// LastProgradeToRetrograde 上一次顺行转逆行留 / previous station from prograde to retrograde.
|
||||||
//
|
//
|
||||||
// 返回 date 之前最近一次由顺行转为逆行的留时刻,结果保持 date 的时区。
|
// 返回 date 当前或之前最近一次由顺行转为逆行的留时刻,结果保持 date 的时区。
|
||||||
// Returns the most recent stationary point where motion changes from prograde to retrograde relative to date, keeping date's time zone.
|
|
||||||
func LastProgradeToRetrograde(date time.Time) time.Time {
|
func LastProgradeToRetrograde(date time.Time) time.Time {
|
||||||
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
|
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
|
||||||
return basic.JDE2DateByZone(basic.LastMarsProgradeToRetrograde(jde), date.Location(), false)
|
return basic.JDE2DateByZone(basic.LastMarsProgradeToRetrograde(jde), date.Location(), false)
|
||||||
@@ -252,8 +247,7 @@ func LastProgradeToRetrograde(date time.Time) time.Time {
|
|||||||
|
|
||||||
// NextProgradeToRetrograde 下一次顺行转逆行留 / next station from prograde to retrograde.
|
// NextProgradeToRetrograde 下一次顺行转逆行留 / next station from prograde to retrograde.
|
||||||
//
|
//
|
||||||
// 返回 date 之后最近一次由顺行转为逆行的留时刻,结果保持 date 的时区。
|
// 返回 date 当前或之后最近一次由顺行转为逆行的留时刻,结果保持 date 的时区。
|
||||||
// Returns the next stationary point where motion changes from prograde to retrograde relative to date, keeping date's time zone.
|
|
||||||
func NextProgradeToRetrograde(date time.Time) time.Time {
|
func NextProgradeToRetrograde(date time.Time) time.Time {
|
||||||
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
|
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
|
||||||
return basic.JDE2DateByZone(basic.NextMarsProgradeToRetrograde(jde), date.Location(), false)
|
return basic.JDE2DateByZone(basic.NextMarsProgradeToRetrograde(jde), date.Location(), false)
|
||||||
@@ -261,8 +255,7 @@ func NextProgradeToRetrograde(date time.Time) time.Time {
|
|||||||
|
|
||||||
// LastRetrogradeToPrograde 上一次逆行转顺行留 / previous station from retrograde to prograde.
|
// LastRetrogradeToPrograde 上一次逆行转顺行留 / previous station from retrograde to prograde.
|
||||||
//
|
//
|
||||||
// 返回 date 之前最近一次由逆行转为顺行的留时刻,结果保持 date 的时区。
|
// 返回 date 当前或之前最近一次由逆行转为顺行的留时刻,结果保持 date 的时区。
|
||||||
// Returns the most recent stationary point where motion changes from retrograde to prograde relative to date, keeping date's time zone.
|
|
||||||
func LastRetrogradeToPrograde(date time.Time) time.Time {
|
func LastRetrogradeToPrograde(date time.Time) time.Time {
|
||||||
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
|
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
|
||||||
return basic.JDE2DateByZone(basic.LastMarsRetrogradeToPrograde(jde), date.Location(), false)
|
return basic.JDE2DateByZone(basic.LastMarsRetrogradeToPrograde(jde), date.Location(), false)
|
||||||
@@ -270,8 +263,7 @@ func LastRetrogradeToPrograde(date time.Time) time.Time {
|
|||||||
|
|
||||||
// NextRetrogradeToPrograde 下一次逆行转顺行留 / next station from retrograde to prograde.
|
// NextRetrogradeToPrograde 下一次逆行转顺行留 / next station from retrograde to prograde.
|
||||||
//
|
//
|
||||||
// 返回 date 之后最近一次由逆行转为顺行的留时刻,结果保持 date 的时区。
|
// 返回 date 当前或之后最近一次由逆行转为顺行的留时刻,结果保持 date 的时区。
|
||||||
// Returns the next stationary point where motion changes from retrograde to prograde relative to date, keeping date's time zone.
|
|
||||||
func NextRetrogradeToPrograde(date time.Time) time.Time {
|
func NextRetrogradeToPrograde(date time.Time) time.Time {
|
||||||
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
|
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
|
||||||
return basic.JDE2DateByZone(basic.NextMarsRetrogradeToPrograde(jde), date.Location(), false)
|
return basic.JDE2DateByZone(basic.NextMarsRetrogradeToPrograde(jde), date.Location(), false)
|
||||||
@@ -279,8 +271,7 @@ func NextRetrogradeToPrograde(date time.Time) time.Time {
|
|||||||
|
|
||||||
// LastEasternQuadrature 上一次东方照 / previous eastern quadrature.
|
// LastEasternQuadrature 上一次东方照 / previous eastern quadrature.
|
||||||
//
|
//
|
||||||
// 返回 date 之前最近一次东方照时刻,结果保持 date 的时区。
|
// 返回 date 当前或之前最近一次东方照时刻,结果保持 date 的时区。
|
||||||
// Returns the most recent eastern quadrature relative to date, keeping date's time zone.
|
|
||||||
func LastEasternQuadrature(date time.Time) time.Time {
|
func LastEasternQuadrature(date time.Time) time.Time {
|
||||||
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
|
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
|
||||||
return basic.JDE2DateByZone(basic.LastMarsEasternQuadrature(jde), date.Location(), false)
|
return basic.JDE2DateByZone(basic.LastMarsEasternQuadrature(jde), date.Location(), false)
|
||||||
@@ -288,8 +279,7 @@ func LastEasternQuadrature(date time.Time) time.Time {
|
|||||||
|
|
||||||
// NextEasternQuadrature 下一次东方照 / next eastern quadrature.
|
// NextEasternQuadrature 下一次东方照 / next eastern quadrature.
|
||||||
//
|
//
|
||||||
// 返回 date 之后最近一次东方照时刻,结果保持 date 的时区。
|
// 返回 date 当前或之后最近一次东方照时刻,结果保持 date 的时区。
|
||||||
// Returns the next eastern quadrature relative to date, keeping date's time zone.
|
|
||||||
func NextEasternQuadrature(date time.Time) time.Time {
|
func NextEasternQuadrature(date time.Time) time.Time {
|
||||||
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
|
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
|
||||||
return basic.JDE2DateByZone(basic.NextMarsEasternQuadrature(jde), date.Location(), false)
|
return basic.JDE2DateByZone(basic.NextMarsEasternQuadrature(jde), date.Location(), false)
|
||||||
@@ -297,8 +287,7 @@ func NextEasternQuadrature(date time.Time) time.Time {
|
|||||||
|
|
||||||
// LastWesternQuadrature 上一次西方照 / previous western quadrature.
|
// LastWesternQuadrature 上一次西方照 / previous western quadrature.
|
||||||
//
|
//
|
||||||
// 返回 date 之前最近一次西方照时刻,结果保持 date 的时区。
|
// 返回 date 当前或之前最近一次西方照时刻,结果保持 date 的时区。
|
||||||
// Returns the most recent western quadrature relative to date, keeping date's time zone.
|
|
||||||
func LastWesternQuadrature(date time.Time) time.Time {
|
func LastWesternQuadrature(date time.Time) time.Time {
|
||||||
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
|
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
|
||||||
return basic.JDE2DateByZone(basic.LastMarsWesternQuadrature(jde), date.Location(), false)
|
return basic.JDE2DateByZone(basic.LastMarsWesternQuadrature(jde), date.Location(), false)
|
||||||
@@ -306,8 +295,7 @@ func LastWesternQuadrature(date time.Time) time.Time {
|
|||||||
|
|
||||||
// NextWesternQuadrature 下一次西方照 / next western quadrature.
|
// NextWesternQuadrature 下一次西方照 / next western quadrature.
|
||||||
//
|
//
|
||||||
// 返回 date 之后最近一次西方照时刻,结果保持 date 的时区。
|
// 返回 date 当前或之后最近一次西方照时刻,结果保持 date 的时区。
|
||||||
// Returns the next western quadrature relative to date, keeping date's time zone.
|
|
||||||
func NextWesternQuadrature(date time.Time) time.Time {
|
func NextWesternQuadrature(date time.Time) time.Time {
|
||||||
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
|
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
|
||||||
return basic.JDE2DateByZone(basic.NextMarsWesternQuadrature(jde), date.Location(), false)
|
return basic.JDE2DateByZone(basic.NextMarsWesternQuadrature(jde), date.Location(), false)
|
||||||
|
|||||||
+34
-52
@@ -207,8 +207,7 @@ func SetTime(date time.Time, lon, lat, height float64, aero bool) (time.Time, er
|
|||||||
|
|
||||||
// LastConjunction 上一次合日 / previous conjunction with the Sun.
|
// LastConjunction 上一次合日 / previous conjunction with the Sun.
|
||||||
//
|
//
|
||||||
// 返回 date 之前最近一次与太阳的合日时刻,结果保持 date 的时区。
|
// 返回 date 当前或之前最近一次与太阳的合日时刻,结果保持 date 的时区。
|
||||||
// Returns the most recent conjunction with the Sun relative to date, keeping date's time zone.
|
|
||||||
func LastConjunction(date time.Time) time.Time {
|
func LastConjunction(date time.Time) time.Time {
|
||||||
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
|
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
|
||||||
return basic.JDE2DateByZone(basic.LastMercuryConjunction(jde), date.Location(), false)
|
return basic.JDE2DateByZone(basic.LastMercuryConjunction(jde), date.Location(), false)
|
||||||
@@ -216,8 +215,7 @@ func LastConjunction(date time.Time) time.Time {
|
|||||||
|
|
||||||
// NextConjunction 下一次合日 / next conjunction with the Sun.
|
// NextConjunction 下一次合日 / next conjunction with the Sun.
|
||||||
//
|
//
|
||||||
// 返回 date 之后最近一次与太阳的合日时刻,结果保持 date 的时区。
|
// 返回 date 当前或之后最近一次与太阳的合日时刻,结果保持 date 的时区。
|
||||||
// Returns the next conjunction with the Sun relative to date, keeping date's time zone.
|
|
||||||
func NextConjunction(date time.Time) time.Time {
|
func NextConjunction(date time.Time) time.Time {
|
||||||
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
|
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
|
||||||
return basic.JDE2DateByZone(basic.NextMercuryConjunction(jde), date.Location(), false)
|
return basic.JDE2DateByZone(basic.NextMercuryConjunction(jde), date.Location(), false)
|
||||||
@@ -225,144 +223,128 @@ func NextConjunction(date time.Time) time.Time {
|
|||||||
|
|
||||||
// LastInferiorConjunction 上一次下合 / previous inferior conjunction.
|
// LastInferiorConjunction 上一次下合 / previous inferior conjunction.
|
||||||
//
|
//
|
||||||
// 返回 date 之前最近一次下合时刻,结果保持 date 的时区。
|
// 返回 date 当前或之前最近一次下合时刻,结果保持 date 的时区。
|
||||||
// Returns the most recent inferior conjunction relative to date, keeping date's time zone.
|
|
||||||
func LastInferiorConjunction(date time.Time) time.Time {
|
func LastInferiorConjunction(date time.Time) time.Time {
|
||||||
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
|
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
|
||||||
return basic.JDE2DateByZone(basic.LastMercuryInferiorConjunction(jde), date.Location(), false)
|
return basic.JDE2DateByZone(basic.LastMercuryInferiorConjunctionInclusive(jde), date.Location(), false)
|
||||||
}
|
}
|
||||||
|
|
||||||
// NextInferiorConjunction 下一次下合 / next inferior conjunction.
|
// NextInferiorConjunction 下一次下合 / next inferior conjunction.
|
||||||
//
|
//
|
||||||
// 返回 date 之后最近一次下合时刻,结果保持 date 的时区。
|
// 返回 date 当前或之后最近一次下合时刻,结果保持 date 的时区。
|
||||||
// Returns the next inferior conjunction relative to date, keeping date's time zone.
|
|
||||||
func NextInferiorConjunction(date time.Time) time.Time {
|
func NextInferiorConjunction(date time.Time) time.Time {
|
||||||
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
|
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
|
||||||
return basic.JDE2DateByZone(basic.NextMercuryInferiorConjunction(jde), date.Location(), false)
|
return basic.JDE2DateByZone(basic.NextMercuryInferiorConjunctionInclusive(jde), date.Location(), false)
|
||||||
}
|
}
|
||||||
|
|
||||||
// LastSuperiorConjunction 上一次上合 / previous superior conjunction.
|
// LastSuperiorConjunction 上一次上合 / previous superior conjunction.
|
||||||
//
|
//
|
||||||
// 返回 date 之前最近一次上合时刻,结果保持 date 的时区。
|
// 返回 date 当前或之前最近一次上合时刻,结果保持 date 的时区。
|
||||||
// Returns the most recent superior conjunction relative to date, keeping date's time zone.
|
|
||||||
func LastSuperiorConjunction(date time.Time) time.Time {
|
func LastSuperiorConjunction(date time.Time) time.Time {
|
||||||
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
|
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
|
||||||
return basic.JDE2DateByZone(basic.LastMercurySuperiorConjunction(jde), date.Location(), false)
|
return basic.JDE2DateByZone(basic.LastMercurySuperiorConjunctionInclusive(jde), date.Location(), false)
|
||||||
}
|
}
|
||||||
|
|
||||||
// NextSuperiorConjunction 下一次上合 / next superior conjunction.
|
// NextSuperiorConjunction 下一次上合 / next superior conjunction.
|
||||||
//
|
//
|
||||||
// 返回 date 之后最近一次上合时刻,结果保持 date 的时区。
|
// 返回 date 当前或之后最近一次上合时刻,结果保持 date 的时区。
|
||||||
// Returns the next superior conjunction relative to date, keeping date's time zone.
|
|
||||||
func NextSuperiorConjunction(date time.Time) time.Time {
|
func NextSuperiorConjunction(date time.Time) time.Time {
|
||||||
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
|
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
|
||||||
return basic.JDE2DateByZone(basic.NextMercurySuperiorConjunction(jde), date.Location(), false)
|
return basic.JDE2DateByZone(basic.NextMercurySuperiorConjunctionInclusive(jde), date.Location(), false)
|
||||||
}
|
}
|
||||||
|
|
||||||
// LastRetrograde 上一次留 / previous stationary point.
|
// LastRetrograde 上一次留 / previous stationary point.
|
||||||
//
|
//
|
||||||
// 返回 date 之前最近一次留时刻,不区分顺转逆还是逆转顺,结果保持 date 的时区。
|
// 返回 date 当前或之前最近一次留时刻,不区分顺转逆还是逆转顺,结果保持 date 的时区。
|
||||||
// Returns the most recent stationary point relative to date without distinguishing direction, keeping date's time zone.
|
|
||||||
func LastRetrograde(date time.Time) time.Time {
|
func LastRetrograde(date time.Time) time.Time {
|
||||||
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
|
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
|
||||||
return basic.JDE2DateByZone(basic.LastMercuryRetrograde(jde), date.Location(), false)
|
return basic.JDE2DateByZone(basic.LastMercuryRetrogradeInclusive(jde), date.Location(), false)
|
||||||
}
|
}
|
||||||
|
|
||||||
// NextRetrograde 下一次留 / next stationary point.
|
// NextRetrograde 下一次留 / next stationary point.
|
||||||
//
|
//
|
||||||
// 返回 date 之后最近一次留时刻,不区分顺转逆还是逆转顺,结果保持 date 的时区。
|
// 返回 date 当前或之后最近一次留时刻,不区分顺转逆还是逆转顺,结果保持 date 的时区。
|
||||||
// Returns the next stationary point relative to date without distinguishing direction, keeping date's time zone.
|
|
||||||
func NextRetrograde(date time.Time) time.Time {
|
func NextRetrograde(date time.Time) time.Time {
|
||||||
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
|
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
|
||||||
return basic.JDE2DateByZone(basic.NextMercuryRetrograde(jde), date.Location(), false)
|
return basic.JDE2DateByZone(basic.NextMercuryRetrogradeInclusive(jde), date.Location(), false)
|
||||||
}
|
}
|
||||||
|
|
||||||
// LastProgradeToRetrograde 上一次顺行转逆行留 / previous station from prograde to retrograde.
|
// LastProgradeToRetrograde 上一次顺行转逆行留 / previous station from prograde to retrograde.
|
||||||
//
|
//
|
||||||
// 返回 date 之前最近一次由顺行转为逆行的留时刻,结果保持 date 的时区。
|
// 返回 date 当前或之前最近一次由顺行转为逆行的留时刻,结果保持 date 的时区。
|
||||||
// Returns the most recent stationary point where motion changes from prograde to retrograde relative to date, keeping date's time zone.
|
|
||||||
func LastProgradeToRetrograde(date time.Time) time.Time {
|
func LastProgradeToRetrograde(date time.Time) time.Time {
|
||||||
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
|
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
|
||||||
return basic.JDE2DateByZone(basic.LastMercuryProgradeToRetrograde(jde), date.Location(), false)
|
return basic.JDE2DateByZone(basic.LastMercuryProgradeToRetrogradeInclusive(jde), date.Location(), false)
|
||||||
}
|
}
|
||||||
|
|
||||||
// NextProgradeToRetrograde 下一次顺行转逆行留 / next station from prograde to retrograde.
|
// NextProgradeToRetrograde 下一次顺行转逆行留 / next station from prograde to retrograde.
|
||||||
//
|
//
|
||||||
// 返回 date 之后最近一次由顺行转为逆行的留时刻,结果保持 date 的时区。
|
// 返回 date 当前或之后最近一次由顺行转为逆行的留时刻,结果保持 date 的时区。
|
||||||
// Returns the next stationary point where motion changes from prograde to retrograde relative to date, keeping date's time zone.
|
|
||||||
func NextProgradeToRetrograde(date time.Time) time.Time {
|
func NextProgradeToRetrograde(date time.Time) time.Time {
|
||||||
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
|
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
|
||||||
return basic.JDE2DateByZone(basic.NextMercuryProgradeToRetrograde(jde), date.Location(), false)
|
return basic.JDE2DateByZone(basic.NextMercuryProgradeToRetrogradeInclusive(jde), date.Location(), false)
|
||||||
}
|
}
|
||||||
|
|
||||||
// LastRetrogradeToPrograde 上一次逆行转顺行留 / previous station from retrograde to prograde.
|
// LastRetrogradeToPrograde 上一次逆行转顺行留 / previous station from retrograde to prograde.
|
||||||
//
|
//
|
||||||
// 返回 date 之前最近一次由逆行转为顺行的留时刻,结果保持 date 的时区。
|
// 返回 date 当前或之前最近一次由逆行转为顺行的留时刻,结果保持 date 的时区。
|
||||||
// Returns the most recent stationary point where motion changes from retrograde to prograde relative to date, keeping date's time zone.
|
|
||||||
func LastRetrogradeToPrograde(date time.Time) time.Time {
|
func LastRetrogradeToPrograde(date time.Time) time.Time {
|
||||||
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
|
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
|
||||||
return basic.JDE2DateByZone(basic.LastMercuryRetrogradeToPrograde(jde), date.Location(), false)
|
return basic.JDE2DateByZone(basic.LastMercuryRetrogradeToProgradeInclusive(jde), date.Location(), false)
|
||||||
}
|
}
|
||||||
|
|
||||||
// NextRetrogradeToPrograde 下一次逆行转顺行留 / next station from retrograde to prograde.
|
// NextRetrogradeToPrograde 下一次逆行转顺行留 / next station from retrograde to prograde.
|
||||||
//
|
//
|
||||||
// 返回 date 之后最近一次由逆行转为顺行的留时刻,结果保持 date 的时区。
|
// 返回 date 当前或之后最近一次由逆行转为顺行的留时刻,结果保持 date 的时区。
|
||||||
// Returns the next stationary point where motion changes from retrograde to prograde relative to date, keeping date's time zone.
|
|
||||||
func NextRetrogradeToPrograde(date time.Time) time.Time {
|
func NextRetrogradeToPrograde(date time.Time) time.Time {
|
||||||
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
|
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
|
||||||
return basic.JDE2DateByZone(basic.NextMercuryRetrogradeToPrograde(jde), date.Location(), false)
|
return basic.JDE2DateByZone(basic.NextMercuryRetrogradeToProgradeInclusive(jde), date.Location(), false)
|
||||||
}
|
}
|
||||||
|
|
||||||
// LastGreatestElongation 上一次大距 / previous greatest elongation.
|
// LastGreatestElongation 上一次大距 / previous greatest elongation.
|
||||||
//
|
//
|
||||||
// 返回 date 之前最近一次大距时刻,不区分东西大距,结果保持 date 的时区。
|
// 返回 date 当前或之前最近一次大距时刻,不区分东西大距,结果保持 date 的时区。
|
||||||
// Returns the most recent greatest elongation relative to date without distinguishing east or west, keeping date's time zone.
|
|
||||||
func LastGreatestElongation(date time.Time) time.Time {
|
func LastGreatestElongation(date time.Time) time.Time {
|
||||||
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
|
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
|
||||||
return basic.JDE2DateByZone(basic.LastMercuryGreatestElongation(jde), date.Location(), false)
|
return basic.JDE2DateByZone(basic.LastMercuryGreatestElongationInclusive(jde), date.Location(), false)
|
||||||
}
|
}
|
||||||
|
|
||||||
// NextGreatestElongation 下一次大距 / next greatest elongation.
|
// NextGreatestElongation 下一次大距 / next greatest elongation.
|
||||||
//
|
//
|
||||||
// 返回 date 之后最近一次大距时刻,不区分东西大距,结果保持 date 的时区。
|
// 返回 date 当前或之后最近一次大距时刻,不区分东西大距,结果保持 date 的时区。
|
||||||
// Returns the next greatest elongation relative to date without distinguishing east or west, keeping date's time zone.
|
|
||||||
func NextGreatestElongation(date time.Time) time.Time {
|
func NextGreatestElongation(date time.Time) time.Time {
|
||||||
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
|
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
|
||||||
return basic.JDE2DateByZone(basic.NextMercuryGreatestElongation(jde), date.Location(), false)
|
return basic.JDE2DateByZone(basic.NextMercuryGreatestElongationInclusive(jde), date.Location(), false)
|
||||||
}
|
}
|
||||||
|
|
||||||
// LastGreatestElongationEast 上一次东大距 / previous greatest eastern elongation.
|
// LastGreatestElongationEast 上一次东大距 / previous greatest eastern elongation.
|
||||||
//
|
//
|
||||||
// 返回 date 之前最近一次东大距时刻,结果保持 date 的时区。
|
// 返回 date 当前或之前最近一次东大距时刻,结果保持 date 的时区。
|
||||||
// Returns the most recent greatest eastern elongation relative to date, keeping date's time zone.
|
|
||||||
func LastGreatestElongationEast(date time.Time) time.Time {
|
func LastGreatestElongationEast(date time.Time) time.Time {
|
||||||
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
|
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
|
||||||
return basic.JDE2DateByZone(basic.LastMercuryGreatestElongationEast(jde), date.Location(), false)
|
return basic.JDE2DateByZone(basic.LastMercuryGreatestElongationEastInclusive(jde), date.Location(), false)
|
||||||
}
|
}
|
||||||
|
|
||||||
// NextGreatestElongationEast 下一次东大距 / next greatest eastern elongation.
|
// NextGreatestElongationEast 下一次东大距 / next greatest eastern elongation.
|
||||||
//
|
//
|
||||||
// 返回 date 之后最近一次东大距时刻,结果保持 date 的时区。
|
// 返回 date 当前或之后最近一次东大距时刻,结果保持 date 的时区。
|
||||||
// Returns the next greatest eastern elongation relative to date, keeping date's time zone.
|
|
||||||
func NextGreatestElongationEast(date time.Time) time.Time {
|
func NextGreatestElongationEast(date time.Time) time.Time {
|
||||||
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
|
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
|
||||||
return basic.JDE2DateByZone(basic.NextMercuryGreatestElongationEast(jde), date.Location(), false)
|
return basic.JDE2DateByZone(basic.NextMercuryGreatestElongationEastInclusive(jde), date.Location(), false)
|
||||||
}
|
}
|
||||||
|
|
||||||
// LastGreatestElongationWest 上一次西大距 / previous greatest western elongation.
|
// LastGreatestElongationWest 上一次西大距 / previous greatest western elongation.
|
||||||
//
|
//
|
||||||
// 返回 date 之前最近一次西大距时刻,结果保持 date 的时区。
|
// 返回 date 当前或之前最近一次西大距时刻,结果保持 date 的时区。
|
||||||
// Returns the most recent greatest western elongation relative to date, keeping date's time zone.
|
|
||||||
func LastGreatestElongationWest(date time.Time) time.Time {
|
func LastGreatestElongationWest(date time.Time) time.Time {
|
||||||
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
|
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
|
||||||
return basic.JDE2DateByZone(basic.LastMercuryGreatestElongationWest(jde), date.Location(), false)
|
return basic.JDE2DateByZone(basic.LastMercuryGreatestElongationWestInclusive(jde), date.Location(), false)
|
||||||
}
|
}
|
||||||
|
|
||||||
// NextGreatestElongationWest 下一次西大距 / next greatest western elongation.
|
// NextGreatestElongationWest 下一次西大距 / next greatest western elongation.
|
||||||
//
|
//
|
||||||
// 返回 date 之后最近一次西大距时刻,结果保持 date 的时区。
|
// 返回 date 当前或之后最近一次西大距时刻,结果保持 date 的时区。
|
||||||
// Returns the next greatest western elongation relative to date, keeping date's time zone.
|
|
||||||
func NextGreatestElongationWest(date time.Time) time.Time {
|
func NextGreatestElongationWest(date time.Time) time.Time {
|
||||||
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
|
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
|
||||||
return basic.JDE2DateByZone(basic.NextMercuryGreatestElongationWest(jde), date.Location(), false)
|
return basic.JDE2DateByZone(basic.NextMercuryGreatestElongationWestInclusive(jde), date.Location(), false)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,73 @@
|
|||||||
|
package mercury
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"b612.me/astro/basic"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TransitInfo 地心水星凌日信息 / geocentric Mercury transit information.
|
||||||
|
//
|
||||||
|
// Start、Greatest、End、InternalStart、InternalEnd 都保持调用者输入的时区。
|
||||||
|
// 内切接触不存在时 InternalStart / InternalEnd 为零值。
|
||||||
|
// Start, Greatest, End, InternalStart, and InternalEnd preserve the caller's timezone.
|
||||||
|
// InternalStart and InternalEnd are zero values when internal contacts do not exist.
|
||||||
|
type TransitInfo struct {
|
||||||
|
Valid bool
|
||||||
|
|
||||||
|
Start time.Time
|
||||||
|
InternalStart time.Time
|
||||||
|
Greatest time.Time
|
||||||
|
InternalEnd time.Time
|
||||||
|
End time.Time
|
||||||
|
|
||||||
|
Duration time.Duration
|
||||||
|
InternalDuration time.Duration
|
||||||
|
|
||||||
|
MinimumSeparationArcsec float64
|
||||||
|
SunSemidiameterArcsec float64
|
||||||
|
PlanetSemidiameterArcsec float64
|
||||||
|
|
||||||
|
HasInternal bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// NextTransit 下一次地心水星凌日 / next geocentric Mercury transit.
|
||||||
|
func NextTransit(date time.Time) TransitInfo {
|
||||||
|
return transitInfoFromBasic(basic.NextMercuryTransit(basic.Date2JDE(date.UTC())), date.Location())
|
||||||
|
}
|
||||||
|
|
||||||
|
// LastTransit 上一次地心水星凌日 / previous geocentric Mercury transit.
|
||||||
|
func LastTransit(date time.Time) TransitInfo {
|
||||||
|
return transitInfoFromBasic(basic.LastMercuryTransit(basic.Date2JDE(date.UTC())), date.Location())
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClosestTransit 最近一次地心水星凌日 / closest geocentric Mercury transit.
|
||||||
|
func ClosestTransit(date time.Time) TransitInfo {
|
||||||
|
return transitInfoFromBasic(basic.ClosestMercuryTransit(basic.Date2JDE(date.UTC())), date.Location())
|
||||||
|
}
|
||||||
|
|
||||||
|
func transitInfoFromBasic(result basic.PlanetTransitResult, loc *time.Location) TransitInfo {
|
||||||
|
if !result.Valid {
|
||||||
|
return TransitInfo{}
|
||||||
|
}
|
||||||
|
start := basic.JDE2DateByZone(result.ExternalIngress, loc, false)
|
||||||
|
greatest := basic.JDE2DateByZone(result.Greatest, loc, false)
|
||||||
|
end := basic.JDE2DateByZone(result.ExternalEgress, loc, false)
|
||||||
|
info := TransitInfo{
|
||||||
|
Valid: true,
|
||||||
|
Start: start,
|
||||||
|
Greatest: greatest,
|
||||||
|
End: end,
|
||||||
|
Duration: end.Sub(start),
|
||||||
|
MinimumSeparationArcsec: result.MinimumSeparationArcsec,
|
||||||
|
SunSemidiameterArcsec: result.SunSemidiameterArcsec,
|
||||||
|
PlanetSemidiameterArcsec: result.PlanetSemidiameterArcsec,
|
||||||
|
HasInternal: result.HasInternal,
|
||||||
|
}
|
||||||
|
if result.HasInternal {
|
||||||
|
info.InternalStart = basic.JDE2DateByZone(result.InternalIngress, loc, false)
|
||||||
|
info.InternalEnd = basic.JDE2DateByZone(result.InternalEgress, loc, false)
|
||||||
|
info.InternalDuration = info.InternalEnd.Sub(info.InternalStart)
|
||||||
|
}
|
||||||
|
return info
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
package mercury
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestTransitWrappers(t *testing.T) {
|
||||||
|
loc := time.FixedZone("CST", 8*3600)
|
||||||
|
info := NextTransit(time.Date(2019, 1, 1, 0, 0, 0, 0, loc))
|
||||||
|
if !info.Valid {
|
||||||
|
t.Fatal("expected valid transit")
|
||||||
|
}
|
||||||
|
if info.Greatest.Location() != loc {
|
||||||
|
t.Fatalf("timezone mismatch: got %v want %v", info.Greatest.Location(), loc)
|
||||||
|
}
|
||||||
|
if info.Greatest.Year() != 2019 || info.Greatest.Month() != time.November || info.Greatest.Day() != 11 {
|
||||||
|
t.Fatalf("unexpected greatest time: %s", info.Greatest)
|
||||||
|
}
|
||||||
|
if !info.HasInternal || info.Duration <= 0 || info.InternalDuration <= 0 {
|
||||||
|
t.Fatalf("unexpected durations: %+v", info)
|
||||||
|
}
|
||||||
|
}
|
||||||
+12
-24
@@ -207,8 +207,7 @@ func SetTime(date time.Time, lon, lat, height float64, aero bool) (time.Time, er
|
|||||||
|
|
||||||
// LastConjunction 上一次合日 / previous conjunction with the Sun.
|
// LastConjunction 上一次合日 / previous conjunction with the Sun.
|
||||||
//
|
//
|
||||||
// 返回 date 之前最近一次与太阳的合日时刻,结果保持 date 的时区。
|
// 返回 date 当前或之前最近一次与太阳的合日时刻,结果保持 date 的时区。
|
||||||
// Returns the most recent conjunction with the Sun relative to date, keeping date's time zone.
|
|
||||||
func LastConjunction(date time.Time) time.Time {
|
func LastConjunction(date time.Time) time.Time {
|
||||||
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
|
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
|
||||||
return basic.JDE2DateByZone(basic.LastNeptuneConjunction(jde), date.Location(), false)
|
return basic.JDE2DateByZone(basic.LastNeptuneConjunction(jde), date.Location(), false)
|
||||||
@@ -216,8 +215,7 @@ func LastConjunction(date time.Time) time.Time {
|
|||||||
|
|
||||||
// NextConjunction 下一次合日 / next conjunction with the Sun.
|
// NextConjunction 下一次合日 / next conjunction with the Sun.
|
||||||
//
|
//
|
||||||
// 返回 date 之后最近一次与太阳的合日时刻,结果保持 date 的时区。
|
// 返回 date 当前或之后最近一次与太阳的合日时刻,结果保持 date 的时区。
|
||||||
// Returns the next conjunction with the Sun relative to date, keeping date's time zone.
|
|
||||||
func NextConjunction(date time.Time) time.Time {
|
func NextConjunction(date time.Time) time.Time {
|
||||||
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
|
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
|
||||||
return basic.JDE2DateByZone(basic.NextNeptuneConjunction(jde), date.Location(), false)
|
return basic.JDE2DateByZone(basic.NextNeptuneConjunction(jde), date.Location(), false)
|
||||||
@@ -225,8 +223,7 @@ func NextConjunction(date time.Time) time.Time {
|
|||||||
|
|
||||||
// LastOpposition 上一次冲日 / previous opposition.
|
// LastOpposition 上一次冲日 / previous opposition.
|
||||||
//
|
//
|
||||||
// 返回 date 之前最近一次冲日时刻,结果保持 date 的时区。
|
// 返回 date 当前或之前最近一次冲日时刻,结果保持 date 的时区。
|
||||||
// Returns the most recent opposition relative to date, keeping date's time zone.
|
|
||||||
func LastOpposition(date time.Time) time.Time {
|
func LastOpposition(date time.Time) time.Time {
|
||||||
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
|
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
|
||||||
return basic.JDE2DateByZone(basic.LastNeptuneOpposition(jde), date.Location(), false)
|
return basic.JDE2DateByZone(basic.LastNeptuneOpposition(jde), date.Location(), false)
|
||||||
@@ -234,8 +231,7 @@ func LastOpposition(date time.Time) time.Time {
|
|||||||
|
|
||||||
// NextOpposition 下一次冲日 / next opposition.
|
// NextOpposition 下一次冲日 / next opposition.
|
||||||
//
|
//
|
||||||
// 返回 date 之后最近一次冲日时刻,结果保持 date 的时区。
|
// 返回 date 当前或之后最近一次冲日时刻,结果保持 date 的时区。
|
||||||
// Returns the next opposition relative to date, keeping date's time zone.
|
|
||||||
func NextOpposition(date time.Time) time.Time {
|
func NextOpposition(date time.Time) time.Time {
|
||||||
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
|
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
|
||||||
return basic.JDE2DateByZone(basic.NextNeptuneOpposition(jde), date.Location(), false)
|
return basic.JDE2DateByZone(basic.NextNeptuneOpposition(jde), date.Location(), false)
|
||||||
@@ -243,8 +239,7 @@ func NextOpposition(date time.Time) time.Time {
|
|||||||
|
|
||||||
// LastProgradeToRetrograde 上一次顺行转逆行留 / previous station from prograde to retrograde.
|
// LastProgradeToRetrograde 上一次顺行转逆行留 / previous station from prograde to retrograde.
|
||||||
//
|
//
|
||||||
// 返回 date 之前最近一次由顺行转为逆行的留时刻,结果保持 date 的时区。
|
// 返回 date 当前或之前最近一次由顺行转为逆行的留时刻,结果保持 date 的时区。
|
||||||
// Returns the most recent stationary point where motion changes from prograde to retrograde relative to date, keeping date's time zone.
|
|
||||||
func LastProgradeToRetrograde(date time.Time) time.Time {
|
func LastProgradeToRetrograde(date time.Time) time.Time {
|
||||||
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
|
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
|
||||||
return basic.JDE2DateByZone(basic.LastNeptuneProgradeToRetrograde(jde), date.Location(), false)
|
return basic.JDE2DateByZone(basic.LastNeptuneProgradeToRetrograde(jde), date.Location(), false)
|
||||||
@@ -252,8 +247,7 @@ func LastProgradeToRetrograde(date time.Time) time.Time {
|
|||||||
|
|
||||||
// NextProgradeToRetrograde 下一次顺行转逆行留 / next station from prograde to retrograde.
|
// NextProgradeToRetrograde 下一次顺行转逆行留 / next station from prograde to retrograde.
|
||||||
//
|
//
|
||||||
// 返回 date 之后最近一次由顺行转为逆行的留时刻,结果保持 date 的时区。
|
// 返回 date 当前或之后最近一次由顺行转为逆行的留时刻,结果保持 date 的时区。
|
||||||
// Returns the next stationary point where motion changes from prograde to retrograde relative to date, keeping date's time zone.
|
|
||||||
func NextProgradeToRetrograde(date time.Time) time.Time {
|
func NextProgradeToRetrograde(date time.Time) time.Time {
|
||||||
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
|
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
|
||||||
return basic.JDE2DateByZone(basic.NextNeptuneProgradeToRetrograde(jde), date.Location(), false)
|
return basic.JDE2DateByZone(basic.NextNeptuneProgradeToRetrograde(jde), date.Location(), false)
|
||||||
@@ -261,8 +255,7 @@ func NextProgradeToRetrograde(date time.Time) time.Time {
|
|||||||
|
|
||||||
// LastRetrogradeToPrograde 上一次逆行转顺行留 / previous station from retrograde to prograde.
|
// LastRetrogradeToPrograde 上一次逆行转顺行留 / previous station from retrograde to prograde.
|
||||||
//
|
//
|
||||||
// 返回 date 之前最近一次由逆行转为顺行的留时刻,结果保持 date 的时区。
|
// 返回 date 当前或之前最近一次由逆行转为顺行的留时刻,结果保持 date 的时区。
|
||||||
// Returns the most recent stationary point where motion changes from retrograde to prograde relative to date, keeping date's time zone.
|
|
||||||
func LastRetrogradeToPrograde(date time.Time) time.Time {
|
func LastRetrogradeToPrograde(date time.Time) time.Time {
|
||||||
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
|
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
|
||||||
return basic.JDE2DateByZone(basic.LastNeptuneRetrogradeToPrograde(jde), date.Location(), false)
|
return basic.JDE2DateByZone(basic.LastNeptuneRetrogradeToPrograde(jde), date.Location(), false)
|
||||||
@@ -270,8 +263,7 @@ func LastRetrogradeToPrograde(date time.Time) time.Time {
|
|||||||
|
|
||||||
// NextRetrogradeToPrograde 下一次逆行转顺行留 / next station from retrograde to prograde.
|
// NextRetrogradeToPrograde 下一次逆行转顺行留 / next station from retrograde to prograde.
|
||||||
//
|
//
|
||||||
// 返回 date 之后最近一次由逆行转为顺行的留时刻,结果保持 date 的时区。
|
// 返回 date 当前或之后最近一次由逆行转为顺行的留时刻,结果保持 date 的时区。
|
||||||
// Returns the next stationary point where motion changes from retrograde to prograde relative to date, keeping date's time zone.
|
|
||||||
func NextRetrogradeToPrograde(date time.Time) time.Time {
|
func NextRetrogradeToPrograde(date time.Time) time.Time {
|
||||||
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
|
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
|
||||||
return basic.JDE2DateByZone(basic.NextNeptuneRetrogradeToPrograde(jde), date.Location(), false)
|
return basic.JDE2DateByZone(basic.NextNeptuneRetrogradeToPrograde(jde), date.Location(), false)
|
||||||
@@ -279,8 +271,7 @@ func NextRetrogradeToPrograde(date time.Time) time.Time {
|
|||||||
|
|
||||||
// LastEasternQuadrature 上一次东方照 / previous eastern quadrature.
|
// LastEasternQuadrature 上一次东方照 / previous eastern quadrature.
|
||||||
//
|
//
|
||||||
// 返回 date 之前最近一次东方照时刻,结果保持 date 的时区。
|
// 返回 date 当前或之前最近一次东方照时刻,结果保持 date 的时区。
|
||||||
// Returns the most recent eastern quadrature relative to date, keeping date's time zone.
|
|
||||||
func LastEasternQuadrature(date time.Time) time.Time {
|
func LastEasternQuadrature(date time.Time) time.Time {
|
||||||
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
|
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
|
||||||
return basic.JDE2DateByZone(basic.LastNeptuneEasternQuadrature(jde), date.Location(), false)
|
return basic.JDE2DateByZone(basic.LastNeptuneEasternQuadrature(jde), date.Location(), false)
|
||||||
@@ -288,8 +279,7 @@ func LastEasternQuadrature(date time.Time) time.Time {
|
|||||||
|
|
||||||
// NextEasternQuadrature 下一次东方照 / next eastern quadrature.
|
// NextEasternQuadrature 下一次东方照 / next eastern quadrature.
|
||||||
//
|
//
|
||||||
// 返回 date 之后最近一次东方照时刻,结果保持 date 的时区。
|
// 返回 date 当前或之后最近一次东方照时刻,结果保持 date 的时区。
|
||||||
// Returns the next eastern quadrature relative to date, keeping date's time zone.
|
|
||||||
func NextEasternQuadrature(date time.Time) time.Time {
|
func NextEasternQuadrature(date time.Time) time.Time {
|
||||||
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
|
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
|
||||||
return basic.JDE2DateByZone(basic.NextNeptuneEasternQuadrature(jde), date.Location(), false)
|
return basic.JDE2DateByZone(basic.NextNeptuneEasternQuadrature(jde), date.Location(), false)
|
||||||
@@ -297,8 +287,7 @@ func NextEasternQuadrature(date time.Time) time.Time {
|
|||||||
|
|
||||||
// LastWesternQuadrature 上一次西方照 / previous western quadrature.
|
// LastWesternQuadrature 上一次西方照 / previous western quadrature.
|
||||||
//
|
//
|
||||||
// 返回 date 之前最近一次西方照时刻,结果保持 date 的时区。
|
// 返回 date 当前或之前最近一次西方照时刻,结果保持 date 的时区。
|
||||||
// Returns the most recent western quadrature relative to date, keeping date's time zone.
|
|
||||||
func LastWesternQuadrature(date time.Time) time.Time {
|
func LastWesternQuadrature(date time.Time) time.Time {
|
||||||
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
|
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
|
||||||
return basic.JDE2DateByZone(basic.LastNeptuneWesternQuadrature(jde), date.Location(), false)
|
return basic.JDE2DateByZone(basic.LastNeptuneWesternQuadrature(jde), date.Location(), false)
|
||||||
@@ -306,8 +295,7 @@ func LastWesternQuadrature(date time.Time) time.Time {
|
|||||||
|
|
||||||
// NextWesternQuadrature 下一次西方照 / next western quadrature.
|
// NextWesternQuadrature 下一次西方照 / next western quadrature.
|
||||||
//
|
//
|
||||||
// 返回 date 之后最近一次西方照时刻,结果保持 date 的时区。
|
// 返回 date 当前或之后最近一次西方照时刻,结果保持 date 的时区。
|
||||||
// Returns the next western quadrature relative to date, keeping date's time zone.
|
|
||||||
func NextWesternQuadrature(date time.Time) time.Time {
|
func NextWesternQuadrature(date time.Time) time.Time {
|
||||||
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
|
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
|
||||||
return basic.JDE2DateByZone(basic.NextNeptuneWesternQuadrature(jde), date.Location(), false)
|
return basic.JDE2DateByZone(basic.NextNeptuneWesternQuadrature(jde), date.Location(), false)
|
||||||
|
|||||||
@@ -1,14 +1,19 @@
|
|||||||
package neptune
|
package neptune
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"math"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func sameUnixSecond(got time.Time, want int64) bool {
|
||||||
|
return math.Abs(float64(got.Unix()-want)) <= 1
|
||||||
|
}
|
||||||
|
|
||||||
func TestNeptune(t *testing.T) {
|
func TestNeptune(t *testing.T) {
|
||||||
tz := time.FixedZone("CST", 8*3600)
|
tz := time.FixedZone("CST", 8*3600)
|
||||||
date := time.Date(2022, 01, 20, 00, 00, 00, 00, tz)
|
date := time.Date(2022, 01, 20, 00, 00, 00, 00, tz)
|
||||||
if NextConjunction(date).Unix() != 1647171800 {
|
if !sameUnixSecond(NextConjunction(date), 1647171800) {
|
||||||
t.Fatal(NextConjunction(date).Unix())
|
t.Fatal(NextConjunction(date).Unix())
|
||||||
}
|
}
|
||||||
if CulminationTime(date, 115).Unix() != 1642665021 {
|
if CulminationTime(date, 115).Unix() != 1642665021 {
|
||||||
|
|||||||
+1
-1
@@ -25,7 +25,7 @@ func WherePlanetN(xt, zn int, jd float64, n int) float64 {
|
|||||||
t := (jd - 2451545) / 36525.0000
|
t := (jd - 2451545) / 36525.0000
|
||||||
t /= 10 // 转为儒略千年数
|
t /= 10 // 转为儒略千年数
|
||||||
|
|
||||||
body := planetViews[xt]
|
body := planetViews()[xt]
|
||||||
coord := body.coords[zn]
|
coord := body.coords[zn]
|
||||||
baseOrderTerms := len(coord.orders[0])
|
baseOrderTerms := len(coord.orders[0])
|
||||||
|
|
||||||
|
|||||||
+14
-1
@@ -25,8 +25,9 @@ func TestWherePlanetNFullMatchesDefault(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestPlanetViewsMatchRawCuts(t *testing.T) {
|
func TestPlanetViewsMatchRawCuts(t *testing.T) {
|
||||||
|
views := planetViews()
|
||||||
for bodyIndex, raw := range planetRawData {
|
for bodyIndex, raw := range planetRawData {
|
||||||
view := planetViews[bodyIndex]
|
view := views[bodyIndex]
|
||||||
if math.Float64bits(view.scale) != math.Float64bits(raw[0]) {
|
if math.Float64bits(view.scale) != math.Float64bits(raw[0]) {
|
||||||
t.Fatalf("body=%d scale mismatch", bodyIndex)
|
t.Fatalf("body=%d scale mismatch", bodyIndex)
|
||||||
}
|
}
|
||||||
@@ -42,3 +43,15 @@ func TestPlanetViewsMatchRawCuts(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestBuildPlanetViewsRejectsInvalidCuts(t *testing.T) {
|
||||||
|
_, err := buildPlanetViews([][]float64{{
|
||||||
|
10000000000,
|
||||||
|
20, 21, 20, 20, 20, 20, 20,
|
||||||
|
20, 20, 20, 20, 20, 20,
|
||||||
|
20, 20, 20, 20, 20, 20,
|
||||||
|
}})
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("expected invalid cut error")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
+23
-6
@@ -1,6 +1,9 @@
|
|||||||
package planet
|
package planet
|
||||||
|
|
||||||
import "fmt"
|
import (
|
||||||
|
"fmt"
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
type coordSeriesView struct {
|
type coordSeriesView struct {
|
||||||
orders [6][]float64
|
orders [6][]float64
|
||||||
@@ -11,13 +14,27 @@ type planetView struct {
|
|||||||
coords [3]coordSeriesView
|
coords [3]coordSeriesView
|
||||||
}
|
}
|
||||||
|
|
||||||
var planetViews = buildPlanetViews(planetRawData)
|
var (
|
||||||
|
planetViewsOnce sync.Once
|
||||||
|
planetViewsCache []planetView
|
||||||
|
planetViewsErr error
|
||||||
|
)
|
||||||
|
|
||||||
func buildPlanetViews(rawData [][]float64) []planetView {
|
func planetViews() []planetView {
|
||||||
|
planetViewsOnce.Do(func() {
|
||||||
|
planetViewsCache, planetViewsErr = buildPlanetViews(planetRawData)
|
||||||
|
})
|
||||||
|
if planetViewsErr != nil {
|
||||||
|
panic(planetViewsErr)
|
||||||
|
}
|
||||||
|
return planetViewsCache
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildPlanetViews(rawData [][]float64) ([]planetView, error) {
|
||||||
views := make([]planetView, len(rawData))
|
views := make([]planetView, len(rawData))
|
||||||
for bodyIndex, raw := range rawData {
|
for bodyIndex, raw := range rawData {
|
||||||
if len(raw) < 20 {
|
if len(raw) < 20 {
|
||||||
panic(fmt.Sprintf("planet raw data %d too short: %d", bodyIndex, len(raw)))
|
return nil, fmt.Errorf("planet raw data %d too short: %d", bodyIndex, len(raw))
|
||||||
}
|
}
|
||||||
view := planetView{scale: raw[0]}
|
view := planetView{scale: raw[0]}
|
||||||
for zn := 0; zn < 3; zn++ {
|
for zn := 0; zn < 3; zn++ {
|
||||||
@@ -26,12 +43,12 @@ func buildPlanetViews(rawData [][]float64) []planetView {
|
|||||||
start := int(raw[pn+order])
|
start := int(raw[pn+order])
|
||||||
end := int(raw[pn+order+1])
|
end := int(raw[pn+order+1])
|
||||||
if start < 0 || end < start || end > len(raw) {
|
if start < 0 || end < start || end > len(raw) {
|
||||||
panic(fmt.Sprintf("planet raw data %d coord %d order %d invalid cut: %d..%d (len=%d)", bodyIndex, zn, order, start, end, len(raw)))
|
return nil, fmt.Errorf("planet raw data %d coord %d order %d invalid cut: %d..%d (len=%d)", bodyIndex, zn, order, start, end, len(raw))
|
||||||
}
|
}
|
||||||
view.coords[zn].orders[order] = raw[start:end]
|
view.coords[zn].orders[order] = raw[start:end]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
views[bodyIndex] = view
|
views[bodyIndex] = view
|
||||||
}
|
}
|
||||||
return views
|
return views, nil
|
||||||
}
|
}
|
||||||
|
|||||||
+12
-24
@@ -207,8 +207,7 @@ func SetTime(date time.Time, lon, lat, height float64, aero bool) (time.Time, er
|
|||||||
|
|
||||||
// LastConjunction 上一次合日 / previous conjunction with the Sun.
|
// LastConjunction 上一次合日 / previous conjunction with the Sun.
|
||||||
//
|
//
|
||||||
// 返回 date 之前最近一次与太阳的合日时刻,结果保持 date 的时区。
|
// 返回 date 当前或之前最近一次与太阳的合日时刻,结果保持 date 的时区。
|
||||||
// Returns the most recent conjunction with the Sun relative to date, keeping date's time zone.
|
|
||||||
func LastConjunction(date time.Time) time.Time {
|
func LastConjunction(date time.Time) time.Time {
|
||||||
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
|
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
|
||||||
return basic.JDE2DateByZone(basic.LastSaturnConjunction(jde), date.Location(), false)
|
return basic.JDE2DateByZone(basic.LastSaturnConjunction(jde), date.Location(), false)
|
||||||
@@ -216,8 +215,7 @@ func LastConjunction(date time.Time) time.Time {
|
|||||||
|
|
||||||
// NextConjunction 下一次合日 / next conjunction with the Sun.
|
// NextConjunction 下一次合日 / next conjunction with the Sun.
|
||||||
//
|
//
|
||||||
// 返回 date 之后最近一次与太阳的合日时刻,结果保持 date 的时区。
|
// 返回 date 当前或之后最近一次与太阳的合日时刻,结果保持 date 的时区。
|
||||||
// Returns the next conjunction with the Sun relative to date, keeping date's time zone.
|
|
||||||
func NextConjunction(date time.Time) time.Time {
|
func NextConjunction(date time.Time) time.Time {
|
||||||
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
|
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
|
||||||
return basic.JDE2DateByZone(basic.NextSaturnConjunction(jde), date.Location(), false)
|
return basic.JDE2DateByZone(basic.NextSaturnConjunction(jde), date.Location(), false)
|
||||||
@@ -225,8 +223,7 @@ func NextConjunction(date time.Time) time.Time {
|
|||||||
|
|
||||||
// LastOpposition 上一次冲日 / previous opposition.
|
// LastOpposition 上一次冲日 / previous opposition.
|
||||||
//
|
//
|
||||||
// 返回 date 之前最近一次冲日时刻,结果保持 date 的时区。
|
// 返回 date 当前或之前最近一次冲日时刻,结果保持 date 的时区。
|
||||||
// Returns the most recent opposition relative to date, keeping date's time zone.
|
|
||||||
func LastOpposition(date time.Time) time.Time {
|
func LastOpposition(date time.Time) time.Time {
|
||||||
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
|
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
|
||||||
return basic.JDE2DateByZone(basic.LastSaturnOpposition(jde), date.Location(), false)
|
return basic.JDE2DateByZone(basic.LastSaturnOpposition(jde), date.Location(), false)
|
||||||
@@ -234,8 +231,7 @@ func LastOpposition(date time.Time) time.Time {
|
|||||||
|
|
||||||
// NextOpposition 下一次冲日 / next opposition.
|
// NextOpposition 下一次冲日 / next opposition.
|
||||||
//
|
//
|
||||||
// 返回 date 之后最近一次冲日时刻,结果保持 date 的时区。
|
// 返回 date 当前或之后最近一次冲日时刻,结果保持 date 的时区。
|
||||||
// Returns the next opposition relative to date, keeping date's time zone.
|
|
||||||
func NextOpposition(date time.Time) time.Time {
|
func NextOpposition(date time.Time) time.Time {
|
||||||
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
|
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
|
||||||
return basic.JDE2DateByZone(basic.NextSaturnOpposition(jde), date.Location(), false)
|
return basic.JDE2DateByZone(basic.NextSaturnOpposition(jde), date.Location(), false)
|
||||||
@@ -243,8 +239,7 @@ func NextOpposition(date time.Time) time.Time {
|
|||||||
|
|
||||||
// LastProgradeToRetrograde 上一次顺行转逆行留 / previous station from prograde to retrograde.
|
// LastProgradeToRetrograde 上一次顺行转逆行留 / previous station from prograde to retrograde.
|
||||||
//
|
//
|
||||||
// 返回 date 之前最近一次由顺行转为逆行的留时刻,结果保持 date 的时区。
|
// 返回 date 当前或之前最近一次由顺行转为逆行的留时刻,结果保持 date 的时区。
|
||||||
// Returns the most recent stationary point where motion changes from prograde to retrograde relative to date, keeping date's time zone.
|
|
||||||
func LastProgradeToRetrograde(date time.Time) time.Time {
|
func LastProgradeToRetrograde(date time.Time) time.Time {
|
||||||
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
|
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
|
||||||
return basic.JDE2DateByZone(basic.LastSaturnProgradeToRetrograde(jde), date.Location(), false)
|
return basic.JDE2DateByZone(basic.LastSaturnProgradeToRetrograde(jde), date.Location(), false)
|
||||||
@@ -252,8 +247,7 @@ func LastProgradeToRetrograde(date time.Time) time.Time {
|
|||||||
|
|
||||||
// NextProgradeToRetrograde 下一次顺行转逆行留 / next station from prograde to retrograde.
|
// NextProgradeToRetrograde 下一次顺行转逆行留 / next station from prograde to retrograde.
|
||||||
//
|
//
|
||||||
// 返回 date 之后最近一次由顺行转为逆行的留时刻,结果保持 date 的时区。
|
// 返回 date 当前或之后最近一次由顺行转为逆行的留时刻,结果保持 date 的时区。
|
||||||
// Returns the next stationary point where motion changes from prograde to retrograde relative to date, keeping date's time zone.
|
|
||||||
func NextProgradeToRetrograde(date time.Time) time.Time {
|
func NextProgradeToRetrograde(date time.Time) time.Time {
|
||||||
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
|
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
|
||||||
return basic.JDE2DateByZone(basic.NextSaturnProgradeToRetrograde(jde), date.Location(), false)
|
return basic.JDE2DateByZone(basic.NextSaturnProgradeToRetrograde(jde), date.Location(), false)
|
||||||
@@ -261,8 +255,7 @@ func NextProgradeToRetrograde(date time.Time) time.Time {
|
|||||||
|
|
||||||
// LastRetrogradeToPrograde 上一次逆行转顺行留 / previous station from retrograde to prograde.
|
// LastRetrogradeToPrograde 上一次逆行转顺行留 / previous station from retrograde to prograde.
|
||||||
//
|
//
|
||||||
// 返回 date 之前最近一次由逆行转为顺行的留时刻,结果保持 date 的时区。
|
// 返回 date 当前或之前最近一次由逆行转为顺行的留时刻,结果保持 date 的时区。
|
||||||
// Returns the most recent stationary point where motion changes from retrograde to prograde relative to date, keeping date's time zone.
|
|
||||||
func LastRetrogradeToPrograde(date time.Time) time.Time {
|
func LastRetrogradeToPrograde(date time.Time) time.Time {
|
||||||
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
|
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
|
||||||
return basic.JDE2DateByZone(basic.LastSaturnRetrogradeToPrograde(jde), date.Location(), false)
|
return basic.JDE2DateByZone(basic.LastSaturnRetrogradeToPrograde(jde), date.Location(), false)
|
||||||
@@ -270,8 +263,7 @@ func LastRetrogradeToPrograde(date time.Time) time.Time {
|
|||||||
|
|
||||||
// NextRetrogradeToPrograde 下一次逆行转顺行留 / next station from retrograde to prograde.
|
// NextRetrogradeToPrograde 下一次逆行转顺行留 / next station from retrograde to prograde.
|
||||||
//
|
//
|
||||||
// 返回 date 之后最近一次由逆行转为顺行的留时刻,结果保持 date 的时区。
|
// 返回 date 当前或之后最近一次由逆行转为顺行的留时刻,结果保持 date 的时区。
|
||||||
// Returns the next stationary point where motion changes from retrograde to prograde relative to date, keeping date's time zone.
|
|
||||||
func NextRetrogradeToPrograde(date time.Time) time.Time {
|
func NextRetrogradeToPrograde(date time.Time) time.Time {
|
||||||
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
|
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
|
||||||
return basic.JDE2DateByZone(basic.NextSaturnRetrogradeToPrograde(jde), date.Location(), false)
|
return basic.JDE2DateByZone(basic.NextSaturnRetrogradeToPrograde(jde), date.Location(), false)
|
||||||
@@ -279,8 +271,7 @@ func NextRetrogradeToPrograde(date time.Time) time.Time {
|
|||||||
|
|
||||||
// LastEasternQuadrature 上一次东方照 / previous eastern quadrature.
|
// LastEasternQuadrature 上一次东方照 / previous eastern quadrature.
|
||||||
//
|
//
|
||||||
// 返回 date 之前最近一次东方照时刻,结果保持 date 的时区。
|
// 返回 date 当前或之前最近一次东方照时刻,结果保持 date 的时区。
|
||||||
// Returns the most recent eastern quadrature relative to date, keeping date's time zone.
|
|
||||||
func LastEasternQuadrature(date time.Time) time.Time {
|
func LastEasternQuadrature(date time.Time) time.Time {
|
||||||
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
|
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
|
||||||
return basic.JDE2DateByZone(basic.LastSaturnEasternQuadrature(jde), date.Location(), false)
|
return basic.JDE2DateByZone(basic.LastSaturnEasternQuadrature(jde), date.Location(), false)
|
||||||
@@ -288,8 +279,7 @@ func LastEasternQuadrature(date time.Time) time.Time {
|
|||||||
|
|
||||||
// NextEasternQuadrature 下一次东方照 / next eastern quadrature.
|
// NextEasternQuadrature 下一次东方照 / next eastern quadrature.
|
||||||
//
|
//
|
||||||
// 返回 date 之后最近一次东方照时刻,结果保持 date 的时区。
|
// 返回 date 当前或之后最近一次东方照时刻,结果保持 date 的时区。
|
||||||
// Returns the next eastern quadrature relative to date, keeping date's time zone.
|
|
||||||
func NextEasternQuadrature(date time.Time) time.Time {
|
func NextEasternQuadrature(date time.Time) time.Time {
|
||||||
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
|
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
|
||||||
return basic.JDE2DateByZone(basic.NextSaturnEasternQuadrature(jde), date.Location(), false)
|
return basic.JDE2DateByZone(basic.NextSaturnEasternQuadrature(jde), date.Location(), false)
|
||||||
@@ -297,8 +287,7 @@ func NextEasternQuadrature(date time.Time) time.Time {
|
|||||||
|
|
||||||
// LastWesternQuadrature 上一次西方照 / previous western quadrature.
|
// LastWesternQuadrature 上一次西方照 / previous western quadrature.
|
||||||
//
|
//
|
||||||
// 返回 date 之前最近一次西方照时刻,结果保持 date 的时区。
|
// 返回 date 当前或之前最近一次西方照时刻,结果保持 date 的时区。
|
||||||
// Returns the most recent western quadrature relative to date, keeping date's time zone.
|
|
||||||
func LastWesternQuadrature(date time.Time) time.Time {
|
func LastWesternQuadrature(date time.Time) time.Time {
|
||||||
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
|
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
|
||||||
return basic.JDE2DateByZone(basic.LastSaturnWesternQuadrature(jde), date.Location(), false)
|
return basic.JDE2DateByZone(basic.LastSaturnWesternQuadrature(jde), date.Location(), false)
|
||||||
@@ -306,8 +295,7 @@ func LastWesternQuadrature(date time.Time) time.Time {
|
|||||||
|
|
||||||
// NextWesternQuadrature 下一次西方照 / next western quadrature.
|
// NextWesternQuadrature 下一次西方照 / next western quadrature.
|
||||||
//
|
//
|
||||||
// 返回 date 之后最近一次西方照时刻,结果保持 date 的时区。
|
// 返回 date 当前或之后最近一次西方照时刻,结果保持 date 的时区。
|
||||||
// Returns the next western quadrature relative to date, keeping date's time zone.
|
|
||||||
func NextWesternQuadrature(date time.Time) time.Time {
|
func NextWesternQuadrature(date time.Time) time.Time {
|
||||||
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
|
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
|
||||||
return basic.JDE2DateByZone(basic.NextSaturnWesternQuadrature(jde), date.Location(), false)
|
return basic.JDE2DateByZone(basic.NextSaturnWesternQuadrature(jde), date.Location(), false)
|
||||||
|
|||||||
+12
-24
@@ -207,8 +207,7 @@ func SetTime(date time.Time, lon, lat, height float64, aero bool) (time.Time, er
|
|||||||
|
|
||||||
// LastConjunction 上一次合日 / previous conjunction with the Sun.
|
// LastConjunction 上一次合日 / previous conjunction with the Sun.
|
||||||
//
|
//
|
||||||
// 返回 date 之前最近一次与太阳的合日时刻,结果保持 date 的时区。
|
// 返回 date 当前或之前最近一次与太阳的合日时刻,结果保持 date 的时区。
|
||||||
// Returns the most recent conjunction with the Sun relative to date, keeping date's time zone.
|
|
||||||
func LastConjunction(date time.Time) time.Time {
|
func LastConjunction(date time.Time) time.Time {
|
||||||
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
|
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
|
||||||
return basic.JDE2DateByZone(basic.LastUranusConjunction(jde), date.Location(), false)
|
return basic.JDE2DateByZone(basic.LastUranusConjunction(jde), date.Location(), false)
|
||||||
@@ -216,8 +215,7 @@ func LastConjunction(date time.Time) time.Time {
|
|||||||
|
|
||||||
// NextConjunction 下一次合日 / next conjunction with the Sun.
|
// NextConjunction 下一次合日 / next conjunction with the Sun.
|
||||||
//
|
//
|
||||||
// 返回 date 之后最近一次与太阳的合日时刻,结果保持 date 的时区。
|
// 返回 date 当前或之后最近一次与太阳的合日时刻,结果保持 date 的时区。
|
||||||
// Returns the next conjunction with the Sun relative to date, keeping date's time zone.
|
|
||||||
func NextConjunction(date time.Time) time.Time {
|
func NextConjunction(date time.Time) time.Time {
|
||||||
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
|
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
|
||||||
return basic.JDE2DateByZone(basic.NextUranusConjunction(jde), date.Location(), false)
|
return basic.JDE2DateByZone(basic.NextUranusConjunction(jde), date.Location(), false)
|
||||||
@@ -225,8 +223,7 @@ func NextConjunction(date time.Time) time.Time {
|
|||||||
|
|
||||||
// LastOpposition 上一次冲日 / previous opposition.
|
// LastOpposition 上一次冲日 / previous opposition.
|
||||||
//
|
//
|
||||||
// 返回 date 之前最近一次冲日时刻,结果保持 date 的时区。
|
// 返回 date 当前或之前最近一次冲日时刻,结果保持 date 的时区。
|
||||||
// Returns the most recent opposition relative to date, keeping date's time zone.
|
|
||||||
func LastOpposition(date time.Time) time.Time {
|
func LastOpposition(date time.Time) time.Time {
|
||||||
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
|
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
|
||||||
return basic.JDE2DateByZone(basic.LastUranusOpposition(jde), date.Location(), false)
|
return basic.JDE2DateByZone(basic.LastUranusOpposition(jde), date.Location(), false)
|
||||||
@@ -234,8 +231,7 @@ func LastOpposition(date time.Time) time.Time {
|
|||||||
|
|
||||||
// NextOpposition 下一次冲日 / next opposition.
|
// NextOpposition 下一次冲日 / next opposition.
|
||||||
//
|
//
|
||||||
// 返回 date 之后最近一次冲日时刻,结果保持 date 的时区。
|
// 返回 date 当前或之后最近一次冲日时刻,结果保持 date 的时区。
|
||||||
// Returns the next opposition relative to date, keeping date's time zone.
|
|
||||||
func NextOpposition(date time.Time) time.Time {
|
func NextOpposition(date time.Time) time.Time {
|
||||||
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
|
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
|
||||||
return basic.JDE2DateByZone(basic.NextUranusOpposition(jde), date.Location(), false)
|
return basic.JDE2DateByZone(basic.NextUranusOpposition(jde), date.Location(), false)
|
||||||
@@ -243,8 +239,7 @@ func NextOpposition(date time.Time) time.Time {
|
|||||||
|
|
||||||
// LastProgradeToRetrograde 上一次顺行转逆行留 / previous station from prograde to retrograde.
|
// LastProgradeToRetrograde 上一次顺行转逆行留 / previous station from prograde to retrograde.
|
||||||
//
|
//
|
||||||
// 返回 date 之前最近一次由顺行转为逆行的留时刻,结果保持 date 的时区。
|
// 返回 date 当前或之前最近一次由顺行转为逆行的留时刻,结果保持 date 的时区。
|
||||||
// Returns the most recent stationary point where motion changes from prograde to retrograde relative to date, keeping date's time zone.
|
|
||||||
func LastProgradeToRetrograde(date time.Time) time.Time {
|
func LastProgradeToRetrograde(date time.Time) time.Time {
|
||||||
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
|
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
|
||||||
return basic.JDE2DateByZone(basic.LastUranusProgradeToRetrograde(jde), date.Location(), false)
|
return basic.JDE2DateByZone(basic.LastUranusProgradeToRetrograde(jde), date.Location(), false)
|
||||||
@@ -252,8 +247,7 @@ func LastProgradeToRetrograde(date time.Time) time.Time {
|
|||||||
|
|
||||||
// NextProgradeToRetrograde 下一次顺行转逆行留 / next station from prograde to retrograde.
|
// NextProgradeToRetrograde 下一次顺行转逆行留 / next station from prograde to retrograde.
|
||||||
//
|
//
|
||||||
// 返回 date 之后最近一次由顺行转为逆行的留时刻,结果保持 date 的时区。
|
// 返回 date 当前或之后最近一次由顺行转为逆行的留时刻,结果保持 date 的时区。
|
||||||
// Returns the next stationary point where motion changes from prograde to retrograde relative to date, keeping date's time zone.
|
|
||||||
func NextProgradeToRetrograde(date time.Time) time.Time {
|
func NextProgradeToRetrograde(date time.Time) time.Time {
|
||||||
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
|
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
|
||||||
return basic.JDE2DateByZone(basic.NextUranusProgradeToRetrograde(jde), date.Location(), false)
|
return basic.JDE2DateByZone(basic.NextUranusProgradeToRetrograde(jde), date.Location(), false)
|
||||||
@@ -261,8 +255,7 @@ func NextProgradeToRetrograde(date time.Time) time.Time {
|
|||||||
|
|
||||||
// LastRetrogradeToPrograde 上一次逆行转顺行留 / previous station from retrograde to prograde.
|
// LastRetrogradeToPrograde 上一次逆行转顺行留 / previous station from retrograde to prograde.
|
||||||
//
|
//
|
||||||
// 返回 date 之前最近一次由逆行转为顺行的留时刻,结果保持 date 的时区。
|
// 返回 date 当前或之前最近一次由逆行转为顺行的留时刻,结果保持 date 的时区。
|
||||||
// Returns the most recent stationary point where motion changes from retrograde to prograde relative to date, keeping date's time zone.
|
|
||||||
func LastRetrogradeToPrograde(date time.Time) time.Time {
|
func LastRetrogradeToPrograde(date time.Time) time.Time {
|
||||||
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
|
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
|
||||||
return basic.JDE2DateByZone(basic.LastUranusRetrogradeToPrograde(jde), date.Location(), false)
|
return basic.JDE2DateByZone(basic.LastUranusRetrogradeToPrograde(jde), date.Location(), false)
|
||||||
@@ -270,8 +263,7 @@ func LastRetrogradeToPrograde(date time.Time) time.Time {
|
|||||||
|
|
||||||
// NextRetrogradeToPrograde 下一次逆行转顺行留 / next station from retrograde to prograde.
|
// NextRetrogradeToPrograde 下一次逆行转顺行留 / next station from retrograde to prograde.
|
||||||
//
|
//
|
||||||
// 返回 date 之后最近一次由逆行转为顺行的留时刻,结果保持 date 的时区。
|
// 返回 date 当前或之后最近一次由逆行转为顺行的留时刻,结果保持 date 的时区。
|
||||||
// Returns the next stationary point where motion changes from retrograde to prograde relative to date, keeping date's time zone.
|
|
||||||
func NextRetrogradeToPrograde(date time.Time) time.Time {
|
func NextRetrogradeToPrograde(date time.Time) time.Time {
|
||||||
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
|
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
|
||||||
return basic.JDE2DateByZone(basic.NextUranusRetrogradeToPrograde(jde), date.Location(), false)
|
return basic.JDE2DateByZone(basic.NextUranusRetrogradeToPrograde(jde), date.Location(), false)
|
||||||
@@ -279,8 +271,7 @@ func NextRetrogradeToPrograde(date time.Time) time.Time {
|
|||||||
|
|
||||||
// LastEasternQuadrature 上一次东方照 / previous eastern quadrature.
|
// LastEasternQuadrature 上一次东方照 / previous eastern quadrature.
|
||||||
//
|
//
|
||||||
// 返回 date 之前最近一次东方照时刻,结果保持 date 的时区。
|
// 返回 date 当前或之前最近一次东方照时刻,结果保持 date 的时区。
|
||||||
// Returns the most recent eastern quadrature relative to date, keeping date's time zone.
|
|
||||||
func LastEasternQuadrature(date time.Time) time.Time {
|
func LastEasternQuadrature(date time.Time) time.Time {
|
||||||
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
|
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
|
||||||
return basic.JDE2DateByZone(basic.LastUranusEasternQuadrature(jde), date.Location(), false)
|
return basic.JDE2DateByZone(basic.LastUranusEasternQuadrature(jde), date.Location(), false)
|
||||||
@@ -288,8 +279,7 @@ func LastEasternQuadrature(date time.Time) time.Time {
|
|||||||
|
|
||||||
// NextEasternQuadrature 下一次东方照 / next eastern quadrature.
|
// NextEasternQuadrature 下一次东方照 / next eastern quadrature.
|
||||||
//
|
//
|
||||||
// 返回 date 之后最近一次东方照时刻,结果保持 date 的时区。
|
// 返回 date 当前或之后最近一次东方照时刻,结果保持 date 的时区。
|
||||||
// Returns the next eastern quadrature relative to date, keeping date's time zone.
|
|
||||||
func NextEasternQuadrature(date time.Time) time.Time {
|
func NextEasternQuadrature(date time.Time) time.Time {
|
||||||
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
|
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
|
||||||
return basic.JDE2DateByZone(basic.NextUranusEasternQuadrature(jde), date.Location(), false)
|
return basic.JDE2DateByZone(basic.NextUranusEasternQuadrature(jde), date.Location(), false)
|
||||||
@@ -297,8 +287,7 @@ func NextEasternQuadrature(date time.Time) time.Time {
|
|||||||
|
|
||||||
// LastWesternQuadrature 上一次西方照 / previous western quadrature.
|
// LastWesternQuadrature 上一次西方照 / previous western quadrature.
|
||||||
//
|
//
|
||||||
// 返回 date 之前最近一次西方照时刻,结果保持 date 的时区。
|
// 返回 date 当前或之前最近一次西方照时刻,结果保持 date 的时区。
|
||||||
// Returns the most recent western quadrature relative to date, keeping date's time zone.
|
|
||||||
func LastWesternQuadrature(date time.Time) time.Time {
|
func LastWesternQuadrature(date time.Time) time.Time {
|
||||||
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
|
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
|
||||||
return basic.JDE2DateByZone(basic.LastUranusWesternQuadrature(jde), date.Location(), false)
|
return basic.JDE2DateByZone(basic.LastUranusWesternQuadrature(jde), date.Location(), false)
|
||||||
@@ -306,8 +295,7 @@ func LastWesternQuadrature(date time.Time) time.Time {
|
|||||||
|
|
||||||
// NextWesternQuadrature 下一次西方照 / next western quadrature.
|
// NextWesternQuadrature 下一次西方照 / next western quadrature.
|
||||||
//
|
//
|
||||||
// 返回 date 之后最近一次西方照时刻,结果保持 date 的时区。
|
// 返回 date 当前或之后最近一次西方照时刻,结果保持 date 的时区。
|
||||||
// Returns the next western quadrature relative to date, keeping date's time zone.
|
|
||||||
func NextWesternQuadrature(date time.Time) time.Time {
|
func NextWesternQuadrature(date time.Time) time.Time {
|
||||||
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
|
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
|
||||||
return basic.JDE2DateByZone(basic.NextUranusWesternQuadrature(jde), date.Location(), false)
|
return basic.JDE2DateByZone(basic.NextUranusWesternQuadrature(jde), date.Location(), false)
|
||||||
|
|||||||
@@ -0,0 +1,73 @@
|
|||||||
|
package venus
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"b612.me/astro/basic"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TransitInfo 地心金星凌日信息 / geocentric Venus transit information.
|
||||||
|
//
|
||||||
|
// Start、Greatest、End、InternalStart、InternalEnd 都保持调用者输入的时区。
|
||||||
|
// 内切接触不存在时 InternalStart / InternalEnd 为零值。
|
||||||
|
// Start, Greatest, End, InternalStart, and InternalEnd preserve the caller's timezone.
|
||||||
|
// InternalStart and InternalEnd are zero values when internal contacts do not exist.
|
||||||
|
type TransitInfo struct {
|
||||||
|
Valid bool
|
||||||
|
|
||||||
|
Start time.Time
|
||||||
|
InternalStart time.Time
|
||||||
|
Greatest time.Time
|
||||||
|
InternalEnd time.Time
|
||||||
|
End time.Time
|
||||||
|
|
||||||
|
Duration time.Duration
|
||||||
|
InternalDuration time.Duration
|
||||||
|
|
||||||
|
MinimumSeparationArcsec float64
|
||||||
|
SunSemidiameterArcsec float64
|
||||||
|
PlanetSemidiameterArcsec float64
|
||||||
|
|
||||||
|
HasInternal bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// NextTransit 下一次地心金星凌日 / next geocentric Venus transit.
|
||||||
|
func NextTransit(date time.Time) TransitInfo {
|
||||||
|
return transitInfoFromBasic(basic.NextVenusTransit(basic.Date2JDE(date.UTC())), date.Location())
|
||||||
|
}
|
||||||
|
|
||||||
|
// LastTransit 上一次地心金星凌日 / previous geocentric Venus transit.
|
||||||
|
func LastTransit(date time.Time) TransitInfo {
|
||||||
|
return transitInfoFromBasic(basic.LastVenusTransit(basic.Date2JDE(date.UTC())), date.Location())
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClosestTransit 最近一次地心金星凌日 / closest geocentric Venus transit.
|
||||||
|
func ClosestTransit(date time.Time) TransitInfo {
|
||||||
|
return transitInfoFromBasic(basic.ClosestVenusTransit(basic.Date2JDE(date.UTC())), date.Location())
|
||||||
|
}
|
||||||
|
|
||||||
|
func transitInfoFromBasic(result basic.PlanetTransitResult, loc *time.Location) TransitInfo {
|
||||||
|
if !result.Valid {
|
||||||
|
return TransitInfo{}
|
||||||
|
}
|
||||||
|
start := basic.JDE2DateByZone(result.ExternalIngress, loc, false)
|
||||||
|
greatest := basic.JDE2DateByZone(result.Greatest, loc, false)
|
||||||
|
end := basic.JDE2DateByZone(result.ExternalEgress, loc, false)
|
||||||
|
info := TransitInfo{
|
||||||
|
Valid: true,
|
||||||
|
Start: start,
|
||||||
|
Greatest: greatest,
|
||||||
|
End: end,
|
||||||
|
Duration: end.Sub(start),
|
||||||
|
MinimumSeparationArcsec: result.MinimumSeparationArcsec,
|
||||||
|
SunSemidiameterArcsec: result.SunSemidiameterArcsec,
|
||||||
|
PlanetSemidiameterArcsec: result.PlanetSemidiameterArcsec,
|
||||||
|
HasInternal: result.HasInternal,
|
||||||
|
}
|
||||||
|
if result.HasInternal {
|
||||||
|
info.InternalStart = basic.JDE2DateByZone(result.InternalIngress, loc, false)
|
||||||
|
info.InternalEnd = basic.JDE2DateByZone(result.InternalEgress, loc, false)
|
||||||
|
info.InternalDuration = info.InternalEnd.Sub(info.InternalStart)
|
||||||
|
}
|
||||||
|
return info
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
package venus
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestTransitWrappers(t *testing.T) {
|
||||||
|
loc := time.FixedZone("CST", 8*3600)
|
||||||
|
info := NextTransit(time.Date(2012, 1, 1, 0, 0, 0, 0, loc))
|
||||||
|
if !info.Valid {
|
||||||
|
t.Fatal("expected valid transit")
|
||||||
|
}
|
||||||
|
if info.Greatest.Location() != loc {
|
||||||
|
t.Fatalf("timezone mismatch: got %v want %v", info.Greatest.Location(), loc)
|
||||||
|
}
|
||||||
|
if info.Greatest.Year() != 2012 || info.Greatest.Month() != time.June || info.Greatest.Day() != 6 {
|
||||||
|
t.Fatalf("unexpected greatest time: %s", info.Greatest)
|
||||||
|
}
|
||||||
|
if !info.HasInternal || info.Duration <= 0 || info.InternalDuration <= 0 {
|
||||||
|
t.Fatalf("unexpected durations: %+v", info)
|
||||||
|
}
|
||||||
|
}
|
||||||
+34
-52
@@ -207,8 +207,7 @@ func SetTime(date time.Time, lon, lat, height float64, aero bool) (time.Time, er
|
|||||||
|
|
||||||
// LastConjunction 上一次合日 / previous conjunction with the Sun.
|
// LastConjunction 上一次合日 / previous conjunction with the Sun.
|
||||||
//
|
//
|
||||||
// 返回 date 之前最近一次与太阳的合日时刻,结果保持 date 的时区。
|
// 返回 date 当前或之前最近一次与太阳的合日时刻,结果保持 date 的时区。
|
||||||
// Returns the most recent conjunction with the Sun relative to date, keeping date's time zone.
|
|
||||||
func LastConjunction(date time.Time) time.Time {
|
func LastConjunction(date time.Time) time.Time {
|
||||||
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
|
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
|
||||||
return basic.JDE2DateByZone(basic.LastVenusConjunction(jde), date.Location(), false)
|
return basic.JDE2DateByZone(basic.LastVenusConjunction(jde), date.Location(), false)
|
||||||
@@ -216,8 +215,7 @@ func LastConjunction(date time.Time) time.Time {
|
|||||||
|
|
||||||
// NextConjunction 下一次合日 / next conjunction with the Sun.
|
// NextConjunction 下一次合日 / next conjunction with the Sun.
|
||||||
//
|
//
|
||||||
// 返回 date 之后最近一次与太阳的合日时刻,结果保持 date 的时区。
|
// 返回 date 当前或之后最近一次与太阳的合日时刻,结果保持 date 的时区。
|
||||||
// Returns the next conjunction with the Sun relative to date, keeping date's time zone.
|
|
||||||
func NextConjunction(date time.Time) time.Time {
|
func NextConjunction(date time.Time) time.Time {
|
||||||
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
|
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
|
||||||
return basic.JDE2DateByZone(basic.NextVenusConjunction(jde), date.Location(), false)
|
return basic.JDE2DateByZone(basic.NextVenusConjunction(jde), date.Location(), false)
|
||||||
@@ -225,144 +223,128 @@ func NextConjunction(date time.Time) time.Time {
|
|||||||
|
|
||||||
// LastInferiorConjunction 上一次下合 / previous inferior conjunction.
|
// LastInferiorConjunction 上一次下合 / previous inferior conjunction.
|
||||||
//
|
//
|
||||||
// 返回 date 之前最近一次下合时刻,结果保持 date 的时区。
|
// 返回 date 当前或之前最近一次下合时刻,结果保持 date 的时区。
|
||||||
// Returns the most recent inferior conjunction relative to date, keeping date's time zone.
|
|
||||||
func LastInferiorConjunction(date time.Time) time.Time {
|
func LastInferiorConjunction(date time.Time) time.Time {
|
||||||
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
|
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
|
||||||
return basic.JDE2DateByZone(basic.LastVenusInferiorConjunction(jde), date.Location(), false)
|
return basic.JDE2DateByZone(basic.LastVenusInferiorConjunctionInclusive(jde), date.Location(), false)
|
||||||
}
|
}
|
||||||
|
|
||||||
// NextInferiorConjunction 下一次下合 / next inferior conjunction.
|
// NextInferiorConjunction 下一次下合 / next inferior conjunction.
|
||||||
//
|
//
|
||||||
// 返回 date 之后最近一次下合时刻,结果保持 date 的时区。
|
// 返回 date 当前或之后最近一次下合时刻,结果保持 date 的时区。
|
||||||
// Returns the next inferior conjunction relative to date, keeping date's time zone.
|
|
||||||
func NextInferiorConjunction(date time.Time) time.Time {
|
func NextInferiorConjunction(date time.Time) time.Time {
|
||||||
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
|
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
|
||||||
return basic.JDE2DateByZone(basic.NextVenusInferiorConjunction(jde), date.Location(), false)
|
return basic.JDE2DateByZone(basic.NextVenusInferiorConjunctionInclusive(jde), date.Location(), false)
|
||||||
}
|
}
|
||||||
|
|
||||||
// LastSuperiorConjunction 上一次上合 / previous superior conjunction.
|
// LastSuperiorConjunction 上一次上合 / previous superior conjunction.
|
||||||
//
|
//
|
||||||
// 返回 date 之前最近一次上合时刻,结果保持 date 的时区。
|
// 返回 date 当前或之前最近一次上合时刻,结果保持 date 的时区。
|
||||||
// Returns the most recent superior conjunction relative to date, keeping date's time zone.
|
|
||||||
func LastSuperiorConjunction(date time.Time) time.Time {
|
func LastSuperiorConjunction(date time.Time) time.Time {
|
||||||
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
|
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
|
||||||
return basic.JDE2DateByZone(basic.LastVenusSuperiorConjunction(jde), date.Location(), false)
|
return basic.JDE2DateByZone(basic.LastVenusSuperiorConjunctionInclusive(jde), date.Location(), false)
|
||||||
}
|
}
|
||||||
|
|
||||||
// NextSuperiorConjunction 下一次上合 / next superior conjunction.
|
// NextSuperiorConjunction 下一次上合 / next superior conjunction.
|
||||||
//
|
//
|
||||||
// 返回 date 之后最近一次上合时刻,结果保持 date 的时区。
|
// 返回 date 当前或之后最近一次上合时刻,结果保持 date 的时区。
|
||||||
// Returns the next superior conjunction relative to date, keeping date's time zone.
|
|
||||||
func NextSuperiorConjunction(date time.Time) time.Time {
|
func NextSuperiorConjunction(date time.Time) time.Time {
|
||||||
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
|
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
|
||||||
return basic.JDE2DateByZone(basic.NextVenusSuperiorConjunction(jde), date.Location(), false)
|
return basic.JDE2DateByZone(basic.NextVenusSuperiorConjunctionInclusive(jde), date.Location(), false)
|
||||||
}
|
}
|
||||||
|
|
||||||
// LastRetrograde 上一次留 / previous stationary point.
|
// LastRetrograde 上一次留 / previous stationary point.
|
||||||
//
|
//
|
||||||
// 返回 date 之前最近一次留时刻,不区分顺转逆还是逆转顺,结果保持 date 的时区。
|
// 返回 date 当前或之前最近一次留时刻,不区分顺转逆还是逆转顺,结果保持 date 的时区。
|
||||||
// Returns the most recent stationary point relative to date without distinguishing direction, keeping date's time zone.
|
|
||||||
func LastRetrograde(date time.Time) time.Time {
|
func LastRetrograde(date time.Time) time.Time {
|
||||||
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
|
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
|
||||||
return basic.JDE2DateByZone(basic.LastVenusRetrograde(jde), date.Location(), false)
|
return basic.JDE2DateByZone(basic.LastVenusRetrogradeInclusive(jde), date.Location(), false)
|
||||||
}
|
}
|
||||||
|
|
||||||
// NextRetrograde 下一次留 / next stationary point.
|
// NextRetrograde 下一次留 / next stationary point.
|
||||||
//
|
//
|
||||||
// 返回 date 之后最近一次留时刻,不区分顺转逆还是逆转顺,结果保持 date 的时区。
|
// 返回 date 当前或之后最近一次留时刻,不区分顺转逆还是逆转顺,结果保持 date 的时区。
|
||||||
// Returns the next stationary point relative to date without distinguishing direction, keeping date's time zone.
|
|
||||||
func NextRetrograde(date time.Time) time.Time {
|
func NextRetrograde(date time.Time) time.Time {
|
||||||
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
|
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
|
||||||
return basic.JDE2DateByZone(basic.NextVenusRetrograde(jde), date.Location(), false)
|
return basic.JDE2DateByZone(basic.NextVenusRetrogradeInclusive(jde), date.Location(), false)
|
||||||
}
|
}
|
||||||
|
|
||||||
// LastProgradeToRetrograde 上一次顺行转逆行留 / previous station from prograde to retrograde.
|
// LastProgradeToRetrograde 上一次顺行转逆行留 / previous station from prograde to retrograde.
|
||||||
//
|
//
|
||||||
// 返回 date 之前最近一次由顺行转为逆行的留时刻,结果保持 date 的时区。
|
// 返回 date 当前或之前最近一次由顺行转为逆行的留时刻,结果保持 date 的时区。
|
||||||
// Returns the most recent stationary point where motion changes from prograde to retrograde relative to date, keeping date's time zone.
|
|
||||||
func LastProgradeToRetrograde(date time.Time) time.Time {
|
func LastProgradeToRetrograde(date time.Time) time.Time {
|
||||||
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
|
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
|
||||||
return basic.JDE2DateByZone(basic.LastVenusProgradeToRetrograde(jde), date.Location(), false)
|
return basic.JDE2DateByZone(basic.LastVenusProgradeToRetrogradeInclusive(jde), date.Location(), false)
|
||||||
}
|
}
|
||||||
|
|
||||||
// NextProgradeToRetrograde 下一次顺行转逆行留 / next station from prograde to retrograde.
|
// NextProgradeToRetrograde 下一次顺行转逆行留 / next station from prograde to retrograde.
|
||||||
//
|
//
|
||||||
// 返回 date 之后最近一次由顺行转为逆行的留时刻,结果保持 date 的时区。
|
// 返回 date 当前或之后最近一次由顺行转为逆行的留时刻,结果保持 date 的时区。
|
||||||
// Returns the next stationary point where motion changes from prograde to retrograde relative to date, keeping date's time zone.
|
|
||||||
func NextProgradeToRetrograde(date time.Time) time.Time {
|
func NextProgradeToRetrograde(date time.Time) time.Time {
|
||||||
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
|
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
|
||||||
return basic.JDE2DateByZone(basic.NextVenusProgradeToRetrograde(jde), date.Location(), false)
|
return basic.JDE2DateByZone(basic.NextVenusProgradeToRetrogradeInclusive(jde), date.Location(), false)
|
||||||
}
|
}
|
||||||
|
|
||||||
// LastRetrogradeToPrograde 上一次逆行转顺行留 / previous station from retrograde to prograde.
|
// LastRetrogradeToPrograde 上一次逆行转顺行留 / previous station from retrograde to prograde.
|
||||||
//
|
//
|
||||||
// 返回 date 之前最近一次由逆行转为顺行的留时刻,结果保持 date 的时区。
|
// 返回 date 当前或之前最近一次由逆行转为顺行的留时刻,结果保持 date 的时区。
|
||||||
// Returns the most recent stationary point where motion changes from retrograde to prograde relative to date, keeping date's time zone.
|
|
||||||
func LastRetrogradeToPrograde(date time.Time) time.Time {
|
func LastRetrogradeToPrograde(date time.Time) time.Time {
|
||||||
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
|
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
|
||||||
return basic.JDE2DateByZone(basic.LastVenusRetrogradeToPrograde(jde), date.Location(), false)
|
return basic.JDE2DateByZone(basic.LastVenusRetrogradeToProgradeInclusive(jde), date.Location(), false)
|
||||||
}
|
}
|
||||||
|
|
||||||
// NextRetrogradeToPrograde 下一次逆行转顺行留 / next station from retrograde to prograde.
|
// NextRetrogradeToPrograde 下一次逆行转顺行留 / next station from retrograde to prograde.
|
||||||
//
|
//
|
||||||
// 返回 date 之后最近一次由逆行转为顺行的留时刻,结果保持 date 的时区。
|
// 返回 date 当前或之后最近一次由逆行转为顺行的留时刻,结果保持 date 的时区。
|
||||||
// Returns the next stationary point where motion changes from retrograde to prograde relative to date, keeping date's time zone.
|
|
||||||
func NextRetrogradeToPrograde(date time.Time) time.Time {
|
func NextRetrogradeToPrograde(date time.Time) time.Time {
|
||||||
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
|
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
|
||||||
return basic.JDE2DateByZone(basic.NextVenusRetrogradeToPrograde(jde), date.Location(), false)
|
return basic.JDE2DateByZone(basic.NextVenusRetrogradeToProgradeInclusive(jde), date.Location(), false)
|
||||||
}
|
}
|
||||||
|
|
||||||
// LastGreatestElongation 上一次大距 / previous greatest elongation.
|
// LastGreatestElongation 上一次大距 / previous greatest elongation.
|
||||||
//
|
//
|
||||||
// 返回 date 之前最近一次大距时刻,不区分东西大距,结果保持 date 的时区。
|
// 返回 date 当前或之前最近一次大距时刻,不区分东西大距,结果保持 date 的时区。
|
||||||
// Returns the most recent greatest elongation relative to date without distinguishing east or west, keeping date's time zone.
|
|
||||||
func LastGreatestElongation(date time.Time) time.Time {
|
func LastGreatestElongation(date time.Time) time.Time {
|
||||||
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
|
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
|
||||||
return basic.JDE2DateByZone(basic.LastVenusGreatestElongation(jde), date.Location(), false)
|
return basic.JDE2DateByZone(basic.LastVenusGreatestElongationInclusive(jde), date.Location(), false)
|
||||||
}
|
}
|
||||||
|
|
||||||
// NextGreatestElongation 下一次大距 / next greatest elongation.
|
// NextGreatestElongation 下一次大距 / next greatest elongation.
|
||||||
//
|
//
|
||||||
// 返回 date 之后最近一次大距时刻,不区分东西大距,结果保持 date 的时区。
|
// 返回 date 当前或之后最近一次大距时刻,不区分东西大距,结果保持 date 的时区。
|
||||||
// Returns the next greatest elongation relative to date without distinguishing east or west, keeping date's time zone.
|
|
||||||
func NextGreatestElongation(date time.Time) time.Time {
|
func NextGreatestElongation(date time.Time) time.Time {
|
||||||
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
|
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
|
||||||
return basic.JDE2DateByZone(basic.NextVenusGreatestElongation(jde), date.Location(), false)
|
return basic.JDE2DateByZone(basic.NextVenusGreatestElongationInclusive(jde), date.Location(), false)
|
||||||
}
|
}
|
||||||
|
|
||||||
// LastGreatestElongationEast 上一次东大距 / previous greatest eastern elongation.
|
// LastGreatestElongationEast 上一次东大距 / previous greatest eastern elongation.
|
||||||
//
|
//
|
||||||
// 返回 date 之前最近一次东大距时刻,结果保持 date 的时区。
|
// 返回 date 当前或之前最近一次东大距时刻,结果保持 date 的时区。
|
||||||
// Returns the most recent greatest eastern elongation relative to date, keeping date's time zone.
|
|
||||||
func LastGreatestElongationEast(date time.Time) time.Time {
|
func LastGreatestElongationEast(date time.Time) time.Time {
|
||||||
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
|
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
|
||||||
return basic.JDE2DateByZone(basic.LastVenusGreatestElongationEast(jde), date.Location(), false)
|
return basic.JDE2DateByZone(basic.LastVenusGreatestElongationEastInclusive(jde), date.Location(), false)
|
||||||
}
|
}
|
||||||
|
|
||||||
// NextGreatestElongationEast 下一次东大距 / next greatest eastern elongation.
|
// NextGreatestElongationEast 下一次东大距 / next greatest eastern elongation.
|
||||||
//
|
//
|
||||||
// 返回 date 之后最近一次东大距时刻,结果保持 date 的时区。
|
// 返回 date 当前或之后最近一次东大距时刻,结果保持 date 的时区。
|
||||||
// Returns the next greatest eastern elongation relative to date, keeping date's time zone.
|
|
||||||
func NextGreatestElongationEast(date time.Time) time.Time {
|
func NextGreatestElongationEast(date time.Time) time.Time {
|
||||||
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
|
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
|
||||||
return basic.JDE2DateByZone(basic.NextVenusGreatestElongationEast(jde), date.Location(), false)
|
return basic.JDE2DateByZone(basic.NextVenusGreatestElongationEastInclusive(jde), date.Location(), false)
|
||||||
}
|
}
|
||||||
|
|
||||||
// LastGreatestElongationWest 上一次西大距 / previous greatest western elongation.
|
// LastGreatestElongationWest 上一次西大距 / previous greatest western elongation.
|
||||||
//
|
//
|
||||||
// 返回 date 之前最近一次西大距时刻,结果保持 date 的时区。
|
// 返回 date 当前或之前最近一次西大距时刻,结果保持 date 的时区。
|
||||||
// Returns the most recent greatest western elongation relative to date, keeping date's time zone.
|
|
||||||
func LastGreatestElongationWest(date time.Time) time.Time {
|
func LastGreatestElongationWest(date time.Time) time.Time {
|
||||||
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
|
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
|
||||||
return basic.JDE2DateByZone(basic.LastVenusGreatestElongationWest(jde), date.Location(), false)
|
return basic.JDE2DateByZone(basic.LastVenusGreatestElongationWestInclusive(jde), date.Location(), false)
|
||||||
}
|
}
|
||||||
|
|
||||||
// NextGreatestElongationWest 下一次西大距 / next greatest western elongation.
|
// NextGreatestElongationWest 下一次西大距 / next greatest western elongation.
|
||||||
//
|
//
|
||||||
// 返回 date 之后最近一次西大距时刻,结果保持 date 的时区。
|
// 返回 date 当前或之后最近一次西大距时刻,结果保持 date 的时区。
|
||||||
// Returns the next greatest western elongation relative to date, keeping date's time zone.
|
|
||||||
func NextGreatestElongationWest(date time.Time) time.Time {
|
func NextGreatestElongationWest(date time.Time) time.Time {
|
||||||
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
|
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
|
||||||
return basic.JDE2DateByZone(basic.NextVenusGreatestElongationWest(jde), date.Location(), false)
|
return basic.JDE2DateByZone(basic.NextVenusGreatestElongationWestInclusive(jde), date.Location(), false)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user