Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
c8dd777a7b
|
|||
|
46b555cd49
|
|||
|
be3af3884c
|
|||
|
34ff6a36ae
|
|||
|
d40c4dfcd9
|
+73
-76
@@ -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:
|
||||||
@@ -1005,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
|
||||||
@@ -1156,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.
|
||||||
@@ -1237,22 +1234,22 @@ Output:
|
|||||||
|
|
||||||
```text
|
```text
|
||||||
true // a valid geocentric Mercury transit was found
|
true // a valid geocentric Mercury transit was found
|
||||||
2019-11-11 12:35:31.637522578 +0000 UTC // first contact: Mercury externally enters the solar disk
|
2019-11-11 12:35:31.617325544 +0000 UTC // first contact: Mercury externally enters the solar disk
|
||||||
2019-11-11 12:37:12.887506484 +0000 UTC // second contact: Mercury is fully inside 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.430488109 +0000 UTC // greatest transit: Mercury center is closest to the Sun center
|
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.246907234 +0000 UTC // third contact: Mercury starts leaving the solar disk
|
2019-11-11 18:02:29.2267102 +0000 UTC // third contact: Mercury starts leaving the solar disk
|
||||||
2019-11-11 18:04:10.707873702 +0000 UTC // fourth contact: Mercury externally leaves 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
|
5h28m39.070351124s // geocentric transit duration from first to fourth contact
|
||||||
75.92460219695154 // minimum Mercury-Sun center separation at greatest transit, arcseconds
|
75.92506897631685 // minimum Mercury-Sun center separation at greatest transit, arcseconds
|
||||||
968.8881521396688 // solar semidiameter at greatest transit, arcseconds
|
968.8881520858397 // solar semidiameter at greatest transit, arcseconds
|
||||||
4.978442856283907 // Mercury semidiameter at greatest transit, arcseconds
|
4.978442860728242 // Mercury semidiameter at greatest transit, arcseconds
|
||||||
true // a valid geocentric Venus transit was found
|
true // a valid geocentric Venus transit was found
|
||||||
2012-06-05 22:09:47.581470608 +0000 UTC // first contact: Venus externally enters the solar disk
|
2012-06-05 22:09:47.514281272 +0000 UTC // first contact: Venus externally enters the solar disk
|
||||||
2012-06-05 22:27:35.979940295 +0000 UTC // second contact: Venus is fully inside 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.686955451 +0000 UTC // greatest transit: Venus center is closest to the Sun center
|
2012-06-06 01:29:35.408823788 +0000 UTC // greatest transit: Venus center is closest to the Sun center
|
||||||
2012-06-06 04:31:35.18302828 +0000 UTC // third contact: Venus starts leaving the solar disk
|
2012-06-06 04:31:34.90493685 +0000 UTC // third contact: Venus starts leaving the solar disk
|
||||||
2012-06-06 04:49:23.581457734 +0000 UTC // fourth contact: Venus externally leaves the solar disk
|
2012-06-06 04:49:23.303366303 +0000 UTC // fourth contact: Venus externally leaves the solar disk
|
||||||
6h39m35.999987126s // geocentric transit duration from first to fourth contact
|
6h39m35.789085031s // geocentric transit duration from first to fourth contact
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Outer planets
|
#### Outer planets
|
||||||
@@ -1315,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.
|
||||||
@@ -1376,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:
|
||||||
@@ -1539,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
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -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,例如:
|
||||||
@@ -1074,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 数据对照
|
||||||
@@ -1232,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` 版),返回地心视直径/视半径,单位为角秒。
|
||||||
@@ -1313,22 +1309,22 @@ func main() {
|
|||||||
|
|
||||||
```text
|
```text
|
||||||
true // 找到一次有效的地心水星凌日
|
true // 找到一次有效的地心水星凌日
|
||||||
2019-11-11 12:35:31.637522578 +0000 UTC // 一触:水星外切进入太阳圆面
|
2019-11-11 12:35:31.617325544 +0000 UTC // 一触:水星外切进入太阳圆面
|
||||||
2019-11-11 12:37:12.887506484 +0000 UTC // 二触:水星完全进入太阳圆面
|
2019-11-11 12:37:13.078211545 +0000 UTC // 二触:水星完全进入太阳圆面
|
||||||
2019-11-11 15:19:48.430488109 +0000 UTC // 凌甚:水星中心最接近太阳中心
|
2019-11-11 15:19:48.410291075 +0000 UTC // 凌甚:水星中心最接近太阳中心
|
||||||
2019-11-11 18:02:29.246907234 +0000 UTC // 三触:水星开始离开太阳圆面
|
2019-11-11 18:02:29.2267102 +0000 UTC // 三触:水星开始离开太阳圆面
|
||||||
2019-11-11 18:04:10.707873702 +0000 UTC // 四触:水星外切离开太阳圆面
|
2019-11-11 18:04:10.687676668 +0000 UTC // 四触:水星外切离开太阳圆面
|
||||||
5h28m39.070351124s // 一触到四触的地心凌日持续时间
|
5h28m39.070351124s // 一触到四触的地心凌日持续时间
|
||||||
75.92460219695154 // 凌甚时水星中心与太阳中心的最小角距离,单位角秒
|
75.92506897631685 // 凌甚时水星中心与太阳中心的最小角距离,单位角秒
|
||||||
968.8881521396688 // 凌甚时太阳视半径,单位角秒
|
968.8881520858397 // 凌甚时太阳视半径,单位角秒
|
||||||
4.978442856283907 // 凌甚时水星视半径,单位角秒
|
4.978442860728242 // 凌甚时水星视半径,单位角秒
|
||||||
true // 找到一次有效的地心金星凌日
|
true // 找到一次有效的地心金星凌日
|
||||||
2012-06-05 22:09:47.581470608 +0000 UTC // 一触:金星外切进入太阳圆面
|
2012-06-05 22:09:47.514281272 +0000 UTC // 一触:金星外切进入太阳圆面
|
||||||
2012-06-05 22:27:35.979940295 +0000 UTC // 二触:金星完全进入太阳圆面
|
2012-06-05 22:27:35.701768398 +0000 UTC // 二触:金星完全进入太阳圆面
|
||||||
2012-06-06 01:29:35.686955451 +0000 UTC // 凌甚:金星中心最接近太阳中心
|
2012-06-06 01:29:35.408823788 +0000 UTC // 凌甚:金星中心最接近太阳中心
|
||||||
2012-06-06 04:31:35.18302828 +0000 UTC // 三触:金星开始离开太阳圆面
|
2012-06-06 04:31:34.90493685 +0000 UTC // 三触:金星开始离开太阳圆面
|
||||||
2012-06-06 04:49:23.581457734 +0000 UTC // 四触:金星外切离开太阳圆面
|
2012-06-06 04:49:23.303366303 +0000 UTC // 四触:金星外切离开太阳圆面
|
||||||
6h39m35.999987126s // 一触到四触的地心凌日持续时间
|
6h39m35.789085031s // 一触到四触的地心凌日持续时间
|
||||||
```
|
```
|
||||||
|
|
||||||
#### 外行星
|
#### 外行星
|
||||||
@@ -1390,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
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -1452,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`:
|
||||||
@@ -1619,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 // 最亮恒星表第一项:中文名、英文常用名、视星等
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,79 @@
|
|||||||
|
package basic
|
||||||
|
|
||||||
|
import "math"
|
||||||
|
|
||||||
|
const (
|
||||||
|
exactEventTolerance = 2.0 / 86400.0
|
||||||
|
exactQueryTTToleranceUT = 0.1 / 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)) <= exactQueryTTToleranceUT
|
||||||
|
}
|
||||||
|
|
||||||
|
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,187 @@
|
|||||||
|
package basic
|
||||||
|
|
||||||
|
import "math"
|
||||||
|
|
||||||
|
const (
|
||||||
|
innerEventEpsilon = 0.1 / 86400.0
|
||||||
|
innerEventWindowPadding = 4.0 / 86400.0
|
||||||
|
innerEventMaximizeEpsilon = 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 <= innerEventMaximizeEpsilon {
|
||||||
|
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,105 @@
|
|||||||
|
package basic
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestInnerPlanetNextEventAdvancesPastReturnedEvent(t *testing.T) {
|
||||||
|
cases := []struct {
|
||||||
|
name string
|
||||||
|
seed float64
|
||||||
|
next func(float64) float64
|
||||||
|
}{
|
||||||
|
{name: "MercuryConjunction", seed: ttjdUTC(2026, 5, 1, 0, 0, 0), next: NextMercuryConjunction},
|
||||||
|
{name: "MercuryInferior", seed: ttjdUTC(2026, 5, 1, 0, 0, 0), next: NextMercuryInferiorConjunction},
|
||||||
|
{name: "MercuryP2R", seed: ttjdUTC(2026, 5, 1, 0, 0, 0), next: NextMercuryProgradeToRetrograde},
|
||||||
|
{name: "MercuryEastElongation", seed: ttjdUTC(2026, 5, 1, 0, 0, 0), next: NextMercuryGreatestElongationEast},
|
||||||
|
{name: "VenusConjunction", seed: ttjdUTC(2026, 5, 1, 0, 0, 0), next: NextVenusConjunction},
|
||||||
|
{name: "VenusWestElongation", seed: ttjdUTC(2026, 5, 1, 0, 0, 0), next: NextVenusGreatestElongationWest},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range cases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
first := tc.next(tc.seed)
|
||||||
|
query := TD2UT(Date2JDE(JDE2DateByZone(first, time.UTC, false).Add(time.Second)), true)
|
||||||
|
next := tc.next(query)
|
||||||
|
if !eventUTQueryAfterOrEqual(next, query) {
|
||||||
|
t.Fatalf("next should be after query: first=%.12f query=%.12f next=%.12f", first, query, next)
|
||||||
|
}
|
||||||
|
if sameEventJD(next, first) {
|
||||||
|
t.Fatalf("next should advance past first event: first=%.12f next=%.12f", first, next)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestInnerPlanetTypedConjunctionExactBoundaryIncludesCurrent(t *testing.T) {
|
||||||
|
cases := []struct {
|
||||||
|
name string
|
||||||
|
seed float64
|
||||||
|
next func(float64) float64
|
||||||
|
last func(float64) float64
|
||||||
|
}{
|
||||||
|
{name: "MercuryInferior", seed: NextMercuryInferiorConjunction(ttjdUTC(2026, 1, 1, 0, 0, 0)), next: NextMercuryInferiorConjunction, last: LastMercuryInferiorConjunction},
|
||||||
|
{name: "MercurySuperior", seed: NextMercurySuperiorConjunction(ttjdUTC(2026, 1, 1, 0, 0, 0)), next: NextMercurySuperiorConjunction, last: LastMercurySuperiorConjunction},
|
||||||
|
{name: "VenusInferior", seed: NextVenusInferiorConjunction(ttjdUTC(2026, 1, 1, 0, 0, 0)), next: NextVenusInferiorConjunction, last: LastVenusInferiorConjunction},
|
||||||
|
{name: "VenusSuperior", seed: NextVenusSuperiorConjunction(ttjdUTC(2026, 1, 1, 0, 0, 0)), next: NextVenusSuperiorConjunction, last: LastVenusSuperiorConjunction},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range cases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
queryTT := TD2UT(tc.seed, true)
|
||||||
|
last := tc.last(queryTT)
|
||||||
|
next := tc.next(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 {
|
||||||
|
|||||||
+80
-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,92 @@ 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 stabilizeMarsStationNearQuery(jde, date float64, searchBeforeOpposition bool) float64 {
|
||||||
|
if math.Abs(eventUTQueryTTDelta(date, jde)) > exactEventTolerance {
|
||||||
|
return date
|
||||||
|
}
|
||||||
|
if searchBeforeOpposition {
|
||||||
|
stableOppositionJD := NextMarsOpposition(jde)
|
||||||
|
sameOppositionJD := marsOppositionFromAfter(stableOppositionJD)
|
||||||
|
return closestEventUTToQueryTT(jde, date, marsRetrogradeAroundOpposition(stableOppositionJD, true), marsRetrogradeAroundOpposition(sameOppositionJD, true))
|
||||||
|
}
|
||||||
|
stableOppositionJD := LastMarsOpposition(jde)
|
||||||
|
sameOppositionJD := marsOppositionFromBefore(stableOppositionJD)
|
||||||
|
return closestEventUTToQueryTT(jde, date, marsRetrogradeAroundOpposition(stableOppositionJD, false), marsRetrogradeAroundOpposition(sameOppositionJD, false))
|
||||||
|
}
|
||||||
|
|
||||||
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)
|
date = stabilizeMarsStationNearQuery(jde, date, false)
|
||||||
return marsRetrograde(oppositionJD+10, false)
|
if sameEventUTQueryTT(date, jde) {
|
||||||
|
stableOppositionJD := LastMarsOpposition(jde)
|
||||||
|
stableDate := marsRetrogradeAroundOpposition(stableOppositionJD, false)
|
||||||
|
sameOppositionJD := marsOppositionFromBefore(stableOppositionJD)
|
||||||
|
return closestEventUTToQueryTT(jde, date, stableDate, 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 {
|
date = stabilizeMarsStationNearQuery(jde, date, false)
|
||||||
oppositionJD := marsConjunctionFull(jde, 180, 0)
|
if sameEventUTQueryTT(date, jde) {
|
||||||
return marsRetrograde(oppositionJD-10, false)
|
stableOppositionJD := LastMarsOpposition(jde)
|
||||||
|
stableDate := marsRetrogradeAroundOpposition(stableOppositionJD, false)
|
||||||
|
sameOppositionJD := marsOppositionFromBefore(stableOppositionJD)
|
||||||
|
return closestEventUTToQueryTT(jde, date, stableDate, 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)
|
date = stabilizeMarsStationNearQuery(jde, date, true)
|
||||||
return marsRetrograde(oppositionJD+10, true)
|
if sameEventUTQueryTT(date, jde) {
|
||||||
|
stableOppositionJD := NextMarsOpposition(jde)
|
||||||
|
stableDate := marsRetrogradeAroundOpposition(stableOppositionJD, true)
|
||||||
|
sameOppositionJD := marsOppositionFromAfter(stableOppositionJD)
|
||||||
|
return closestEventUTToQueryTT(jde, date, stableDate, 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 {
|
date = stabilizeMarsStationNearQuery(jde, date, true)
|
||||||
oppositionJD := marsConjunctionFull(jde, 180, 0)
|
if sameEventUTQueryTT(date, jde) {
|
||||||
return marsRetrograde(oppositionJD-10, true)
|
stableOppositionJD := NextMarsOpposition(jde)
|
||||||
|
stableDate := marsRetrogradeAroundOpposition(stableOppositionJD, true)
|
||||||
|
sameOppositionJD := marsOppositionFromAfter(stableOppositionJD)
|
||||||
|
return closestEventUTToQueryTT(jde, date, stableDate, 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 {
|
||||||
|
|||||||
+288
-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 {
|
||||||
@@ -167,6 +168,26 @@ func mercuryConjunctionLegacy(jde float64, next uint8) float64 {
|
|||||||
|
|
||||||
func mercuryConjunction(jde float64, next uint8) float64 {
|
func mercuryConjunction(jde float64, next uint8) float64 {
|
||||||
//0=last 1=next
|
//0=last 1=next
|
||||||
|
if math.Abs(mercuryConjunctionExactDelta(jde)) <= 30.0/86400.0 {
|
||||||
|
best := math.NaN()
|
||||||
|
consider := func(inferior bool) {
|
||||||
|
eventUT := TD2UT(mercuryConjunctionExactTT(jde, inferior), false)
|
||||||
|
if next == 0 && !eventUTQueryBeforeOrEqual(eventUT, jde) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if next == 1 && !eventUTQueryAfterOrEqual(eventUT, jde) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if math.IsNaN(best) || math.Abs(eventUTQueryTTDelta(eventUT, jde)) < math.Abs(eventUTQueryTTDelta(best, jde)) {
|
||||||
|
best = eventUT
|
||||||
|
}
|
||||||
|
}
|
||||||
|
consider(true)
|
||||||
|
consider(false)
|
||||||
|
if !math.IsNaN(best) {
|
||||||
|
return best
|
||||||
|
}
|
||||||
|
}
|
||||||
currentDelta := mercuryConjunctionExactDelta(jde)
|
currentDelta := mercuryConjunctionExactDelta(jde)
|
||||||
// pos 大于0:远离太阳 小于0:靠近太阳
|
// pos 大于0:远离太阳 小于0:靠近太阳
|
||||||
distanceTrend := math.Abs(mercuryConjunctionExactDelta(jde+1/86400.0)) - math.Abs(currentDelta)
|
distanceTrend := math.Abs(mercuryConjunctionExactDelta(jde+1/86400.0)) - math.Abs(currentDelta)
|
||||||
@@ -206,41 +227,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 +286,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 +295,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 +305,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 LastMercuryRetrogradeStrict(jde float64) float64 {
|
||||||
|
return LastMercuryRetrograde(jde)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NextMercuryRetrogradeStrict(jde float64) float64 {
|
||||||
|
return NextMercuryRetrograde(jde)
|
||||||
|
}
|
||||||
|
|
||||||
func NextMercuryProgradeToRetrograde(jde float64) float64 {
|
func NextMercuryProgradeToRetrograde(jde float64) float64 {
|
||||||
date := NextMercuryRetrograde(jde)
|
return nextMercuryTypedStation(jde, true)
|
||||||
sub := Limit360(MercuryApparentRa(date) - SunApparentRa(date))
|
|
||||||
if sub > 180 {
|
|
||||||
return NextMercuryRetrograde(date + MERCURY_S_PERIOD/2)
|
|
||||||
}
|
|
||||||
return date
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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 + innerEventWindowPadding, inferior - innerEventWindowPadding
|
||||||
|
}
|
||||||
|
|
||||||
|
func mercuryWestElongationWindowEndingAt(superior float64) (float64, float64) {
|
||||||
|
lastInferior := LastMercuryInferiorConjunction(eventUTLastQueryTT(superior))
|
||||||
|
return lastInferior + innerEventWindowPadding, superior - innerEventWindowPadding
|
||||||
|
}
|
||||||
|
|
||||||
|
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 +532,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 +566,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))
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
package basic
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type moonGeocentricApparentSample struct {
|
||||||
|
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 TestMoonGeocentricApparentCoordinatesMatchHorizonsBaseline(t *testing.T) {
|
||||||
|
data, err := os.ReadFile("testdata/moon_geocentric_apparent_baseline.json")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("read baseline: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var samples []moonGeocentricApparentSample
|
||||||
|
if err := json.Unmarshal(data, &samples); err != nil {
|
||||||
|
t.Fatalf("decode baseline: %v", err)
|
||||||
|
}
|
||||||
|
if len(samples) == 0 {
|
||||||
|
t.Fatal("empty moon apparent baseline")
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, sample := range samples {
|
||||||
|
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 := "moon." + sample.InputUTC
|
||||||
|
|
||||||
|
assertPlanetApparentAngleClose(t, prefix+".RightAscension", HMoonGeocentricApparentRa(jd), sample.RightAscension, 0.001)
|
||||||
|
assertPlanetPhaseClose(t, prefix+".Declination", HMoonGeocentricApparentDec(jd), sample.Declination, 0.001)
|
||||||
|
assertPlanetApparentAngleClose(t, prefix+".EclipticLongitude", HMoonApparentLo(jd), sample.EclipticLongitude, 0.001)
|
||||||
|
assertPlanetPhaseClose(t, prefix+".EclipticLatitude", HMoonTrueBo(jd), sample.EclipticLatitude, 0.001)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMoonGeocentricTrueCoordinatesFollowDefinition(t *testing.T) {
|
||||||
|
samples := []time.Time{
|
||||||
|
time.Date(1900, 1, 14, 12, 0, 0, 0, time.UTC),
|
||||||
|
time.Date(1950, 6, 3, 0, 0, 0, 0, time.UTC),
|
||||||
|
time.Date(2000, 2, 29, 18, 0, 0, 0, time.UTC),
|
||||||
|
time.Date(2026, 1, 1, 6, 0, 0, 0, time.UTC),
|
||||||
|
time.Date(2100, 8, 17, 9, 0, 0, 0, time.UTC),
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, sample := range samples {
|
||||||
|
jd := TD2UT(Date2JDE(sample.UTC()), true)
|
||||||
|
wantRA, wantDec := LoBoToRaDec(jd, HMoonTrueLo(jd), HMoonTrueBo(jd))
|
||||||
|
gotRA, gotDec := HMoonGeocentricTrueRaDec(jd)
|
||||||
|
|
||||||
|
assertPlanetApparentAngleClose(t, sample.Format(time.RFC3339)+".TrueRightAscension", gotRA, wantRA, 1e-12)
|
||||||
|
assertPlanetPhaseClose(t, sample.Format(time.RFC3339)+".TrueDeclination", gotDec, wantDec, 1e-12)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
package basic
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestHMoonGeocentricApparentRaDecComponentsMatch(t *testing.T) {
|
||||||
|
jd := TD2UT(JDECalc(2026, 1, 1.25), true)
|
||||||
|
|
||||||
|
ra, dec := HMoonGeocentricApparentRaDec(jd)
|
||||||
|
if diff := math.Abs(ra - HMoonGeocentricApparentRa(jd)); diff > 1e-12 {
|
||||||
|
t.Fatalf("RA pair mismatch: got %.15f want %.15f", ra, HMoonGeocentricApparentRa(jd))
|
||||||
|
}
|
||||||
|
if diff := math.Abs(dec - HMoonGeocentricApparentDec(jd)); diff > 1e-12 {
|
||||||
|
t.Fatalf("Dec pair mismatch: got %.15f want %.15f", dec, HMoonGeocentricApparentDec(jd))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHMoonGeocentricTrueRaDecComponentsMatch(t *testing.T) {
|
||||||
|
jd := TD2UT(JDECalc(2026, 1, 1.25), true)
|
||||||
|
|
||||||
|
ra, dec := HMoonGeocentricTrueRaDec(jd)
|
||||||
|
if diff := math.Abs(ra - HMoonGeocentricTrueRa(jd)); diff > 1e-12 {
|
||||||
|
t.Fatalf("RA pair mismatch: got %.15f want %.15f", ra, HMoonGeocentricTrueRa(jd))
|
||||||
|
}
|
||||||
|
if diff := math.Abs(dec - HMoonGeocentricTrueDec(jd)); diff > 1e-12 {
|
||||||
|
t.Fatalf("Dec pair mismatch: got %.15f want %.15f", dec, HMoonGeocentricTrueDec(jd))
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,397 @@
|
|||||||
|
package basic
|
||||||
|
|
||||||
|
import "math"
|
||||||
|
|
||||||
|
const (
|
||||||
|
moonPlanetConjunctionEstimateN = 8
|
||||||
|
moonPlanetConjunctionNearQueryDeltaDeg = 3.0
|
||||||
|
moonPlanetConjunctionDirectionEpsilon = 0.1 / 86400.0
|
||||||
|
moonPlanetConjunctionBracketStepDays = 0.5
|
||||||
|
moonPlanetConjunctionNearQueryStepDays = 0.25
|
||||||
|
moonPlanetConjunctionNearQueryHalfSpan = 1.5
|
||||||
|
moonPlanetConjunctionBracketHalfSpan = 2.0
|
||||||
|
moonPlanetConjunctionBracketGrowth = 2.0
|
||||||
|
moonPlanetConjunctionBracketAttempts = 3
|
||||||
|
moonPlanetConjunctionRefineStepDays = 0.5 / 86400.0
|
||||||
|
moonPlanetConjunctionEventTolerance = 0.01
|
||||||
|
moonPlanetConjunctionFallbackSpanScale = 1.5
|
||||||
|
)
|
||||||
|
|
||||||
|
type moonPlanetConjunctionLocalResult struct {
|
||||||
|
lastUT float64
|
||||||
|
nextUT float64
|
||||||
|
}
|
||||||
|
|
||||||
|
func emptyMoonPlanetConjunctionLocalResult() moonPlanetConjunctionLocalResult {
|
||||||
|
return moonPlanetConjunctionLocalResult{
|
||||||
|
lastUT: math.NaN(),
|
||||||
|
nextUT: math.NaN(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MoonPlanetConjunctionPlanet 月球合月目标行星 / target planet for Moon-planet conjunction events.
|
||||||
|
type MoonPlanetConjunctionPlanet int
|
||||||
|
|
||||||
|
const (
|
||||||
|
MoonPlanetConjunctionMercury MoonPlanetConjunctionPlanet = iota + 1
|
||||||
|
MoonPlanetConjunctionVenus
|
||||||
|
MoonPlanetConjunctionMars
|
||||||
|
MoonPlanetConjunctionJupiter
|
||||||
|
MoonPlanetConjunctionSaturn
|
||||||
|
MoonPlanetConjunctionUranus
|
||||||
|
MoonPlanetConjunctionNeptune
|
||||||
|
)
|
||||||
|
|
||||||
|
func moonPlanetConjunctionWrappedDelta(diff float64) float64 {
|
||||||
|
diff = math.Mod(diff+180, 360)
|
||||||
|
if diff < 0 {
|
||||||
|
diff += 360
|
||||||
|
}
|
||||||
|
return diff - 180
|
||||||
|
}
|
||||||
|
|
||||||
|
func moonPlanetConjunctionDeltaAt(jdTT float64, planet MoonPlanetConjunctionPlanet, n int) float64 {
|
||||||
|
moonRA := HMoonGeocentricApparentRaN(jdTT, n)
|
||||||
|
var planetRA float64
|
||||||
|
switch planet {
|
||||||
|
case MoonPlanetConjunctionMercury:
|
||||||
|
planetRA = MercuryApparentRaN(jdTT, n)
|
||||||
|
case MoonPlanetConjunctionVenus:
|
||||||
|
planetRA = VenusApparentRaN(jdTT, n)
|
||||||
|
case MoonPlanetConjunctionMars:
|
||||||
|
planetRA = MarsApparentRaN(jdTT, n)
|
||||||
|
case MoonPlanetConjunctionJupiter:
|
||||||
|
planetRA = JupiterApparentRaN(jdTT, n)
|
||||||
|
case MoonPlanetConjunctionSaturn:
|
||||||
|
planetRA = SaturnApparentRaN(jdTT, n)
|
||||||
|
case MoonPlanetConjunctionUranus:
|
||||||
|
planetRA = UranusApparentRaN(jdTT, n)
|
||||||
|
case MoonPlanetConjunctionNeptune:
|
||||||
|
planetRA = NeptuneApparentRaN(jdTT, n)
|
||||||
|
default:
|
||||||
|
return math.NaN()
|
||||||
|
}
|
||||||
|
return moonPlanetConjunctionWrappedDelta(moonRA - planetRA)
|
||||||
|
}
|
||||||
|
|
||||||
|
func moonPlanetConjunctionPeriodDays(planet MoonPlanetConjunctionPlanet) float64 {
|
||||||
|
switch planet {
|
||||||
|
case MoonPlanetConjunctionMercury:
|
||||||
|
return 28.1
|
||||||
|
case MoonPlanetConjunctionVenus:
|
||||||
|
return 28.4
|
||||||
|
case MoonPlanetConjunctionMars:
|
||||||
|
return 29.2
|
||||||
|
case MoonPlanetConjunctionJupiter:
|
||||||
|
return 28.0
|
||||||
|
case MoonPlanetConjunctionSaturn:
|
||||||
|
return 27.4
|
||||||
|
case MoonPlanetConjunctionUranus:
|
||||||
|
return 27.3
|
||||||
|
case MoonPlanetConjunctionNeptune:
|
||||||
|
return 27.3
|
||||||
|
default:
|
||||||
|
return math.NaN()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func moonPlanetConjunctionBeforeOrEqual(eventUT, queryTT float64) bool {
|
||||||
|
return eventUTQueryTTDelta(eventUT, queryTT) <= moonPlanetConjunctionDirectionEpsilon
|
||||||
|
}
|
||||||
|
|
||||||
|
func moonPlanetConjunctionAfterOrEqual(eventUT, queryTT float64) bool {
|
||||||
|
return eventUTQueryTTDelta(eventUT, queryTT) >= -moonPlanetConjunctionDirectionEpsilon
|
||||||
|
}
|
||||||
|
|
||||||
|
func moonPlanetConjunctionInDirection(eventUT, queryTT float64, direction int) bool {
|
||||||
|
switch direction {
|
||||||
|
case -1:
|
||||||
|
return moonPlanetConjunctionBeforeOrEqual(eventUT, queryTT)
|
||||||
|
case 1:
|
||||||
|
return moonPlanetConjunctionAfterOrEqual(eventUT, queryTT)
|
||||||
|
default:
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func moonPlanetConjunctionFindBracket(centerTT, halfSpan, step float64, planet MoonPlanetConjunctionPlanet) (float64, float64, bool) {
|
||||||
|
if math.IsNaN(centerTT) || math.IsNaN(halfSpan) || math.IsNaN(step) || halfSpan <= 0 || step <= 0 {
|
||||||
|
return 0, 0, false
|
||||||
|
}
|
||||||
|
start := centerTT - halfSpan
|
||||||
|
end := centerTT + halfSpan
|
||||||
|
samples := int(math.Ceil((end-start)/step)) + 1
|
||||||
|
prevTT := start
|
||||||
|
prevVal := moonPlanetConjunctionDeltaAt(prevTT, planet, -1)
|
||||||
|
if math.IsNaN(prevVal) {
|
||||||
|
return 0, 0, false
|
||||||
|
}
|
||||||
|
if prevVal == 0 {
|
||||||
|
return prevTT, prevTT, true
|
||||||
|
}
|
||||||
|
bestLeft := 0.0
|
||||||
|
bestRight := 0.0
|
||||||
|
bestDistance := math.Inf(1)
|
||||||
|
for i := 1; i <= samples; i++ {
|
||||||
|
tt := start + float64(i)*step
|
||||||
|
if tt > end {
|
||||||
|
tt = end
|
||||||
|
}
|
||||||
|
val := moonPlanetConjunctionDeltaAt(tt, planet, -1)
|
||||||
|
if math.IsNaN(val) {
|
||||||
|
return 0, 0, false
|
||||||
|
}
|
||||||
|
if val == 0 {
|
||||||
|
return tt, tt, true
|
||||||
|
}
|
||||||
|
if prevVal*val < 0 {
|
||||||
|
mid := (prevTT + tt) / 2.0
|
||||||
|
distance := math.Abs(mid - centerTT)
|
||||||
|
if distance < bestDistance {
|
||||||
|
bestLeft = prevTT
|
||||||
|
bestRight = tt
|
||||||
|
bestDistance = distance
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if tt == end {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
prevTT = tt
|
||||||
|
prevVal = val
|
||||||
|
}
|
||||||
|
if math.IsInf(bestDistance, 1) {
|
||||||
|
return 0, 0, false
|
||||||
|
}
|
||||||
|
return bestLeft, bestRight, true
|
||||||
|
}
|
||||||
|
|
||||||
|
func moonPlanetConjunctionRefineBracket(leftTT, rightTT float64, planet MoonPlanetConjunctionPlanet) float64 {
|
||||||
|
if leftTT > rightTT {
|
||||||
|
leftTT, rightTT = rightTT, leftTT
|
||||||
|
}
|
||||||
|
if leftTT == rightTT {
|
||||||
|
return leftTT
|
||||||
|
}
|
||||||
|
center := (leftTT + rightTT) / 2.0
|
||||||
|
halfWindow := (rightTT - leftTT) / 2.0
|
||||||
|
return eventZeroRefine(center, halfWindow, moonPlanetConjunctionRefineStepDays, func(sampleTT float64) float64 {
|
||||||
|
return moonPlanetConjunctionDeltaAt(sampleTT, planet, -1)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func moonPlanetConjunctionEventUT(leftTT, rightTT float64, planet MoonPlanetConjunctionPlanet) float64 {
|
||||||
|
eventTT := moonPlanetConjunctionRefineBracket(leftTT, rightTT, planet)
|
||||||
|
if math.Abs(moonPlanetConjunctionDeltaAt(eventTT, planet, -1)) > moonPlanetConjunctionEventTolerance {
|
||||||
|
return math.NaN()
|
||||||
|
}
|
||||||
|
return TD2UT(eventTT, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
func moonPlanetConjunctionCollectLocalEvent(result *moonPlanetConjunctionLocalResult, queryTT, eventUT float64) {
|
||||||
|
if math.IsNaN(eventUT) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if moonPlanetConjunctionBeforeOrEqual(eventUT, queryTT) {
|
||||||
|
if math.IsNaN(result.lastUT) || math.Abs(eventUTQueryTTDelta(eventUT, queryTT)) < math.Abs(eventUTQueryTTDelta(result.lastUT, queryTT)) {
|
||||||
|
result.lastUT = eventUT
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if moonPlanetConjunctionAfterOrEqual(eventUT, queryTT) {
|
||||||
|
if math.IsNaN(result.nextUT) || math.Abs(eventUTQueryTTDelta(eventUT, queryTT)) < math.Abs(eventUTQueryTTDelta(result.nextUT, queryTT)) {
|
||||||
|
result.nextUT = eventUT
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func moonPlanetConjunctionShouldCheckLocal(queryTT float64, planet MoonPlanetConjunctionPlanet) bool {
|
||||||
|
delta := moonPlanetConjunctionDeltaAt(queryTT, planet, moonPlanetConjunctionEstimateN)
|
||||||
|
if math.IsNaN(delta) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return math.Abs(delta) <= moonPlanetConjunctionNearQueryDeltaDeg
|
||||||
|
}
|
||||||
|
|
||||||
|
func moonPlanetConjunctionLocalEvents(queryTT float64, planet MoonPlanetConjunctionPlanet) moonPlanetConjunctionLocalResult {
|
||||||
|
result := emptyMoonPlanetConjunctionLocalResult()
|
||||||
|
start := queryTT - moonPlanetConjunctionNearQueryHalfSpan
|
||||||
|
end := queryTT + moonPlanetConjunctionNearQueryHalfSpan
|
||||||
|
step := moonPlanetConjunctionNearQueryStepDays
|
||||||
|
prevTT := start
|
||||||
|
prevVal := moonPlanetConjunctionDeltaAt(prevTT, planet, -1)
|
||||||
|
if math.IsNaN(prevVal) {
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
samples := int(math.Ceil((end-start)/step)) + 1
|
||||||
|
for i := 1; i <= samples; i++ {
|
||||||
|
tt := start + float64(i)*step
|
||||||
|
if tt > end {
|
||||||
|
tt = end
|
||||||
|
}
|
||||||
|
val := moonPlanetConjunctionDeltaAt(tt, planet, -1)
|
||||||
|
if math.IsNaN(val) {
|
||||||
|
return emptyMoonPlanetConjunctionLocalResult()
|
||||||
|
}
|
||||||
|
if prevVal == 0 || val == 0 || prevVal*val < 0 {
|
||||||
|
moonPlanetConjunctionCollectLocalEvent(&result, queryTT, moonPlanetConjunctionEventUT(prevTT, tt, planet))
|
||||||
|
}
|
||||||
|
if tt == end {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
prevTT = tt
|
||||||
|
prevVal = val
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func moonPlanetConjunctionMaybeLocalEvents(queryTT float64, planet MoonPlanetConjunctionPlanet) moonPlanetConjunctionLocalResult {
|
||||||
|
if !moonPlanetConjunctionShouldCheckLocal(queryTT, planet) {
|
||||||
|
return emptyMoonPlanetConjunctionLocalResult()
|
||||||
|
}
|
||||||
|
return moonPlanetConjunctionLocalEvents(queryTT, planet)
|
||||||
|
}
|
||||||
|
|
||||||
|
func moonPlanetConjunctionGuessTT(queryTT float64, planet MoonPlanetConjunctionPlanet, direction int) float64 {
|
||||||
|
delta := moonPlanetConjunctionDeltaAt(queryTT, planet, moonPlanetConjunctionEstimateN)
|
||||||
|
if math.IsNaN(delta) {
|
||||||
|
return math.NaN()
|
||||||
|
}
|
||||||
|
period := moonPlanetConjunctionPeriodDays(planet)
|
||||||
|
if math.IsNaN(period) {
|
||||||
|
return math.NaN()
|
||||||
|
}
|
||||||
|
switch direction {
|
||||||
|
case -1:
|
||||||
|
return queryTT - innerLastCycleOffset(delta, period)
|
||||||
|
case 1:
|
||||||
|
return queryTT + innerNextCycleOffset(delta, period)
|
||||||
|
default:
|
||||||
|
return math.NaN()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func moonPlanetConjunctionDirectionalFallback(queryTT float64, planet MoonPlanetConjunctionPlanet, direction int) float64 {
|
||||||
|
period := moonPlanetConjunctionPeriodDays(planet)
|
||||||
|
if math.IsNaN(period) {
|
||||||
|
return math.NaN()
|
||||||
|
}
|
||||||
|
span := period * moonPlanetConjunctionFallbackSpanScale
|
||||||
|
if span <= 0 {
|
||||||
|
return math.NaN()
|
||||||
|
}
|
||||||
|
step := moonPlanetConjunctionNearQueryStepDays
|
||||||
|
start := queryTT
|
||||||
|
end := queryTT
|
||||||
|
switch direction {
|
||||||
|
case -1:
|
||||||
|
start -= span
|
||||||
|
case 1:
|
||||||
|
end += span
|
||||||
|
default:
|
||||||
|
return math.NaN()
|
||||||
|
}
|
||||||
|
|
||||||
|
prevTT := start
|
||||||
|
prevVal := moonPlanetConjunctionDeltaAt(prevTT, planet, -1)
|
||||||
|
if math.IsNaN(prevVal) {
|
||||||
|
return math.NaN()
|
||||||
|
}
|
||||||
|
|
||||||
|
bestEventUT := math.NaN()
|
||||||
|
for tt := start + step; ; tt += step {
|
||||||
|
if tt > end {
|
||||||
|
tt = end
|
||||||
|
}
|
||||||
|
val := moonPlanetConjunctionDeltaAt(tt, planet, -1)
|
||||||
|
if math.IsNaN(val) {
|
||||||
|
return math.NaN()
|
||||||
|
}
|
||||||
|
if prevVal == 0 || val == 0 || prevVal*val < 0 {
|
||||||
|
eventUT := moonPlanetConjunctionEventUT(prevTT, tt, planet)
|
||||||
|
if !math.IsNaN(eventUT) && moonPlanetConjunctionInDirection(eventUT, queryTT, direction) {
|
||||||
|
if direction == 1 {
|
||||||
|
return eventUT
|
||||||
|
}
|
||||||
|
bestEventUT = eventUT
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if tt == end {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
prevTT = tt
|
||||||
|
prevVal = val
|
||||||
|
}
|
||||||
|
return bestEventUT
|
||||||
|
}
|
||||||
|
|
||||||
|
func moonPlanetConjunctionDirectionalEventWithLocal(queryTT float64, planet MoonPlanetConjunctionPlanet, direction int, local moonPlanetConjunctionLocalResult) float64 {
|
||||||
|
switch direction {
|
||||||
|
case -1:
|
||||||
|
if !math.IsNaN(local.lastUT) {
|
||||||
|
return local.lastUT
|
||||||
|
}
|
||||||
|
case 1:
|
||||||
|
if !math.IsNaN(local.nextUT) {
|
||||||
|
return local.nextUT
|
||||||
|
}
|
||||||
|
}
|
||||||
|
guessTT := moonPlanetConjunctionGuessTT(queryTT, planet, direction)
|
||||||
|
if math.IsNaN(guessTT) {
|
||||||
|
return math.NaN()
|
||||||
|
}
|
||||||
|
halfSpan := moonPlanetConjunctionBracketHalfSpan
|
||||||
|
for attempt := 0; attempt < moonPlanetConjunctionBracketAttempts; attempt++ {
|
||||||
|
left, right, ok := moonPlanetConjunctionFindBracket(guessTT, halfSpan, moonPlanetConjunctionBracketStepDays, planet)
|
||||||
|
if ok {
|
||||||
|
eventUT := moonPlanetConjunctionEventUT(left, right, planet)
|
||||||
|
if math.IsNaN(eventUT) {
|
||||||
|
halfSpan *= moonPlanetConjunctionBracketGrowth
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if moonPlanetConjunctionInDirection(eventUT, queryTT, direction) {
|
||||||
|
return eventUT
|
||||||
|
}
|
||||||
|
}
|
||||||
|
halfSpan *= moonPlanetConjunctionBracketGrowth
|
||||||
|
}
|
||||||
|
return moonPlanetConjunctionDirectionalFallback(queryTT, planet, direction)
|
||||||
|
}
|
||||||
|
|
||||||
|
func moonPlanetConjunctionDirectionalEvent(queryTT float64, planet MoonPlanetConjunctionPlanet, direction int) float64 {
|
||||||
|
return moonPlanetConjunctionDirectionalEventWithLocal(queryTT, planet, direction, moonPlanetConjunctionMaybeLocalEvents(queryTT, planet))
|
||||||
|
}
|
||||||
|
|
||||||
|
// LastMoonPlanetConjunction 指定时刻之前最近一次行星合月(赤经合) / previous Moon-planet conjunction at or before jde.
|
||||||
|
func LastMoonPlanetConjunction(jde float64, planet MoonPlanetConjunctionPlanet) float64 {
|
||||||
|
return moonPlanetConjunctionDirectionalEvent(jde, planet, -1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NextMoonPlanetConjunction 指定时刻之后最近一次行星合月(赤经合) / next Moon-planet conjunction at or after jde.
|
||||||
|
func NextMoonPlanetConjunction(jde float64, planet MoonPlanetConjunctionPlanet) float64 {
|
||||||
|
return moonPlanetConjunctionDirectionalEvent(jde, planet, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClosestMoonPlanetConjunction 离指定时刻最近一次行星合月(赤经合) / closest Moon-planet conjunction to jde.
|
||||||
|
func ClosestMoonPlanetConjunction(jde float64, planet MoonPlanetConjunctionPlanet) float64 {
|
||||||
|
local := moonPlanetConjunctionMaybeLocalEvents(jde, planet)
|
||||||
|
if !math.IsNaN(local.lastUT) && !math.IsNaN(local.nextUT) {
|
||||||
|
if sameEventJD(local.lastUT, local.nextUT) {
|
||||||
|
return local.lastUT
|
||||||
|
}
|
||||||
|
return closestEventUTToQueryTT(jde, local.lastUT, local.nextUT)
|
||||||
|
}
|
||||||
|
if !math.IsNaN(local.lastUT) {
|
||||||
|
return local.lastUT
|
||||||
|
}
|
||||||
|
if !math.IsNaN(local.nextUT) {
|
||||||
|
return local.nextUT
|
||||||
|
}
|
||||||
|
last := moonPlanetConjunctionDirectionalEventWithLocal(jde, planet, -1, local)
|
||||||
|
next := moonPlanetConjunctionDirectionalEventWithLocal(jde, planet, 1, local)
|
||||||
|
if math.IsNaN(last) {
|
||||||
|
return next
|
||||||
|
}
|
||||||
|
if math.IsNaN(next) {
|
||||||
|
return last
|
||||||
|
}
|
||||||
|
return closestEventUTToQueryTT(jde, last, next)
|
||||||
|
}
|
||||||
@@ -0,0 +1,284 @@
|
|||||||
|
package basic
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"math"
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type moonPlanetConjunctionBaselineSample struct {
|
||||||
|
Planet string `json:"planet"`
|
||||||
|
Year int `json:"year"`
|
||||||
|
Month int `json:"month"`
|
||||||
|
TimeUTC string `json:"time_utc"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type moonPlanetConjunctionBaseline struct {
|
||||||
|
Samples []moonPlanetConjunctionBaselineSample `json:"samples"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadMoonPlanetConjunctionBaseline(t *testing.T) moonPlanetConjunctionBaseline {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
paths := [][]string{
|
||||||
|
{
|
||||||
|
"testdata/moon_planet_conjunction_baseline.json",
|
||||||
|
"basic/testdata/moon_planet_conjunction_baseline.json",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"testdata/moon_planet_conjunction_baseline_samples.json",
|
||||||
|
"basic/testdata/moon_planet_conjunction_baseline_samples.json",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
var merged moonPlanetConjunctionBaseline
|
||||||
|
for index, candidates := range paths {
|
||||||
|
var (
|
||||||
|
data []byte
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
for _, path := range candidates {
|
||||||
|
data, err = os.ReadFile(path)
|
||||||
|
if err == nil {
|
||||||
|
var baseline moonPlanetConjunctionBaseline
|
||||||
|
if err := json.Unmarshal(data, &baseline); err != nil {
|
||||||
|
t.Fatalf("decode baseline %s: %v", path, err)
|
||||||
|
}
|
||||||
|
merged.Samples = append(merged.Samples, baseline.Samples...)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err != nil && index == 0 {
|
||||||
|
t.Fatalf("read baseline: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(merged.Samples) == 0 {
|
||||||
|
t.Fatal("empty moon-planet conjunction baseline")
|
||||||
|
}
|
||||||
|
return merged
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMoonPlanetConjunctionsMatchHorizonsBaseline(t *testing.T) {
|
||||||
|
baseline := loadMoonPlanetConjunctionBaseline(t)
|
||||||
|
|
||||||
|
type conjunctionCase struct {
|
||||||
|
planet MoonPlanetConjunctionPlanet
|
||||||
|
next func(float64, MoonPlanetConjunctionPlanet) float64
|
||||||
|
}
|
||||||
|
|
||||||
|
cases := map[string]conjunctionCase{
|
||||||
|
"mercury": {planet: MoonPlanetConjunctionMercury, next: NextMoonPlanetConjunction},
|
||||||
|
"venus": {planet: MoonPlanetConjunctionVenus, next: NextMoonPlanetConjunction},
|
||||||
|
"mars": {planet: MoonPlanetConjunctionMars, next: NextMoonPlanetConjunction},
|
||||||
|
"jupiter": {planet: MoonPlanetConjunctionJupiter, next: NextMoonPlanetConjunction},
|
||||||
|
"saturn": {planet: MoonPlanetConjunctionSaturn, next: NextMoonPlanetConjunction},
|
||||||
|
"uranus": {planet: MoonPlanetConjunctionUranus, next: NextMoonPlanetConjunction},
|
||||||
|
"neptune": {planet: MoonPlanetConjunctionNeptune, next: NextMoonPlanetConjunction},
|
||||||
|
}
|
||||||
|
|
||||||
|
const tolerance = 20 * time.Second
|
||||||
|
var maxDiff time.Duration
|
||||||
|
|
||||||
|
seen := make(map[string]int, len(cases))
|
||||||
|
for _, sample := range baseline.Samples {
|
||||||
|
tc, ok := cases[sample.Planet]
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("unknown planet %q", sample.Planet)
|
||||||
|
}
|
||||||
|
|
||||||
|
wantTime, err := time.Parse(time.RFC3339Nano, sample.TimeUTC)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("parse sample time %q: %v", sample.TimeUTC, err)
|
||||||
|
}
|
||||||
|
queryTT := TD2UT(Date2JDE(wantTime.Add(-12*time.Hour).UTC()), true)
|
||||||
|
gotUT := tc.next(queryTT, tc.planet)
|
||||||
|
gotTime := JDE2DateByZone(gotUT, time.UTC, false)
|
||||||
|
diff := gotTime.Sub(wantTime)
|
||||||
|
if diff < 0 {
|
||||||
|
diff = -diff
|
||||||
|
}
|
||||||
|
if diff > maxDiff {
|
||||||
|
maxDiff = diff
|
||||||
|
}
|
||||||
|
if diff > tolerance {
|
||||||
|
t.Fatalf("%s %04d-%02d time mismatch: got %s want %s tolerance %v", sample.Planet, sample.Year, sample.Month, gotTime.Format(time.RFC3339Nano), sample.TimeUTC, tolerance)
|
||||||
|
}
|
||||||
|
|
||||||
|
delta := math.Abs(moonPlanetConjunctionDeltaAt(TD2UT(gotUT, true), tc.planet, -1))
|
||||||
|
if delta > 0.01 {
|
||||||
|
t.Fatalf("%s %04d-%02d event not near conjunction: delta=%.8f deg", sample.Planet, sample.Year, sample.Month, delta)
|
||||||
|
}
|
||||||
|
seen[sample.Planet]++
|
||||||
|
}
|
||||||
|
|
||||||
|
for planet := range cases {
|
||||||
|
if seen[planet] == 0 {
|
||||||
|
t.Fatalf("missing baseline samples for %s", planet)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Logf("moon-planet conjunction max diff: time=%v", maxDiff)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMoonPlanetConjunctionDirectionalConsistencyAtComputedEvent(t *testing.T) {
|
||||||
|
baseline := loadMoonPlanetConjunctionBaseline(t)
|
||||||
|
|
||||||
|
planets := map[string]MoonPlanetConjunctionPlanet{
|
||||||
|
"mercury": MoonPlanetConjunctionMercury,
|
||||||
|
"venus": MoonPlanetConjunctionVenus,
|
||||||
|
"mars": MoonPlanetConjunctionMars,
|
||||||
|
"jupiter": MoonPlanetConjunctionJupiter,
|
||||||
|
"saturn": MoonPlanetConjunctionSaturn,
|
||||||
|
"uranus": MoonPlanetConjunctionUranus,
|
||||||
|
"neptune": MoonPlanetConjunctionNeptune,
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, sample := range baseline.Samples {
|
||||||
|
planet, ok := planets[sample.Planet]
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("unknown planet %q", sample.Planet)
|
||||||
|
}
|
||||||
|
wantTime, err := time.Parse(time.RFC3339Nano, sample.TimeUTC)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("parse sample time %q: %v", sample.TimeUTC, err)
|
||||||
|
}
|
||||||
|
seedTT := TD2UT(Date2JDE(wantTime.Add(-12*time.Hour).UTC()), true)
|
||||||
|
eventUT := NextMoonPlanetConjunction(seedTT, planet)
|
||||||
|
eventTime := JDE2DateByZone(eventUT, time.UTC, false)
|
||||||
|
queryAtTT := TD2UT(Date2JDE(eventTime.UTC()), true)
|
||||||
|
queryAfterTT := TD2UT(Date2JDE(eventTime.Add(time.Hour).UTC()), true)
|
||||||
|
|
||||||
|
exactNext := NextMoonPlanetConjunction(queryAtTT, planet)
|
||||||
|
exactClosest := ClosestMoonPlanetConjunction(queryAtTT, planet)
|
||||||
|
exactLastAfter := LastMoonPlanetConjunction(queryAfterTT, planet)
|
||||||
|
|
||||||
|
for name, gotUT := range map[string]float64{
|
||||||
|
"exactNext": exactNext,
|
||||||
|
"exactClosest": exactClosest,
|
||||||
|
"lastAfterEvent": exactLastAfter,
|
||||||
|
} {
|
||||||
|
gotTime := JDE2DateByZone(gotUT, time.UTC, false)
|
||||||
|
if diff := math.Abs(gotUT - eventUT); diff > 1e-9 {
|
||||||
|
t.Fatalf("%s %s mismatch: got %s want %s diff=%v", sample.Planet, name, gotTime.Format(time.RFC3339Nano), eventTime.Format(time.RFC3339Nano), diff*86400)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMoonPlanetConjunctionRejectsOppositionBranchJump(t *testing.T) {
|
||||||
|
query := time.Date(1900, 11, 10, 12, 0, 0, 0, time.UTC)
|
||||||
|
queryTT := TD2UT(Date2JDE(query), true)
|
||||||
|
|
||||||
|
lastUT := LastMoonPlanetConjunction(queryTT, MoonPlanetConjunctionSaturn)
|
||||||
|
nextUT := NextMoonPlanetConjunction(queryTT, MoonPlanetConjunctionSaturn)
|
||||||
|
|
||||||
|
if math.Abs(lastUT-Date2JDE(query)) <= 5.0/86400.0 {
|
||||||
|
t.Fatalf("last returned query time on branch jump: got %s", JDE2DateByZone(lastUT, time.UTC, false).Format(time.RFC3339Nano))
|
||||||
|
}
|
||||||
|
if math.Abs(nextUT-Date2JDE(query)) <= 5.0/86400.0 {
|
||||||
|
t.Fatalf("next returned query time on branch jump: got %s", JDE2DateByZone(nextUT, time.UTC, false).Format(time.RFC3339Nano))
|
||||||
|
}
|
||||||
|
|
||||||
|
for name, gotUT := range map[string]float64{
|
||||||
|
"last": lastUT,
|
||||||
|
"next": nextUT,
|
||||||
|
} {
|
||||||
|
delta := math.Abs(moonPlanetConjunctionDeltaAt(TD2UT(gotUT, true), MoonPlanetConjunctionSaturn, -1))
|
||||||
|
if delta > moonPlanetConjunctionEventTolerance {
|
||||||
|
t.Fatalf("%s returned non-event candidate: delta=%.8f event=%s", name, delta, JDE2DateByZone(gotUT, time.UTC, false).Format(time.RFC3339Nano))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMoonPlanetConjunctionDirectionalOrderingOnSampleQueries(t *testing.T) {
|
||||||
|
samples := []struct {
|
||||||
|
planet MoonPlanetConjunctionPlanet
|
||||||
|
query time.Time
|
||||||
|
}{
|
||||||
|
{planet: MoonPlanetConjunctionSaturn, query: time.Date(1700, 4, 15, 12, 0, 0, 0, time.UTC)},
|
||||||
|
{planet: MoonPlanetConjunctionMercury, query: time.Date(1900, 1, 14, 12, 0, 0, 0, time.UTC)},
|
||||||
|
{planet: MoonPlanetConjunctionVenus, query: time.Date(1950, 6, 3, 12, 0, 0, 0, time.UTC)},
|
||||||
|
{planet: MoonPlanetConjunctionMars, query: time.Date(2000, 2, 29, 18, 0, 0, 0, time.UTC)},
|
||||||
|
{planet: MoonPlanetConjunctionJupiter, query: time.Date(2026, 5, 20, 0, 0, 0, 0, time.UTC)},
|
||||||
|
{planet: MoonPlanetConjunctionSaturn, query: time.Date(2100, 8, 17, 6, 0, 0, 0, time.UTC)},
|
||||||
|
{planet: MoonPlanetConjunctionUranus, query: time.Date(2200, 11, 2, 9, 0, 0, 0, time.UTC)},
|
||||||
|
{planet: MoonPlanetConjunctionNeptune, query: time.Date(2300, 4, 24, 3, 0, 0, 0, time.UTC)},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, sample := range samples {
|
||||||
|
queryTT := TD2UT(Date2JDE(sample.query.UTC()), true)
|
||||||
|
lastUT := LastMoonPlanetConjunction(queryTT, sample.planet)
|
||||||
|
nextUT := NextMoonPlanetConjunction(queryTT, sample.planet)
|
||||||
|
closestUT := ClosestMoonPlanetConjunction(queryTT, sample.planet)
|
||||||
|
|
||||||
|
if math.IsNaN(lastUT) || math.IsNaN(nextUT) || math.IsNaN(closestUT) {
|
||||||
|
t.Fatalf("planet=%v query=%s returned NaN event(s): last=%v next=%v closest=%v", sample.planet, sample.query.Format(time.RFC3339), lastUT, nextUT, closestUT)
|
||||||
|
}
|
||||||
|
if !eventUTQueryBeforeOrEqual(lastUT, queryTT) {
|
||||||
|
t.Fatalf("planet=%v last after query: last=%s query=%s", sample.planet, JDE2DateByZone(lastUT, time.UTC, false).Format(time.RFC3339Nano), sample.query.Format(time.RFC3339Nano))
|
||||||
|
}
|
||||||
|
if !eventUTQueryAfterOrEqual(nextUT, queryTT) {
|
||||||
|
t.Fatalf("planet=%v next before query: next=%s query=%s", sample.planet, JDE2DateByZone(nextUT, time.UTC, false).Format(time.RFC3339Nano), sample.query.Format(time.RFC3339Nano))
|
||||||
|
}
|
||||||
|
if closestUT != closestEventUTToQueryTT(queryTT, lastUT, nextUT) {
|
||||||
|
t.Fatalf("planet=%v closest mismatch: got=%s want=%s", sample.planet, JDE2DateByZone(closestUT, time.UTC, false).Format(time.RFC3339Nano), JDE2DateByZone(closestEventUTToQueryTT(queryTT, lastUT, nextUT), time.UTC, false).Format(time.RFC3339Nano))
|
||||||
|
}
|
||||||
|
for name, gotUT := range map[string]float64{
|
||||||
|
"last": lastUT,
|
||||||
|
"next": nextUT,
|
||||||
|
"closest": closestUT,
|
||||||
|
} {
|
||||||
|
delta := math.Abs(moonPlanetConjunctionDeltaAt(TD2UT(gotUT, true), sample.planet, -1))
|
||||||
|
if delta > moonPlanetConjunctionEventTolerance {
|
||||||
|
t.Fatalf("planet=%v %s returned non-event candidate: delta=%.8f event=%s", sample.planet, name, delta, JDE2DateByZone(gotUT, time.UTC, false).Format(time.RFC3339Nano))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMoonPlanetConjunctionKeepsImmediateNeighborEvents(t *testing.T) {
|
||||||
|
query := time.Date(1700, 4, 15, 12, 0, 0, 0, time.UTC)
|
||||||
|
queryTT := TD2UT(Date2JDE(query.UTC()), true)
|
||||||
|
|
||||||
|
lastUT := LastMoonPlanetConjunction(queryTT, MoonPlanetConjunctionSaturn)
|
||||||
|
nextUT := NextMoonPlanetConjunction(queryTT, MoonPlanetConjunctionSaturn)
|
||||||
|
closestUT := ClosestMoonPlanetConjunction(queryTT, MoonPlanetConjunctionSaturn)
|
||||||
|
|
||||||
|
wantLast := time.Date(1700, 4, 15, 11, 55, 59, 115569293, time.UTC)
|
||||||
|
wantNext := time.Date(1700, 5, 13, 0, 35, 5, 981616675, time.UTC)
|
||||||
|
const tolerance = 5.0 / 86400.0
|
||||||
|
|
||||||
|
if diff := math.Abs(lastUT - Date2JDE(wantLast)); diff > tolerance {
|
||||||
|
t.Fatalf("last mismatch: got=%s want=%s diff=%.3fs", JDE2DateByZone(lastUT, time.UTC, false).Format(time.RFC3339Nano), wantLast.Format(time.RFC3339Nano), diff*86400)
|
||||||
|
}
|
||||||
|
if diff := math.Abs(nextUT - Date2JDE(wantNext)); diff > tolerance {
|
||||||
|
t.Fatalf("next mismatch: got=%s want=%s diff=%.3fs", JDE2DateByZone(nextUT, time.UTC, false).Format(time.RFC3339Nano), wantNext.Format(time.RFC3339Nano), diff*86400)
|
||||||
|
}
|
||||||
|
if !sameEventJD(closestUT, lastUT) {
|
||||||
|
t.Fatalf("closest should keep immediate previous event: closest=%s last=%s", JDE2DateByZone(closestUT, time.UTC, false).Format(time.RFC3339Nano), JDE2DateByZone(lastUT, time.UTC, false).Format(time.RFC3339Nano))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMoonPlanetConjunctionNextAdvancesPastReturnedEvent(t *testing.T) {
|
||||||
|
seed := TD2UT(Date2JDE(time.Date(2026, 5, 1, 0, 0, 0, 0, time.UTC)), true)
|
||||||
|
eventUT := NextMoonPlanetConjunction(seed, MoonPlanetConjunctionMercury)
|
||||||
|
query := JDE2DateByZone(eventUT, time.UTC, false).Add(time.Second)
|
||||||
|
queryTT := TD2UT(Date2JDE(query.UTC()), true)
|
||||||
|
|
||||||
|
nextUT := NextMoonPlanetConjunction(queryTT, MoonPlanetConjunctionMercury)
|
||||||
|
if eventUTQueryTTDelta(nextUT, queryTT) <= 0 {
|
||||||
|
t.Fatalf("expected next conjunction after query: query=%s next=%s delta=%.6fs",
|
||||||
|
query.Format(time.RFC3339Nano),
|
||||||
|
JDE2DateByZone(nextUT, time.UTC, false).Format(time.RFC3339Nano),
|
||||||
|
eventUTQueryTTDelta(nextUT, queryTT)*86400,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if sameEventJD(nextUT, eventUT) {
|
||||||
|
t.Fatalf("next conjunction should advance to a later event: event=%s next=%s",
|
||||||
|
JDE2DateByZone(eventUT, time.UTC, false).Format(time.RFC3339Nano),
|
||||||
|
JDE2DateByZone(nextUT, time.UTC, false).Format(time.RFC3339Nano),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -126,6 +126,68 @@ func HMoonApparentLoN(jd float64, n int) float64 {
|
|||||||
return HMoonTrueLoN(jd, n) + Nutation2000Bi(jd)
|
return HMoonTrueLoN(jd, n) + Nutation2000Bi(jd)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// HMoonGeocentricApparentRa 月亮地心视赤经 / apparent geocentric right ascension of the Moon.
|
||||||
|
func HMoonGeocentricApparentRa(jd float64) float64 {
|
||||||
|
return HMoonGeocentricApparentRaN(jd, -1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// HMoonGeocentricApparentRaN 月亮地心视赤经(截断版) / truncated apparent geocentric right ascension of the Moon.
|
||||||
|
func HMoonGeocentricApparentRaN(jd float64, n int) float64 {
|
||||||
|
return LoToRa(jd, HMoonApparentLoN(jd, n), HMoonTrueBoN(jd, n))
|
||||||
|
}
|
||||||
|
|
||||||
|
// HMoonGeocentricApparentDec 月亮地心视赤纬 / apparent geocentric declination of the Moon.
|
||||||
|
func HMoonGeocentricApparentDec(jd float64) float64 {
|
||||||
|
return HMoonGeocentricApparentDecN(jd, -1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// HMoonGeocentricApparentDecN 月亮地心视赤纬(截断版) / truncated apparent geocentric declination of the Moon.
|
||||||
|
func HMoonGeocentricApparentDecN(jd float64, n int) float64 {
|
||||||
|
return ArcSin(Sin(HMoonTrueBoN(jd, n))*Cos(TrueObliquity(jd)) +
|
||||||
|
Cos(HMoonTrueBoN(jd, n))*Sin(TrueObliquity(jd))*Sin(HMoonApparentLoN(jd, n)))
|
||||||
|
}
|
||||||
|
|
||||||
|
// HMoonGeocentricApparentRaDec 月亮地心视赤经、视赤纬 / apparent geocentric right ascension and declination of the Moon.
|
||||||
|
func HMoonGeocentricApparentRaDec(jd float64) (float64, float64) {
|
||||||
|
return HMoonGeocentricApparentRaDecN(jd, -1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// HMoonGeocentricApparentRaDecN 月亮地心视赤经、视赤纬(截断版) / truncated apparent geocentric right ascension and declination of the Moon.
|
||||||
|
func HMoonGeocentricApparentRaDecN(jd float64, n int) (float64, float64) {
|
||||||
|
return LoBoToRaDec(jd, HMoonApparentLoN(jd, n), HMoonTrueBoN(jd, n))
|
||||||
|
}
|
||||||
|
|
||||||
|
// HMoonGeocentricTrueRa 月亮地心真赤经 / true geocentric right ascension of the Moon.
|
||||||
|
func HMoonGeocentricTrueRa(jd float64) float64 {
|
||||||
|
return HMoonGeocentricTrueRaN(jd, -1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// HMoonGeocentricTrueRaN 月亮地心真赤经(截断版) / truncated true geocentric right ascension of the Moon.
|
||||||
|
func HMoonGeocentricTrueRaN(jd float64, n int) float64 {
|
||||||
|
return LoToRa(jd, HMoonTrueLoN(jd, n), HMoonTrueBoN(jd, n))
|
||||||
|
}
|
||||||
|
|
||||||
|
// HMoonGeocentricTrueDec 月亮地心真赤纬 / true geocentric declination of the Moon.
|
||||||
|
func HMoonGeocentricTrueDec(jd float64) float64 {
|
||||||
|
return HMoonGeocentricTrueDecN(jd, -1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// HMoonGeocentricTrueDecN 月亮地心真赤纬(截断版) / truncated true geocentric declination of the Moon.
|
||||||
|
func HMoonGeocentricTrueDecN(jd float64, n int) float64 {
|
||||||
|
return ArcSin(Sin(HMoonTrueBoN(jd, n))*Cos(TrueObliquity(jd)) +
|
||||||
|
Cos(HMoonTrueBoN(jd, n))*Sin(TrueObliquity(jd))*Sin(HMoonTrueLoN(jd, n)))
|
||||||
|
}
|
||||||
|
|
||||||
|
// HMoonGeocentricTrueRaDec 月亮地心真赤经、真赤纬 / true geocentric right ascension and declination of the Moon.
|
||||||
|
func HMoonGeocentricTrueRaDec(jd float64) (float64, float64) {
|
||||||
|
return HMoonGeocentricTrueRaDecN(jd, -1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// HMoonGeocentricTrueRaDecN 月亮地心真赤经、真赤纬(截断版) / truncated true geocentric right ascension and declination of the Moon.
|
||||||
|
func HMoonGeocentricTrueRaDecN(jd float64, n int) (float64, float64) {
|
||||||
|
return LoBoToRaDec(jd, HMoonTrueLoN(jd, n), HMoonTrueBoN(jd, n))
|
||||||
|
}
|
||||||
|
|
||||||
func HMoonTrueRaDec(jd float64) (float64, float64) {
|
func HMoonTrueRaDec(jd float64) (float64, float64) {
|
||||||
return HMoonTrueRaDecN(jd, -1)
|
return HMoonTrueRaDecN(jd, -1)
|
||||||
}
|
}
|
||||||
|
|||||||
+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,84 @@
|
|||||||
|
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(2025, 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 TestOuterPlanetNextEventAdvancesPastReturnedEvent(t *testing.T) {
|
||||||
|
cases := []struct {
|
||||||
|
name string
|
||||||
|
seed float64
|
||||||
|
next func(float64) float64
|
||||||
|
}{
|
||||||
|
{name: "MarsOpposition", seed: ttjdUTC(2026, 1, 1, 0, 0, 0), next: NextMarsOpposition},
|
||||||
|
{name: "MarsP2R", seed: ttjdUTC(2026, 1, 1, 0, 0, 0), next: NextMarsProgradeToRetrograde},
|
||||||
|
{name: "JupiterOpposition", seed: ttjdUTC(2026, 1, 1, 0, 0, 0), next: NextJupiterOpposition},
|
||||||
|
{name: "SaturnConjunction", seed: ttjdUTC(2026, 1, 1, 0, 0, 0), next: NextSaturnConjunction},
|
||||||
|
{name: "UranusP2R", seed: ttjdUTC(2026, 1, 1, 0, 0, 0), next: NextUranusProgradeToRetrograde},
|
||||||
|
{name: "NeptuneR2P", seed: ttjdUTC(2026, 1, 1, 0, 0, 0), next: NextNeptuneRetrogradeToPrograde},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range cases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
first := tc.next(tc.seed)
|
||||||
|
query := TD2UT(Date2JDE(JDE2DateByZone(first, time.UTC, false).Add(time.Second)), true)
|
||||||
|
next := tc.next(query)
|
||||||
|
if !eventUTQueryAfterOrEqual(next, query) {
|
||||||
|
t.Fatalf("next should be after query: first=%.12f query=%.12f next=%.12f", first, query, next)
|
||||||
|
}
|
||||||
|
if sameEventJD(next, first) {
|
||||||
|
t.Fatalf("next should advance past first event: first=%.12f next=%.12f", first, next)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
@@ -0,0 +1,34 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"input_utc": "2026-01-01T00:00:00Z",
|
||||||
|
"right_ascension": 63.920306258,
|
||||||
|
"declination": 26.403701421,
|
||||||
|
"ecliptic_longitude": 66.7156363,
|
||||||
|
"ecliptic_latitude": 5.0490966
|
||||||
|
}
|
||||||
|
,
|
||||||
|
{
|
||||||
|
"input_utc": "2026-05-01T00:00:00Z",
|
||||||
|
"right_ascension": 208.849626532,
|
||||||
|
"declination": -16.256039871,
|
||||||
|
"ecliptic_longitude": 212.5315932,
|
||||||
|
"ecliptic_latitude": -4.1622995
|
||||||
|
}
|
||||||
|
,
|
||||||
|
{
|
||||||
|
"input_utc": "2026-08-29T00:00:00Z",
|
||||||
|
"right_ascension": 346.062573698,
|
||||||
|
"declination": -4.416718092,
|
||||||
|
"ecliptic_longitude": 345.4608613,
|
||||||
|
"ecliptic_latitude": 1.4247855
|
||||||
|
}
|
||||||
|
,
|
||||||
|
{
|
||||||
|
"input_utc": "2026-12-27T00:00:00Z",
|
||||||
|
"right_ascension": 139.195692903,
|
||||||
|
"declination": 16.272137989,
|
||||||
|
"ecliptic_longitude": 136.6058459,
|
||||||
|
"ecliptic_latitude": 0.4338603
|
||||||
|
}
|
||||||
|
|
||||||
|
]
|
||||||
@@ -0,0 +1,94 @@
|
|||||||
|
{
|
||||||
|
"samples": [
|
||||||
|
{"planet":"mercury","year":2026,"month":1,"time_utc":"2026-01-18T15:06:38Z"},
|
||||||
|
{"planet":"mercury","year":2026,"month":2,"time_utc":"2026-02-18T23:02:34Z"},
|
||||||
|
{"planet":"mercury","year":2026,"month":3,"time_utc":"2026-03-17T14:07:16Z"},
|
||||||
|
{"planet":"mercury","year":2026,"month":4,"time_utc":"2026-04-15T19:11:09Z"},
|
||||||
|
{"planet":"mercury","year":2026,"month":5,"time_utc":"2026-05-17T02:50:17Z"},
|
||||||
|
{"planet":"mercury","year":2026,"month":6,"time_utc":"2026-06-16T19:32:07Z"},
|
||||||
|
{"planet":"mercury","year":2026,"month":7,"time_utc":"2026-07-14T04:37:03Z"},
|
||||||
|
{"planet":"mercury","year":2026,"month":8,"time_utc":"2026-08-11T12:47:22Z"},
|
||||||
|
{"planet":"mercury","year":2026,"month":9,"time_utc":"2026-09-12T07:28:45Z"},
|
||||||
|
{"planet":"mercury","year":2026,"month":10,"time_utc":"2026-10-12T20:08:25Z"},
|
||||||
|
{"planet":"mercury","year":2026,"month":11,"time_utc":"2026-11-08T16:33:09Z"},
|
||||||
|
{"planet":"mercury","year":2026,"month":12,"time_utc":"2026-12-07T22:03:14Z"},
|
||||||
|
{"planet":"venus","year":2026,"month":1,"time_utc":"2026-01-19T01:01:00Z"},
|
||||||
|
{"planet":"venus","year":2026,"month":2,"time_utc":"2026-02-18T09:20:04Z"},
|
||||||
|
{"planet":"venus","year":2026,"month":3,"time_utc":"2026-03-20T12:37:37Z"},
|
||||||
|
{"planet":"venus","year":2026,"month":4,"time_utc":"2026-04-19T08:47:17Z"},
|
||||||
|
{"planet":"venus","year":2026,"month":5,"time_utc":"2026-05-19T01:49:30Z"},
|
||||||
|
{"planet":"venus","year":2026,"month":6,"time_utc":"2026-06-17T20:20:35Z"},
|
||||||
|
{"planet":"venus","year":2026,"month":7,"time_utc":"2026-07-17T16:31:26Z"},
|
||||||
|
{"planet":"venus","year":2026,"month":8,"time_utc":"2026-08-16T08:47:10Z"},
|
||||||
|
{"planet":"venus","year":2026,"month":9,"time_utc":"2026-09-14T11:10:57Z"},
|
||||||
|
{"planet":"venus","year":2026,"month":10,"time_utc":"2026-10-12T02:31:37Z"},
|
||||||
|
{"planet":"venus","year":2026,"month":11,"time_utc":"2026-11-07T11:33:28Z"},
|
||||||
|
{"planet":"venus","year":2026,"month":12,"time_utc":"2026-12-05T10:44:42Z"},
|
||||||
|
{"planet":"mars","year":2026,"month":1,"time_utc":"2026-01-18T14:09:59Z"},
|
||||||
|
{"planet":"mars","year":2026,"month":2,"time_utc":"2026-02-16T17:40:45Z"},
|
||||||
|
{"planet":"mars","year":2026,"month":3,"time_utc":"2026-03-17T21:52:35Z"},
|
||||||
|
{"planet":"mars","year":2026,"month":4,"time_utc":"2026-04-16T00:46:20Z"},
|
||||||
|
{"planet":"mars","year":2026,"month":5,"time_utc":"2026-05-15T00:44:20Z"},
|
||||||
|
{"planet":"mars","year":2026,"month":6,"time_utc":"2026-06-12T21:15:20Z"},
|
||||||
|
{"planet":"mars","year":2026,"month":7,"time_utc":"2026-07-11T14:38:57Z"},
|
||||||
|
{"planet":"mars","year":2026,"month":8,"time_utc":"2026-08-09T05:31:55Z"},
|
||||||
|
{"planet":"mars","year":2026,"month":9,"time_utc":"2026-09-06T18:25:43Z"},
|
||||||
|
{"planet":"mars","year":2026,"month":10,"time_utc":"2026-10-05T05:31:58Z"},
|
||||||
|
{"planet":"mars","year":2026,"month":11,"time_utc":"2026-11-02T14:25:02Z"},
|
||||||
|
{"planet":"mars","year":2026,"month":11,"time_utc":"2026-11-30T19:34:48Z"},
|
||||||
|
{"planet":"mars","year":2026,"month":12,"time_utc":"2026-12-28T17:44:28Z"},
|
||||||
|
{"planet":"jupiter","year":2026,"month":1,"time_utc":"2026-01-03T21:59:20Z"},
|
||||||
|
{"planet":"jupiter","year":2026,"month":1,"time_utc":"2026-01-31T02:29:07Z"},
|
||||||
|
{"planet":"jupiter","year":2026,"month":2,"time_utc":"2026-02-27T06:24:21Z"},
|
||||||
|
{"planet":"jupiter","year":2026,"month":3,"time_utc":"2026-03-26T12:11:13Z"},
|
||||||
|
{"planet":"jupiter","year":2026,"month":4,"time_utc":"2026-04-22T22:03:39Z"},
|
||||||
|
{"planet":"jupiter","year":2026,"month":5,"time_utc":"2026-05-20T12:36:55Z"},
|
||||||
|
{"planet":"jupiter","year":2026,"month":6,"time_utc":"2026-06-17T06:51:28Z"},
|
||||||
|
{"planet":"jupiter","year":2026,"month":7,"time_utc":"2026-07-15T03:03:33Z"},
|
||||||
|
{"planet":"jupiter","year":2026,"month":8,"time_utc":"2026-08-11T23:22:55Z"},
|
||||||
|
{"planet":"jupiter","year":2026,"month":9,"time_utc":"2026-09-08T18:11:18Z"},
|
||||||
|
{"planet":"jupiter","year":2026,"month":10,"time_utc":"2026-10-06T10:16:20Z"},
|
||||||
|
{"planet":"jupiter","year":2026,"month":11,"time_utc":"2026-11-02T23:09:31Z"},
|
||||||
|
{"planet":"jupiter","year":2026,"month":11,"time_utc":"2026-11-30T09:16:19Z"},
|
||||||
|
{"planet":"jupiter","year":2026,"month":12,"time_utc":"2026-12-27T17:30:35Z"},
|
||||||
|
{"planet":"saturn","year":2026,"month":1,"time_utc":"2026-01-23T12:40:10Z"},
|
||||||
|
{"planet":"saturn","year":2026,"month":2,"time_utc":"2026-02-20T00:03:21Z"},
|
||||||
|
{"planet":"saturn","year":2026,"month":3,"time_utc":"2026-03-19T14:12:20Z"},
|
||||||
|
{"planet":"saturn","year":2026,"month":4,"time_utc":"2026-04-16T06:08:13Z"},
|
||||||
|
{"planet":"saturn","year":2026,"month":5,"time_utc":"2026-05-13T21:58:07Z"},
|
||||||
|
{"planet":"saturn","year":2026,"month":6,"time_utc":"2026-06-10T11:41:01Z"},
|
||||||
|
{"planet":"saturn","year":2026,"month":7,"time_utc":"2026-07-07T21:49:36Z"},
|
||||||
|
{"planet":"saturn","year":2026,"month":8,"time_utc":"2026-08-04T04:10:47Z"},
|
||||||
|
{"planet":"saturn","year":2026,"month":8,"time_utc":"2026-08-31T08:07:26Z"},
|
||||||
|
{"planet":"saturn","year":2026,"month":9,"time_utc":"2026-09-27T12:00:54Z"},
|
||||||
|
{"planet":"saturn","year":2026,"month":10,"time_utc":"2026-10-24T17:41:49Z"},
|
||||||
|
{"planet":"saturn","year":2026,"month":11,"time_utc":"2026-11-21T01:26:46Z"},
|
||||||
|
{"planet":"saturn","year":2026,"month":12,"time_utc":"2026-12-18T10:18:35Z"},
|
||||||
|
{"planet":"uranus","year":2026,"month":1,"time_utc":"2026-01-27T18:46:51Z"},
|
||||||
|
{"planet":"uranus","year":2026,"month":2,"time_utc":"2026-02-24T00:35:51Z"},
|
||||||
|
{"planet":"uranus","year":2026,"month":3,"time_utc":"2026-03-23T07:40:49Z"},
|
||||||
|
{"planet":"uranus","year":2026,"month":4,"time_utc":"2026-04-19T17:35:45Z"},
|
||||||
|
{"planet":"uranus","year":2026,"month":5,"time_utc":"2026-05-17T05:58:41Z"},
|
||||||
|
{"planet":"uranus","year":2026,"month":6,"time_utc":"2026-06-13T19:08:48Z"},
|
||||||
|
{"planet":"uranus","year":2026,"month":7,"time_utc":"2026-07-11T07:07:53Z"},
|
||||||
|
{"planet":"uranus","year":2026,"month":8,"time_utc":"2026-08-07T16:30:03Z"},
|
||||||
|
{"planet":"uranus","year":2026,"month":9,"time_utc":"2026-09-03T23:05:03Z"},
|
||||||
|
{"planet":"uranus","year":2026,"month":10,"time_utc":"2026-10-01T04:18:41Z"},
|
||||||
|
{"planet":"uranus","year":2026,"month":10,"time_utc":"2026-10-28T10:24:46Z"},
|
||||||
|
{"planet":"uranus","year":2026,"month":11,"time_utc":"2026-11-24T18:39:41Z"},
|
||||||
|
{"planet":"uranus","year":2026,"month":12,"time_utc":"2026-12-22T04:19:54Z"},
|
||||||
|
{"planet":"neptune","year":2026,"month":1,"time_utc":"2026-01-23T15:49:28Z"},
|
||||||
|
{"planet":"neptune","year":2026,"month":2,"time_utc":"2026-02-19T23:30:09Z"},
|
||||||
|
{"planet":"neptune","year":2026,"month":3,"time_utc":"2026-03-19T09:34:27Z"},
|
||||||
|
{"planet":"neptune","year":2026,"month":4,"time_utc":"2026-04-15T21:23:14Z"},
|
||||||
|
{"planet":"neptune","year":2026,"month":5,"time_utc":"2026-05-13T09:11:35Z"},
|
||||||
|
{"planet":"neptune","year":2026,"month":6,"time_utc":"2026-06-09T19:13:52Z"},
|
||||||
|
{"planet":"neptune","year":2026,"month":7,"time_utc":"2026-07-07T02:37:58Z"},
|
||||||
|
{"planet":"neptune","year":2026,"month":8,"time_utc":"2026-08-03T07:56:14Z"},
|
||||||
|
{"planet":"neptune","year":2026,"month":8,"time_utc":"2026-08-30T12:50:11Z"},
|
||||||
|
{"planet":"neptune","year":2026,"month":9,"time_utc":"2026-09-26T19:04:28Z"},
|
||||||
|
{"planet":"neptune","year":2026,"month":10,"time_utc":"2026-10-24T03:16:37Z"},
|
||||||
|
{"planet":"neptune","year":2026,"month":11,"time_utc":"2026-11-20T12:35:40Z"},
|
||||||
|
{"planet":"neptune","year":2026,"month":12,"time_utc":"2026-12-17T21:26:40Z"}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,92 @@
|
|||||||
|
{
|
||||||
|
"samples": [
|
||||||
|
{"planet":"mercury","year":1996,"month":1,"time_utc":"1996-01-20T07:48:44Z"},
|
||||||
|
{"planet":"mercury","year":1996,"month":2,"time_utc":"1996-02-17T05:58:27Z"},
|
||||||
|
{"planet":"mercury","year":1996,"month":3,"time_utc":"1996-03-18T21:54:14Z"},
|
||||||
|
{"planet":"mercury","year":1996,"month":4,"time_utc":"1996-04-19T10:29:52Z"},
|
||||||
|
{"planet":"mercury","year":1996,"month":5,"time_utc":"1996-05-17T04:02:30Z"},
|
||||||
|
{"planet":"mercury","year":1996,"month":6,"time_utc":"1996-06-14T00:19:49Z"},
|
||||||
|
{"planet":"mercury","year":1996,"month":7,"time_utc":"1996-07-16T08:03:39Z"},
|
||||||
|
{"planet":"mercury","year":1996,"month":8,"time_utc":"1996-08-16T18:24:11Z"},
|
||||||
|
{"planet":"mercury","year":1996,"month":9,"time_utc":"1996-09-13T13:17:46Z"},
|
||||||
|
{"planet":"mercury","year":1996,"month":10,"time_utc":"1996-10-11T09:44:59Z"},
|
||||||
|
{"planet":"mercury","year":1996,"month":11,"time_utc":"1996-11-11T12:53:54Z"},
|
||||||
|
{"planet":"mercury","year":1996,"month":12,"time_utc":"1996-12-12T05:10:41Z"},
|
||||||
|
{"planet":"venus","year":1996,"month":1,"time_utc":"1996-01-23T08:25:31Z"},
|
||||||
|
{"planet":"venus","year":1996,"month":2,"time_utc":"1996-02-22T04:36:35Z"},
|
||||||
|
{"planet":"venus","year":1996,"month":3,"time_utc":"1996-03-22T23:53:11Z"},
|
||||||
|
{"planet":"venus","year":1996,"month":4,"time_utc":"1996-04-21T14:13:39Z"},
|
||||||
|
{"planet":"venus","year":1996,"month":5,"time_utc":"1996-05-20T00:40:14Z"},
|
||||||
|
{"planet":"venus","year":1996,"month":6,"time_utc":"1996-06-15T09:13:17Z"},
|
||||||
|
{"planet":"venus","year":1996,"month":7,"time_utc":"1996-07-12T08:38:52Z"},
|
||||||
|
{"planet":"venus","year":1996,"month":8,"time_utc":"1996-08-10T03:59:30Z"},
|
||||||
|
{"planet":"venus","year":1996,"month":9,"time_utc":"1996-09-08T23:10:01Z"},
|
||||||
|
{"planet":"venus","year":1996,"month":10,"time_utc":"1996-10-09T04:07:49Z"},
|
||||||
|
{"planet":"venus","year":1996,"month":11,"time_utc":"1996-11-08T09:31:53Z"},
|
||||||
|
{"planet":"venus","year":1996,"month":12,"time_utc":"1996-12-08T13:14:51Z"},
|
||||||
|
{"planet":"mars","year":1996,"month":1,"time_utc":"1996-01-21T07:43:41Z"},
|
||||||
|
{"planet":"mars","year":1996,"month":2,"time_utc":"1996-02-19T07:57:56Z"},
|
||||||
|
{"planet":"mars","year":1996,"month":3,"time_utc":"1996-03-19T07:08:23Z"},
|
||||||
|
{"planet":"mars","year":1996,"month":4,"time_utc":"1996-04-17T05:16:03Z"},
|
||||||
|
{"planet":"mars","year":1996,"month":5,"time_utc":"1996-05-16T02:56:01Z"},
|
||||||
|
{"planet":"mars","year":1996,"month":6,"time_utc":"1996-06-14T00:47:52Z"},
|
||||||
|
{"planet":"mars","year":1996,"month":7,"time_utc":"1996-07-12T23:06:35Z"},
|
||||||
|
{"planet":"mars","year":1996,"month":8,"time_utc":"1996-08-10T21:29:02Z"},
|
||||||
|
{"planet":"mars","year":1996,"month":9,"time_utc":"1996-09-08T19:05:05Z"},
|
||||||
|
{"planet":"mars","year":1996,"month":10,"time_utc":"1996-10-07T14:58:13Z"},
|
||||||
|
{"planet":"mars","year":1996,"month":11,"time_utc":"1996-11-05T08:08:58Z"},
|
||||||
|
{"planet":"mars","year":1996,"month":12,"time_utc":"1996-12-03T21:11:34Z"},
|
||||||
|
{"planet":"jupiter","year":1996,"month":1,"time_utc":"1996-01-18T19:46:08Z"},
|
||||||
|
{"planet":"jupiter","year":1996,"month":2,"time_utc":"1996-02-15T15:00:36Z"},
|
||||||
|
{"planet":"jupiter","year":1996,"month":3,"time_utc":"1996-03-14T06:11:13Z"},
|
||||||
|
{"planet":"jupiter","year":1996,"month":4,"time_utc":"1996-04-10T16:55:53Z"},
|
||||||
|
{"planet":"jupiter","year":1996,"month":5,"time_utc":"1996-05-08T00:11:28Z"},
|
||||||
|
{"planet":"jupiter","year":1996,"month":6,"time_utc":"1996-06-04T05:31:30Z"},
|
||||||
|
{"planet":"jupiter","year":1996,"month":7,"time_utc":"1996-07-01T10:21:43Z"},
|
||||||
|
{"planet":"jupiter","year":1996,"month":7,"time_utc":"1996-07-28T15:41:14Z"},
|
||||||
|
{"planet":"jupiter","year":1996,"month":8,"time_utc":"1996-08-24T22:04:24Z"},
|
||||||
|
{"planet":"jupiter","year":1996,"month":9,"time_utc":"1996-09-21T05:58:04Z"},
|
||||||
|
{"planet":"jupiter","year":1996,"month":10,"time_utc":"1996-10-18T16:05:44Z"},
|
||||||
|
{"planet":"jupiter","year":1996,"month":11,"time_utc":"1996-11-15T05:27:06Z"},
|
||||||
|
{"planet":"jupiter","year":1996,"month":12,"time_utc":"1996-12-12T22:38:11Z"},
|
||||||
|
{"planet":"saturn","year":1996,"month":1,"time_utc":"1996-01-24T03:47:15Z"},
|
||||||
|
{"planet":"saturn","year":1996,"month":2,"time_utc":"1996-02-20T19:10:42Z"},
|
||||||
|
{"planet":"saturn","year":1996,"month":3,"time_utc":"1996-03-19T10:59:03Z"},
|
||||||
|
{"planet":"saturn","year":1996,"month":4,"time_utc":"1996-04-16T01:04:52Z"},
|
||||||
|
{"planet":"saturn","year":1996,"month":5,"time_utc":"1996-05-13T12:31:53Z"},
|
||||||
|
{"planet":"saturn","year":1996,"month":6,"time_utc":"1996-06-09T21:39:17Z"},
|
||||||
|
{"planet":"saturn","year":1996,"month":7,"time_utc":"1996-07-07T05:32:21Z"},
|
||||||
|
{"planet":"saturn","year":1996,"month":8,"time_utc":"1996-08-03T13:13:36Z"},
|
||||||
|
{"planet":"saturn","year":1996,"month":8,"time_utc":"1996-08-30T21:01:02Z"},
|
||||||
|
{"planet":"saturn","year":1996,"month":9,"time_utc":"1996-09-27T04:20:31Z"},
|
||||||
|
{"planet":"saturn","year":1996,"month":10,"time_utc":"1996-10-24T10:21:42Z"},
|
||||||
|
{"planet":"saturn","year":1996,"month":11,"time_utc":"1996-11-20T15:05:45Z"},
|
||||||
|
{"planet":"saturn","year":1996,"month":12,"time_utc":"1996-12-17T20:17:06Z"},
|
||||||
|
{"planet":"uranus","year":1996,"month":1,"time_utc":"1996-01-20T15:54:24Z"},
|
||||||
|
{"planet":"uranus","year":1996,"month":2,"time_utc":"1996-02-17T05:20:18Z"},
|
||||||
|
{"planet":"uranus","year":1996,"month":3,"time_utc":"1996-03-15T16:05:29Z"},
|
||||||
|
{"planet":"uranus","year":1996,"month":4,"time_utc":"1996-04-11T23:42:31Z"},
|
||||||
|
{"planet":"uranus","year":1996,"month":5,"time_utc":"1996-05-09T05:36:22Z"},
|
||||||
|
{"planet":"uranus","year":1996,"month":6,"time_utc":"1996-06-05T11:50:19Z"},
|
||||||
|
{"planet":"uranus","year":1996,"month":7,"time_utc":"1996-07-02T19:33:47Z"},
|
||||||
|
{"planet":"uranus","year":1996,"month":7,"time_utc":"1996-07-30T04:29:08Z"},
|
||||||
|
{"planet":"uranus","year":1996,"month":8,"time_utc":"1996-08-26T13:21:06Z"},
|
||||||
|
{"planet":"uranus","year":1996,"month":9,"time_utc":"1996-09-22T20:54:56Z"},
|
||||||
|
{"planet":"uranus","year":1996,"month":10,"time_utc":"1996-10-20T03:02:32Z"},
|
||||||
|
{"planet":"uranus","year":1996,"month":11,"time_utc":"1996-11-16T09:17:15Z"},
|
||||||
|
{"planet":"uranus","year":1996,"month":12,"time_utc":"1996-12-13T17:54:51Z"},
|
||||||
|
{"planet":"neptune","year":1996,"month":1,"time_utc":"1996-01-20T07:21:06Z"},
|
||||||
|
{"planet":"neptune","year":1996,"month":2,"time_utc":"1996-02-16T19:38:46Z"},
|
||||||
|
{"planet":"neptune","year":1996,"month":3,"time_utc":"1996-03-15T05:08:50Z"},
|
||||||
|
{"planet":"neptune","year":1996,"month":4,"time_utc":"1996-04-11T11:48:33Z"},
|
||||||
|
{"planet":"neptune","year":1996,"month":5,"time_utc":"1996-05-08T17:24:20Z"},
|
||||||
|
{"planet":"neptune","year":1996,"month":6,"time_utc":"1996-06-04T23:59:10Z"},
|
||||||
|
{"planet":"neptune","year":1996,"month":7,"time_utc":"1996-07-02T08:22:46Z"},
|
||||||
|
{"planet":"neptune","year":1996,"month":7,"time_utc":"1996-07-29T17:55:53Z"},
|
||||||
|
{"planet":"neptune","year":1996,"month":8,"time_utc":"1996-08-26T03:10:20Z"},
|
||||||
|
{"planet":"neptune","year":1996,"month":9,"time_utc":"1996-09-22T10:48:58Z"},
|
||||||
|
{"planet":"neptune","year":1996,"month":10,"time_utc":"1996-10-19T16:49:59Z"},
|
||||||
|
{"planet":"neptune","year":1996,"month":11,"time_utc":"1996-11-15T22:54:41Z"},
|
||||||
|
{"planet":"neptune","year":1996,"month":12,"time_utc":"1996-12-13T07:18:13Z"}
|
||||||
|
]
|
||||||
|
}
|
||||||
+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 {
|
||||||
|
|||||||
+415
-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,102 @@ 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)
|
||||||
|
if math.Abs(venusSunLongitudeDelta(queryTT)) <= 30.0/86400.0 {
|
||||||
|
exact := eventZeroRefine(left, 1.0, 0.000005, venusSunLongitudeDelta)
|
||||||
|
eventUT := TD2UT(exact, false)
|
||||||
|
if next == 0 && eventUTQueryBeforeOrEqual(eventUT, queryTT) {
|
||||||
|
return eventUT
|
||||||
}
|
}
|
||||||
break
|
if next == 1 && eventUTQueryAfterOrEqual(eventUT, queryTT) {
|
||||||
}
|
return eventUT
|
||||||
JD1 := jde
|
|
||||||
for {
|
|
||||||
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 nextVenusTypedConjunction(jde float64, inferior bool) float64 {
|
||||||
|
return nextVenusTypedConjunctionFromEvent(jde, inferior)
|
||||||
|
}
|
||||||
|
|
||||||
|
func lastVenusTypedConjunction(jde float64, inferior bool) float64 {
|
||||||
|
return lastVenusTypedConjunctionFromEvent(jde, inferior)
|
||||||
|
}
|
||||||
|
|
||||||
func NextVenusInferiorConjunction(jde float64) float64 {
|
func NextVenusInferiorConjunction(jde float64) float64 {
|
||||||
date := NextVenusConjunction(jde)
|
return nextVenusTypedConjunction(jde, true)
|
||||||
if EarthVenusAway(date) > EarthAway(date) {
|
|
||||||
return NextVenusConjunction(date + 2)
|
|
||||||
}
|
|
||||||
return date
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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 +323,301 @@ 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 + innerEventWindowPadding, inferior - innerEventWindowPadding
|
||||||
|
}
|
||||||
|
|
||||||
|
func venusWestElongationWindowEndingAt(superior float64) (float64, float64) {
|
||||||
|
lastInferior := LastVenusInferiorConjunction(eventUTLastQueryTT(superior))
|
||||||
|
return lastInferior + innerEventWindowPadding, superior - innerEventWindowPadding
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
|
||||||
}
|
}
|
||||||
return date
|
if east > west {
|
||||||
|
return east
|
||||||
|
}
|
||||||
|
return west
|
||||||
|
}
|
||||||
|
|
||||||
|
func LastVenusInferiorConjunctionInclusive(jde float64) float64 {
|
||||||
|
return inclusiveLastSimpleEvent(jde, LastVenusInferiorConjunction, NextVenusInferiorConjunction)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NextVenusInferiorConjunctionInclusive(jde float64) float64 {
|
||||||
|
return inclusiveNextSimpleEvent(jde, LastVenusInferiorConjunction, NextVenusInferiorConjunction)
|
||||||
|
}
|
||||||
|
|
||||||
|
func LastVenusSuperiorConjunctionInclusive(jde float64) float64 {
|
||||||
|
return inclusiveLastSimpleEvent(jde, LastVenusSuperiorConjunction, NextVenusSuperiorConjunction)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NextVenusSuperiorConjunctionInclusive(jde float64) float64 {
|
||||||
|
return inclusiveNextSimpleEvent(jde, LastVenusSuperiorConjunction, NextVenusSuperiorConjunction)
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -50,6 +50,10 @@ const (
|
|||||||
// 返回 农历月,日,是否闰月以及文字描述
|
// 返回 农历月,日,是否闰月以及文字描述
|
||||||
// 按现行农历GB/T 33661-2017算法计算,推荐使用年限为[1929-3000]年
|
// 按现行农历GB/T 33661-2017算法计算,推荐使用年限为[1929-3000]年
|
||||||
// 古代由于定朔定气误差此处计算会与古时不符
|
// 古代由于定朔定气误差此处计算会与古时不符
|
||||||
|
// Inputs are civil year, month, day, and timezone offset in hours.
|
||||||
|
// Returns the lunar year, month, day, leap-month flag, and text description.
|
||||||
|
// The current GB/T 33661-2017 lunar-calendar convention is recommended for years 1929-3000.
|
||||||
|
// For ancient dates, the result may differ from historical practice because computed new-moon and solar-term reconstructions are approximate for ancient dates.
|
||||||
func Lunar(year, month, day int, timezone float64) (int, int, int, bool, string) {
|
func Lunar(year, month, day int, timezone float64) (int, int, int, bool, string) {
|
||||||
return basic.GetLunar(year, month, day, timezone/24.0)
|
return basic.GetLunar(year, month, day, timezone/24.0)
|
||||||
}
|
}
|
||||||
@@ -62,6 +66,12 @@ func Lunar(year, month, day int, timezone float64) (int, int, int, bool, string)
|
|||||||
// 由于农历还未到鼠年,故应当传入Solar(2019,12,30,false)
|
// 由于农历还未到鼠年,故应当传入Solar(2019,12,30,false)
|
||||||
// 按现行农历GB/T 33661-2017算法计算,推荐使用年限为[1929-3000]年
|
// 按现行农历GB/T 33661-2017算法计算,推荐使用年限为[1929-3000]年
|
||||||
// 古代由于定朔定气误差此处计算会与古时不符
|
// 古代由于定朔定气误差此处计算会与古时不符
|
||||||
|
// Inputs are the civil-year proxy of the lunar year, lunar month, lunar day, leap-month flag, and timezone offset in hours.
|
||||||
|
// Returns the corresponding civil time.
|
||||||
|
// The lunar year parameter follows the civil year containing the lunar New Year of that cycle.
|
||||||
|
// For example, the last day of the Ji-Hai year corresponds to 2020-01-24, but should still be passed as `Solar(2019, 12, 30, false, ...)`.
|
||||||
|
// The current GB/T 33661-2017 lunar-calendar convention is recommended for years 1929-3000.
|
||||||
|
// For ancient dates, the result may differ from historical practice because computed new-moon and solar-term reconstructions are approximate for ancient dates.
|
||||||
func Solar(year, month, day int, leap bool, timezone float64) time.Time {
|
func Solar(year, month, day int, leap bool, timezone float64) time.Time {
|
||||||
jde := basic.GetSolar(year, month, day, leap, timezone/24.0)
|
jde := basic.GetSolar(year, month, day, leap, timezone/24.0)
|
||||||
zone := time.FixedZone("CST", int(timezone*3600))
|
zone := time.FixedZone("CST", int(timezone*3600))
|
||||||
@@ -74,6 +84,11 @@ func Solar(year, month, day int, leap bool, timezone float64) time.Time {
|
|||||||
// 支持年份:[-103,3000]
|
// 支持年份:[-103,3000]
|
||||||
// [-103,1912] 按照古代历法提供的农历信息
|
// [-103,1912] 按照古代历法提供的农历信息
|
||||||
// (1912,3000]按现行农历GB/T 33661-2017算法计算
|
// (1912,3000]按现行农历GB/T 33661-2017算法计算
|
||||||
|
// Input is a civil `time.Time`.
|
||||||
|
// Returns a `Time` value carrying the lunar-calendar information.
|
||||||
|
// Supported years are [-103, 3000].
|
||||||
|
// Years [-103, 1912] use the historical-calendar tables included in this package.
|
||||||
|
// Years (1912, 3000] use the current GB/T 33661-2017 lunar-calendar convention.
|
||||||
func SolarToLunar(date time.Time) (Time, error) {
|
func SolarToLunar(date time.Time) (Time, error) {
|
||||||
return innerSolarToLunar(date)
|
return innerSolarToLunar(date)
|
||||||
}
|
}
|
||||||
@@ -84,6 +99,11 @@ func SolarToLunar(date time.Time) (Time, error) {
|
|||||||
// 支持年份:[-103,3000]
|
// 支持年份:[-103,3000]
|
||||||
// [-103,1912] 按照古代历法提供的农历信息
|
// [-103,1912] 按照古代历法提供的农历信息
|
||||||
// (1912,3000]按现行农历GB/T 33661-2017算法计算
|
// (1912,3000]按现行农历GB/T 33661-2017算法计算
|
||||||
|
// Inputs are the civil year, month, and day.
|
||||||
|
// Returns a `Time` value carrying the lunar-calendar information.
|
||||||
|
// Supported years are [-103, 3000].
|
||||||
|
// Years [-103, 1912] use the historical-calendar tables included in this package.
|
||||||
|
// Years (1912, 3000] use the current GB/T 33661-2017 lunar-calendar convention.
|
||||||
func SolarToLunarByYMD(year, month, day int) (Time, error) {
|
func SolarToLunarByYMD(year, month, day int) (Time, error) {
|
||||||
return innerSolarToLunarByYMD(year, month, day)
|
return innerSolarToLunarByYMD(year, month, day)
|
||||||
}
|
}
|
||||||
@@ -165,6 +185,14 @@ func transformModenLunar2Time(date time.Time, year, month, day int, leap bool, d
|
|||||||
// 年号+农历月中文描述+农历日中文描述
|
// 年号+农历月中文描述+农历日中文描述
|
||||||
// 年号+农历月中文描述+干支日中文描述
|
// 年号+农历月中文描述+干支日中文描述
|
||||||
// 支持年份:[-103,3000]
|
// 支持年份:[-103,3000]
|
||||||
|
// Input is a lunar-date description such as `二零二零年正月初一`, `元丰六年十月十二`, or `元嘉二十七年七月庚午日`.
|
||||||
|
// Returns all matching `Time` results with both civil and lunar information.
|
||||||
|
// The parser accepts these forms:
|
||||||
|
// lunar year text + lunar month text + lunar day text
|
||||||
|
// lunar year text + lunar month text + sexagenary day text
|
||||||
|
// era name + lunar month text + lunar day text
|
||||||
|
// era name + lunar month text + sexagenary day text
|
||||||
|
// Supported years are [-103, 3000].
|
||||||
func LunarToSolar(desc string) ([]Time, error) {
|
func LunarToSolar(desc string) ([]Time, error) {
|
||||||
dates, err := innerParseLunar(desc)
|
dates, err := innerParseLunar(desc)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -189,6 +217,12 @@ func LunarToSolar(desc string) ([]Time, error) {
|
|||||||
// 支持年份:[-103,3000]
|
// 支持年份:[-103,3000]
|
||||||
// [-103,1912] 按照古代历法提供的农历信息,注意,这里农历月份代表的是以当时的历法推定的农历月与正月的距离,正月为1,二月为2,依次类推,闰月显示所闰月
|
// [-103,1912] 按照古代历法提供的农历信息,注意,这里农历月份代表的是以当时的历法推定的农历月与正月的距离,正月为1,二月为2,依次类推,闰月显示所闰月
|
||||||
// (1912,3000]按现行农历GB/T 33661-2017算法计算
|
// (1912,3000]按现行农历GB/T 33661-2017算法计算
|
||||||
|
// Deprecated: use LunarToSolarByYMD.
|
||||||
|
// Inputs are lunar year, month, day, and the leap-month flag.
|
||||||
|
// Returns a `Time` value carrying both civil and lunar information.
|
||||||
|
// Supported years are [-103, 3000].
|
||||||
|
// For years [-103, 1912], the lunar month index follows the historical calendar in force at that time, counted from the first month of that year.
|
||||||
|
// Years (1912, 3000] use the current GB/T 33661-2017 lunar-calendar convention.
|
||||||
func LunarToSolarSingle(year, month, day int, leap bool) (Time, error) {
|
func LunarToSolarSingle(year, month, day int, leap bool) (Time, error) {
|
||||||
return LunarToSolarByYMD(year, month, day, leap)
|
return LunarToSolarByYMD(year, month, day, leap)
|
||||||
}
|
}
|
||||||
@@ -199,6 +233,11 @@ func LunarToSolarSingle(year, month, day int, leap bool) (Time, error) {
|
|||||||
// 支持年份:[-103,3000]
|
// 支持年份:[-103,3000]
|
||||||
// [-103,1912] 按照古代历法提供的农历信息,注意,这里农历月份代表的是以当时的历法推定的农历月与正月的距离,正月为1,二月为2,依次类推,闰月显示所闰月
|
// [-103,1912] 按照古代历法提供的农历信息,注意,这里农历月份代表的是以当时的历法推定的农历月与正月的距离,正月为1,二月为2,依次类推,闰月显示所闰月
|
||||||
// (1912,3000]按现行农历GB/T 33661-2017算法计算
|
// (1912,3000]按现行农历GB/T 33661-2017算法计算
|
||||||
|
// Inputs are lunar year, month, day, and the leap-month flag.
|
||||||
|
// Returns a `Time` value carrying both civil and lunar information.
|
||||||
|
// Supported years are [-103, 3000].
|
||||||
|
// For years [-103, 1912], the lunar month index follows the historical calendar in force at that time, counted from the first month of that year.
|
||||||
|
// Years (1912, 3000] use the current GB/T 33661-2017 lunar-calendar convention.
|
||||||
func LunarToSolarByYMD(year, month, day int, leap bool) (Time, error) {
|
func LunarToSolarByYMD(year, month, day int, leap bool) (Time, error) {
|
||||||
if year < -103 || year > 9999 {
|
if year < -103 || year > 9999 {
|
||||||
return Time{}, fmt.Errorf("年份超出范围")
|
return Time{}, fmt.Errorf("年份超出范围")
|
||||||
@@ -218,6 +257,7 @@ func LunarToSolarByYMD(year, month, day int, leap bool) (Time, error) {
|
|||||||
// JieQi 节气时刻(北京时间) / solar term instant in Beijing time.
|
// JieQi 节气时刻(北京时间) / solar term instant in Beijing time.
|
||||||
//
|
//
|
||||||
// 返回传入年份、节气对应的北京时间节气时间。
|
// 返回传入年份、节气对应的北京时间节气时间。
|
||||||
|
// Returns the Beijing-time instant of the requested solar term in the supplied year.
|
||||||
func JieQi(year, term int) time.Time {
|
func JieQi(year, term int) time.Time {
|
||||||
calcJde := basic.GetJQTime(year, term)
|
calcJde := basic.GetJQTime(year, term)
|
||||||
zone := time.FixedZone("CST", 8*3600)
|
zone := time.FixedZone("CST", 8*3600)
|
||||||
@@ -227,6 +267,7 @@ func JieQi(year, term int) time.Time {
|
|||||||
// WuHou 物候时刻(北京时间) / pentad instant in Beijing time.
|
// WuHou 物候时刻(北京时间) / pentad instant in Beijing time.
|
||||||
//
|
//
|
||||||
// 返回传入年份、物候对应的北京时间物候时间。
|
// 返回传入年份、物候对应的北京时间物候时间。
|
||||||
|
// Returns the Beijing-time instant of the requested pentad in the supplied year.
|
||||||
func WuHou(year, term int) time.Time {
|
func WuHou(year, term int) time.Time {
|
||||||
calcJde := basic.GetWuHouTime(year, term)
|
calcJde := basic.GetWuHouTime(year, term)
|
||||||
zone := time.FixedZone("CST", 8*3600)
|
zone := time.FixedZone("CST", 8*3600)
|
||||||
|
|||||||
@@ -52,6 +52,7 @@ type Time struct {
|
|||||||
// Solar 公历时间 / solar time.
|
// Solar 公历时间 / solar time.
|
||||||
//
|
//
|
||||||
// 返回内部保存的公历 `time.Time`,不做时区或历法再计算。
|
// 返回内部保存的公历 `time.Time`,不做时区或历法再计算。
|
||||||
|
// Returns the stored civil `time.Time` directly, without any further time-zone or calendar conversion.
|
||||||
func (t Time) Solar() time.Time {
|
func (t Time) Solar() time.Time {
|
||||||
return t.solarTime
|
return t.solarTime
|
||||||
}
|
}
|
||||||
@@ -59,6 +60,7 @@ func (t Time) Solar() time.Time {
|
|||||||
// Time 公历时间 / solar time.
|
// Time 公历时间 / solar time.
|
||||||
//
|
//
|
||||||
// 是 `Solar` 的同义接口,便于把 `calendar.Time` 当作普通时间对象使用。
|
// 是 `Solar` 的同义接口,便于把 `calendar.Time` 当作普通时间对象使用。
|
||||||
|
// This is an alias of `Solar`, convenient when `calendar.Time` is used as a regular time object.
|
||||||
func (t Time) Time() time.Time {
|
func (t Time) Time() time.Time {
|
||||||
return t.solarTime
|
return t.solarTime
|
||||||
}
|
}
|
||||||
@@ -66,6 +68,7 @@ func (t Time) Time() time.Time {
|
|||||||
// Lunars 农历候选结果 / lunar candidates.
|
// Lunars 农历候选结果 / lunar candidates.
|
||||||
//
|
//
|
||||||
// 返回全部候选农历结果。
|
// 返回全部候选农历结果。
|
||||||
|
// Returns all candidate lunar-calendar results.
|
||||||
func (t Time) Lunars() []LunarTime {
|
func (t Time) Lunars() []LunarTime {
|
||||||
return t.lunars
|
return t.lunars
|
||||||
}
|
}
|
||||||
@@ -73,6 +76,7 @@ func (t Time) Lunars() []LunarTime {
|
|||||||
// LunarDesc 农历描述 / lunar descriptions.
|
// LunarDesc 农历描述 / lunar descriptions.
|
||||||
//
|
//
|
||||||
// 返回全部候选结果的农历描述,如开元二年正月初一;若无年号,则返回年份描述,如二零二五年正月初一。
|
// 返回全部候选结果的农历描述,如开元二年正月初一;若无年号,则返回年份描述,如二零二五年正月初一。
|
||||||
|
// Returns the lunar-date descriptions for all candidates. If no era name is available, the year is described directly.
|
||||||
func (t Time) LunarDesc() []string {
|
func (t Time) LunarDesc() []string {
|
||||||
var res []string
|
var res []string
|
||||||
for _, v := range t.lunars {
|
for _, v := range t.lunars {
|
||||||
@@ -84,6 +88,7 @@ func (t Time) LunarDesc() []string {
|
|||||||
// LunarDescWithEmperor 含君主信息的农历描述 / lunar descriptions with emperor.
|
// LunarDescWithEmperor 含君主信息的农历描述 / lunar descriptions with emperor.
|
||||||
//
|
//
|
||||||
// 返回全部候选结果中含有君主信息的农历描述,如唐玄宗 开元二年正月初一;若无年号,则返回年份描述,如二零二五年正月初一。
|
// 返回全部候选结果中含有君主信息的农历描述,如唐玄宗 开元二年正月初一;若无年号,则返回年份描述,如二零二五年正月初一。
|
||||||
|
// Returns the candidate descriptions with emperor names when available. If no era name is available, the year is described directly.
|
||||||
func (t Time) LunarDescWithEmperor() []string {
|
func (t Time) LunarDescWithEmperor() []string {
|
||||||
var res []string
|
var res []string
|
||||||
for _, v := range t.lunars {
|
for _, v := range t.lunars {
|
||||||
@@ -95,6 +100,7 @@ func (t Time) LunarDescWithEmperor() []string {
|
|||||||
// LunarDescWithDynasty 含朝代信息的农历描述 / lunar descriptions with dynasty.
|
// LunarDescWithDynasty 含朝代信息的农历描述 / lunar descriptions with dynasty.
|
||||||
//
|
//
|
||||||
// 返回全部候选结果中含有朝代信息的农历描述,如唐 开元二年正月初一;若无年号,则返回年份描述,如二零二五年正月初一。
|
// 返回全部候选结果中含有朝代信息的农历描述,如唐 开元二年正月初一;若无年号,则返回年份描述,如二零二五年正月初一。
|
||||||
|
// Returns the candidate descriptions with dynasty names when available. If no era name is available, the year is described directly.
|
||||||
func (t Time) LunarDescWithDynasty() []string {
|
func (t Time) LunarDescWithDynasty() []string {
|
||||||
var res []string
|
var res []string
|
||||||
for _, v := range t.lunars {
|
for _, v := range t.lunars {
|
||||||
@@ -106,6 +112,7 @@ func (t Time) LunarDescWithDynasty() []string {
|
|||||||
// LunarDescWithDynastyAndEmperor 含朝代与君主信息的农历描述 / lunar descriptions with dynasty and emperor.
|
// LunarDescWithDynastyAndEmperor 含朝代与君主信息的农历描述 / lunar descriptions with dynasty and emperor.
|
||||||
//
|
//
|
||||||
// 返回全部候选结果中含有朝代和君主信息的农历描述,如唐 唐玄宗 开元二年正月初一;若无年号,则返回年份描述,如二零二五年正月初一。
|
// 返回全部候选结果中含有朝代和君主信息的农历描述,如唐 唐玄宗 开元二年正月初一;若无年号,则返回年份描述,如二零二五年正月初一。
|
||||||
|
// Returns the candidate descriptions with both dynasty and emperor names when available. If no era name is available, the year is described directly.
|
||||||
func (t Time) LunarDescWithDynastyAndEmperor() []string {
|
func (t Time) LunarDescWithDynastyAndEmperor() []string {
|
||||||
var res []string
|
var res []string
|
||||||
for _, v := range t.lunars {
|
for _, v := range t.lunars {
|
||||||
@@ -117,6 +124,7 @@ func (t Time) LunarDescWithDynastyAndEmperor() []string {
|
|||||||
// LunarInfo 农历结构化信息 / structured lunar information.
|
// LunarInfo 农历结构化信息 / structured lunar information.
|
||||||
//
|
//
|
||||||
// 返回全部候选结果对应的结构化农历信息切片。
|
// 返回全部候选结果对应的结构化农历信息切片。
|
||||||
|
// Returns the structured lunar-calendar information for all candidates.
|
||||||
func (t Time) LunarInfo() []LunarInfo {
|
func (t Time) LunarInfo() []LunarInfo {
|
||||||
var res []LunarInfo
|
var res []LunarInfo
|
||||||
for _, v := range t.lunars {
|
for _, v := range t.lunars {
|
||||||
@@ -128,6 +136,7 @@ func (t Time) LunarInfo() []LunarInfo {
|
|||||||
// Eras 朝代、皇帝、年号信息 / era information.
|
// Eras 朝代、皇帝、年号信息 / era information.
|
||||||
//
|
//
|
||||||
// 返回全部候选结果对应的朝代、皇帝、年号信息。
|
// 返回全部候选结果对应的朝代、皇帝、年号信息。
|
||||||
|
// Returns the dynasty, emperor, and era-name records associated with all candidates.
|
||||||
func (t Time) Eras() []EraDesc {
|
func (t Time) Eras() []EraDesc {
|
||||||
var res []EraDesc
|
var res []EraDesc
|
||||||
for _, v := range t.lunars {
|
for _, v := range t.lunars {
|
||||||
@@ -139,6 +148,7 @@ func (t Time) Eras() []EraDesc {
|
|||||||
// Lunar 首个农历结果 / first lunar result.
|
// Lunar 首个农历结果 / first lunar result.
|
||||||
//
|
//
|
||||||
// 若存在多个候选结果,只返回第一个;无结果时返回零值 `LunarTime`。
|
// 若存在多个候选结果,只返回第一个;无结果时返回零值 `LunarTime`。
|
||||||
|
// Returns only the first candidate when multiple results exist. A zero-value `LunarTime` is returned when no result is available.
|
||||||
func (t Time) Lunar() LunarTime {
|
func (t Time) Lunar() LunarTime {
|
||||||
if len(t.lunars) > 0 {
|
if len(t.lunars) > 0 {
|
||||||
return t.lunars[0]
|
return t.lunars[0]
|
||||||
@@ -149,6 +159,7 @@ func (t Time) Lunar() LunarTime {
|
|||||||
// Add 时间偏移 / add a duration.
|
// Add 时间偏移 / add a duration.
|
||||||
//
|
//
|
||||||
// 返回公历时间偏移后的农历结果。
|
// 返回公历时间偏移后的农历结果。
|
||||||
|
// Returns the lunar-calendar result after applying the duration to the stored civil time.
|
||||||
func (t Time) Add(d time.Duration) Time {
|
func (t Time) Add(d time.Duration) Time {
|
||||||
if d < time.Second {
|
if d < time.Second {
|
||||||
newT := t.solarTime.Add(d)
|
newT := t.solarTime.Add(d)
|
||||||
@@ -236,6 +247,7 @@ func (l LunarTime) IsLeap() bool {
|
|||||||
// Eras 朝代、皇帝、年号信息 / era information.
|
// Eras 朝代、皇帝、年号信息 / era information.
|
||||||
//
|
//
|
||||||
// 返回该农历结果对应的朝代、皇帝、年号信息。
|
// 返回该农历结果对应的朝代、皇帝、年号信息。
|
||||||
|
// Returns the dynasty, emperor, and era-name records associated with this lunar result.
|
||||||
func (l LunarTime) Eras() []EraDesc {
|
func (l LunarTime) Eras() []EraDesc {
|
||||||
return l.eras
|
return l.eras
|
||||||
}
|
}
|
||||||
@@ -243,6 +255,7 @@ func (l LunarTime) Eras() []EraDesc {
|
|||||||
// MonthDay 农历月日描述 / lunar month-day description.
|
// MonthDay 农历月日描述 / lunar month-day description.
|
||||||
//
|
//
|
||||||
// 获取农历月日描述,如正月初一。此处,十一月表示为冬月,十二月表示为腊月。
|
// 获取农历月日描述,如正月初一。此处,十一月表示为冬月,十二月表示为腊月。
|
||||||
|
// Returns the lunar month-day description, such as `正月初一`. In this package, month 11 is written as `冬月` and month 12 as `腊月`.
|
||||||
func (l LunarTime) MonthDay() string {
|
func (l LunarTime) MonthDay() string {
|
||||||
return l.desc
|
return l.desc
|
||||||
}
|
}
|
||||||
@@ -250,6 +263,7 @@ func (l LunarTime) MonthDay() string {
|
|||||||
// LunarDesc 农历描述 / lunar descriptions.
|
// LunarDesc 农历描述 / lunar descriptions.
|
||||||
//
|
//
|
||||||
// 获取农历描述,如开元二年正月初一,若无年号,则返回年份描述,如二零二五年正月初一。
|
// 获取农历描述,如开元二年正月初一,若无年号,则返回年份描述,如二零二五年正月初一。
|
||||||
|
// Returns the lunar-date descriptions for this result. If no era name is available, the year is described directly.
|
||||||
func (l LunarTime) LunarDesc() []string {
|
func (l LunarTime) LunarDesc() []string {
|
||||||
return l.innerDescWithNianHao(false, false)
|
return l.innerDescWithNianHao(false, false)
|
||||||
}
|
}
|
||||||
@@ -258,6 +272,7 @@ func (l LunarTime) LunarDesc() []string {
|
|||||||
//
|
//
|
||||||
// 获取含有君主信息的农历描述,如唐玄宗 开元二年正月初一,若无年号,则返回年份描述,如二零二五年正月初一。
|
// 获取含有君主信息的农历描述,如唐玄宗 开元二年正月初一,若无年号,则返回年份描述,如二零二五年正月初一。
|
||||||
// 君主信息仅供参考,多个皇帝用同一个年号的场景,此处不准
|
// 君主信息仅供参考,多个皇帝用同一个年号的场景,此处不准
|
||||||
|
// Returns the lunar-date descriptions with emperor names when available. Emperor names are for reference only and may be ambiguous when multiple emperors used the same era name.
|
||||||
func (l LunarTime) LunarDescWithEmperor() []string {
|
func (l LunarTime) LunarDescWithEmperor() []string {
|
||||||
return l.innerDescWithNianHao(true, false)
|
return l.innerDescWithNianHao(true, false)
|
||||||
}
|
}
|
||||||
@@ -265,6 +280,7 @@ func (l LunarTime) LunarDescWithEmperor() []string {
|
|||||||
// LunarDescWithDynasty 含朝代信息的农历描述 / lunar descriptions with dynasty.
|
// LunarDescWithDynasty 含朝代信息的农历描述 / lunar descriptions with dynasty.
|
||||||
//
|
//
|
||||||
// 获取含有朝代信息的农历描述,如唐 开元二年正月初一,若无年号,则返回年份描述,如二零二五年正月初一。
|
// 获取含有朝代信息的农历描述,如唐 开元二年正月初一,若无年号,则返回年份描述,如二零二五年正月初一。
|
||||||
|
// Returns the lunar-date descriptions with dynasty names when available. If no era name is available, the year is described directly.
|
||||||
func (l LunarTime) LunarDescWithDynasty() []string {
|
func (l LunarTime) LunarDescWithDynasty() []string {
|
||||||
return l.innerDescWithNianHao(false, true)
|
return l.innerDescWithNianHao(false, true)
|
||||||
}
|
}
|
||||||
@@ -273,6 +289,7 @@ func (l LunarTime) LunarDescWithDynasty() []string {
|
|||||||
//
|
//
|
||||||
// 获取含有朝代和君主信息的农历描述,如唐 唐玄宗 开元二年正月初一,若无年号,则返回年份描述,如二零二五年正月初一。
|
// 获取含有朝代和君主信息的农历描述,如唐 唐玄宗 开元二年正月初一,若无年号,则返回年份描述,如二零二五年正月初一。
|
||||||
// 君主信息仅供参考,多个皇帝用同一个年号的场景,此处不准
|
// 君主信息仅供参考,多个皇帝用同一个年号的场景,此处不准
|
||||||
|
// Returns the lunar-date descriptions with both dynasty and emperor names when available. Emperor names are for reference only and may be ambiguous when multiple emperors used the same era name.
|
||||||
func (l LunarTime) LunarDescWithDynastyAndEmperor() []string {
|
func (l LunarTime) LunarDescWithDynastyAndEmperor() []string {
|
||||||
return l.innerDescWithNianHao(true, true)
|
return l.innerDescWithNianHao(true, true)
|
||||||
}
|
}
|
||||||
@@ -299,6 +316,7 @@ func (l LunarTime) innerDescWithNianHao(withEmperor bool, withDynasty bool) []st
|
|||||||
// LunarInfo 农历结构化信息 / structured lunar information.
|
// LunarInfo 农历结构化信息 / structured lunar information.
|
||||||
//
|
//
|
||||||
// 返回该农历结果对应的结构化农历信息切片;若存在多个并行年号,则会有多条记录。
|
// 返回该农历结果对应的结构化农历信息切片;若存在多个并行年号,则会有多条记录。
|
||||||
|
// Returns the structured lunar-calendar information for this result. Multiple records are returned when parallel era-name interpretations exist.
|
||||||
func (l LunarTime) LunarInfo() []LunarInfo {
|
func (l LunarTime) LunarInfo() []LunarInfo {
|
||||||
var res []LunarInfo
|
var res []LunarInfo
|
||||||
for _, v := range l.eras {
|
for _, v := range l.eras {
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import "b612.me/astro/formula"
|
|||||||
// AirmassPlaneParallelFromTrueAltitude 平行平板大气质量 / plane-parallel airmass from true altitude.
|
// AirmassPlaneParallelFromTrueAltitude 平行平板大气质量 / plane-parallel airmass from true altitude.
|
||||||
//
|
//
|
||||||
// 输入为真高度角,单位度。适合中高空几何近似,接近地平线时会发散。
|
// 输入为真高度角,单位度。适合中高空几何近似,接近地平线时会发散。
|
||||||
|
// Input is true altitude in degrees. This geometric approximation is suitable at moderate and high altitudes but diverges near the horizon.
|
||||||
func AirmassPlaneParallelFromTrueAltitude(trueAltitude float64) float64 {
|
func AirmassPlaneParallelFromTrueAltitude(trueAltitude float64) float64 {
|
||||||
return formula.AirmassPlaneParallel(trueAltitude)
|
return formula.AirmassPlaneParallel(trueAltitude)
|
||||||
}
|
}
|
||||||
@@ -12,6 +13,7 @@ func AirmassPlaneParallelFromTrueAltitude(trueAltitude float64) float64 {
|
|||||||
// AirmassKastenYoungFromApparentAltitude Kasten-Young 大气质量 / Kasten-Young airmass from apparent altitude.
|
// AirmassKastenYoungFromApparentAltitude Kasten-Young 大气质量 / Kasten-Young airmass from apparent altitude.
|
||||||
//
|
//
|
||||||
// 输入为视高度角,单位度。
|
// 输入为视高度角,单位度。
|
||||||
|
// Input is apparent altitude in degrees.
|
||||||
func AirmassKastenYoungFromApparentAltitude(apparentAltitude float64) float64 {
|
func AirmassKastenYoungFromApparentAltitude(apparentAltitude float64) float64 {
|
||||||
return formula.AirmassKastenYoung(apparentAltitude)
|
return formula.AirmassKastenYoung(apparentAltitude)
|
||||||
}
|
}
|
||||||
@@ -19,6 +21,7 @@ func AirmassKastenYoungFromApparentAltitude(apparentAltitude float64) float64 {
|
|||||||
// AirmassPickeringFromApparentAltitude Pickering 大气质量 / Pickering airmass from apparent altitude.
|
// AirmassPickeringFromApparentAltitude Pickering 大气质量 / Pickering airmass from apparent altitude.
|
||||||
//
|
//
|
||||||
// 输入为视高度角,单位度。
|
// 输入为视高度角,单位度。
|
||||||
|
// Input is apparent altitude in degrees.
|
||||||
func AirmassPickeringFromApparentAltitude(apparentAltitude float64) float64 {
|
func AirmassPickeringFromApparentAltitude(apparentAltitude float64) float64 {
|
||||||
return formula.AirmassPickering(apparentAltitude)
|
return formula.AirmassPickering(apparentAltitude)
|
||||||
}
|
}
|
||||||
@@ -26,6 +29,7 @@ func AirmassPickeringFromApparentAltitude(apparentAltitude float64) float64 {
|
|||||||
// AirmassKastenYoungFromTrueAltitude Kasten-Young 大气质量 / Kasten-Young airmass from true altitude.
|
// AirmassKastenYoungFromTrueAltitude Kasten-Young 大气质量 / Kasten-Young airmass from true altitude.
|
||||||
//
|
//
|
||||||
// 先用 pressureHPa / temperatureC 估算大气折射,将真高度角换算为视高度角,再代入经验公式。
|
// 先用 pressureHPa / temperatureC 估算大气折射,将真高度角换算为视高度角,再代入经验公式。
|
||||||
|
// First estimates atmospheric refraction from pressureHPa and temperatureC, converts true altitude to apparent altitude, and then applies the empirical formula.
|
||||||
func AirmassKastenYoungFromTrueAltitude(trueAltitude, pressureHPa, temperatureC float64) float64 {
|
func AirmassKastenYoungFromTrueAltitude(trueAltitude, pressureHPa, temperatureC float64) float64 {
|
||||||
return formula.AirmassKastenYoung(ApparentAltitude(trueAltitude, pressureHPa, temperatureC))
|
return formula.AirmassKastenYoung(ApparentAltitude(trueAltitude, pressureHPa, temperatureC))
|
||||||
}
|
}
|
||||||
@@ -33,6 +37,7 @@ func AirmassKastenYoungFromTrueAltitude(trueAltitude, pressureHPa, temperatureC
|
|||||||
// AirmassPickeringFromTrueAltitude Pickering 大气质量 / Pickering airmass from true altitude.
|
// AirmassPickeringFromTrueAltitude Pickering 大气质量 / Pickering airmass from true altitude.
|
||||||
//
|
//
|
||||||
// 先用 pressureHPa / temperatureC 估算大气折射,将真高度角换算为视高度角,再代入经验公式。
|
// 先用 pressureHPa / temperatureC 估算大气折射,将真高度角换算为视高度角,再代入经验公式。
|
||||||
|
// First estimates atmospheric refraction from pressureHPa and temperatureC, converts true altitude to apparent altitude, and then applies the empirical formula.
|
||||||
func AirmassPickeringFromTrueAltitude(trueAltitude, pressureHPa, temperatureC float64) float64 {
|
func AirmassPickeringFromTrueAltitude(trueAltitude, pressureHPa, temperatureC float64) float64 {
|
||||||
return formula.AirmassPickering(ApparentAltitude(trueAltitude, pressureHPa, temperatureC))
|
return formula.AirmassPickering(ApparentAltitude(trueAltitude, pressureHPa, temperatureC))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import (
|
|||||||
// AtmosphericRefractionFromTrueAltitude 真高度角折射修正 / atmospheric refraction from true altitude.
|
// AtmosphericRefractionFromTrueAltitude 真高度角折射修正 / atmospheric refraction from true altitude.
|
||||||
//
|
//
|
||||||
// 输入真高度角,返回应加到真高度角上的大气折射修正量,单位度。
|
// 输入真高度角,返回应加到真高度角上的大气折射修正量,单位度。
|
||||||
|
// Takes true altitude and returns the atmospheric-refraction correction to be added to it, in degrees.
|
||||||
func AtmosphericRefractionFromTrueAltitude(trueAltitude, pressureHPa, temperatureC float64) float64 {
|
func AtmosphericRefractionFromTrueAltitude(trueAltitude, pressureHPa, temperatureC float64) float64 {
|
||||||
return basic.RefractionFromTrueAltitude(trueAltitude, pressureHPa, temperatureC)
|
return basic.RefractionFromTrueAltitude(trueAltitude, pressureHPa, temperatureC)
|
||||||
}
|
}
|
||||||
@@ -16,6 +17,7 @@ func AtmosphericRefractionFromTrueAltitude(trueAltitude, pressureHPa, temperatur
|
|||||||
// AtmosphericRefractionFromApparentAltitude 视高度角折射修正 / atmospheric refraction from apparent altitude.
|
// AtmosphericRefractionFromApparentAltitude 视高度角折射修正 / atmospheric refraction from apparent altitude.
|
||||||
//
|
//
|
||||||
// 输入视高度角,返回对应的大气折射修正量,单位度。
|
// 输入视高度角,返回对应的大气折射修正量,单位度。
|
||||||
|
// Takes apparent altitude and returns the corresponding atmospheric-refraction correction, in degrees.
|
||||||
func AtmosphericRefractionFromApparentAltitude(apparentAltitude, pressureHPa, temperatureC float64) float64 {
|
func AtmosphericRefractionFromApparentAltitude(apparentAltitude, pressureHPa, temperatureC float64) float64 {
|
||||||
return basic.RefractionFromApparentAltitude(apparentAltitude, pressureHPa, temperatureC)
|
return basic.RefractionFromApparentAltitude(apparentAltitude, pressureHPa, temperatureC)
|
||||||
}
|
}
|
||||||
@@ -23,6 +25,7 @@ func AtmosphericRefractionFromApparentAltitude(apparentAltitude, pressureHPa, te
|
|||||||
// ApparentAltitude 真高度角转视高度角 / apparent altitude from true altitude.
|
// ApparentAltitude 真高度角转视高度角 / apparent altitude from true altitude.
|
||||||
//
|
//
|
||||||
// 输入真高度角,返回加入标准大气折射后的视高度角,单位度。
|
// 输入真高度角,返回加入标准大气折射后的视高度角,单位度。
|
||||||
|
// Takes true altitude and returns the apparent altitude after applying standard atmospheric refraction, in degrees.
|
||||||
func ApparentAltitude(trueAltitude, pressureHPa, temperatureC float64) float64 {
|
func ApparentAltitude(trueAltitude, pressureHPa, temperatureC float64) float64 {
|
||||||
return basic.ApparentAltitude(trueAltitude, pressureHPa, temperatureC)
|
return basic.ApparentAltitude(trueAltitude, pressureHPa, temperatureC)
|
||||||
}
|
}
|
||||||
@@ -30,6 +33,7 @@ func ApparentAltitude(trueAltitude, pressureHPa, temperatureC float64) float64 {
|
|||||||
// TrueAltitude 视高度角转真高度角 / true altitude from apparent altitude.
|
// TrueAltitude 视高度角转真高度角 / true altitude from apparent altitude.
|
||||||
//
|
//
|
||||||
// 输入视高度角,返回去除大气折射后的真高度角,单位度。
|
// 输入视高度角,返回去除大气折射后的真高度角,单位度。
|
||||||
|
// Takes apparent altitude and returns the true altitude after removing atmospheric refraction, in degrees.
|
||||||
func TrueAltitude(apparentAltitude, pressureHPa, temperatureC float64) float64 {
|
func TrueAltitude(apparentAltitude, pressureHPa, temperatureC float64) float64 {
|
||||||
return basic.TrueAltitude(apparentAltitude, pressureHPa, temperatureC)
|
return basic.TrueAltitude(apparentAltitude, pressureHPa, temperatureC)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,6 +25,8 @@ var icrsToGalacticMatrix = [3][3]float64{
|
|||||||
// 返回:
|
// 返回:
|
||||||
//
|
//
|
||||||
// 赤经 RA,单位度;赤纬 Dec,单位度
|
// 赤经 RA,单位度;赤纬 Dec,单位度
|
||||||
|
//
|
||||||
|
// Returns right ascension and declination in degrees for the supplied ecliptic longitude, latitude, and obliquity.
|
||||||
func EclipticToEquatorialByObliquity(lon, lat, obliquity float64) Equatorial {
|
func EclipticToEquatorialByObliquity(lon, lat, obliquity float64) Equatorial {
|
||||||
sinLon, cosLon := sinCosDeg(lon)
|
sinLon, cosLon := sinCosDeg(lon)
|
||||||
sinLat, cosLat := sinCosDeg(lat)
|
sinLat, cosLat := sinCosDeg(lat)
|
||||||
@@ -44,6 +46,8 @@ func EclipticToEquatorialByObliquity(lon, lat, obliquity float64) Equatorial {
|
|||||||
// 返回:
|
// 返回:
|
||||||
//
|
//
|
||||||
// 黄经 Lon,单位度;黄纬 Lat,单位度
|
// 黄经 Lon,单位度;黄纬 Lat,单位度
|
||||||
|
//
|
||||||
|
// Returns ecliptic longitude and latitude in degrees for the supplied right ascension, declination, and obliquity.
|
||||||
func EquatorialToEclipticByObliquity(ra, dec, obliquity float64) Ecliptic {
|
func EquatorialToEclipticByObliquity(ra, dec, obliquity float64) Ecliptic {
|
||||||
sinRA, cosRA := sinCosDeg(ra)
|
sinRA, cosRA := sinCosDeg(ra)
|
||||||
sinDec, cosDec := sinCosDeg(dec)
|
sinDec, cosDec := sinCosDeg(dec)
|
||||||
@@ -63,6 +67,8 @@ func EquatorialToEclipticByObliquity(ra, dec, obliquity float64) Ecliptic {
|
|||||||
// 返回:
|
// 返回:
|
||||||
//
|
//
|
||||||
// 方位角 Azimuth(正北为0,顺时针增加)、高度角 Altitude、天顶距 Zenith,均为度
|
// 方位角 Azimuth(正北为0,顺时针增加)、高度角 Altitude、天顶距 Zenith,均为度
|
||||||
|
//
|
||||||
|
// Returns azimuth, altitude, and zenith distance in degrees from the supplied hour angle, declination, and site latitude.
|
||||||
func HourAngleDeclinationToHorizontal(hourAngle, declination, latitude float64) Horizontal {
|
func HourAngleDeclinationToHorizontal(hourAngle, declination, latitude float64) Horizontal {
|
||||||
sinLatitude, cosLatitude := sinCosDeg(latitude)
|
sinLatitude, cosLatitude := sinCosDeg(latitude)
|
||||||
sinDeclination, cosDeclination := sinCosDeg(declination)
|
sinDeclination, cosDeclination := sinCosDeg(declination)
|
||||||
@@ -87,6 +93,8 @@ func HourAngleDeclinationToHorizontal(hourAngle, declination, latitude float64)
|
|||||||
// 返回:
|
// 返回:
|
||||||
//
|
//
|
||||||
// 时角 HourAngle,单位度;赤纬 Declination,单位度
|
// 时角 HourAngle,单位度;赤纬 Declination,单位度
|
||||||
|
//
|
||||||
|
// Returns hour angle and declination in degrees from the supplied horizontal coordinates and site latitude.
|
||||||
func HorizontalToHourAngleDeclination(azimuth, altitude, latitude float64) (hourAngle, declination float64) {
|
func HorizontalToHourAngleDeclination(azimuth, altitude, latitude float64) (hourAngle, declination float64) {
|
||||||
sinLatitude, cosLatitude := sinCosDeg(latitude)
|
sinLatitude, cosLatitude := sinCosDeg(latitude)
|
||||||
sinAltitude, cosAltitude := sinCosDeg(altitude)
|
sinAltitude, cosAltitude := sinCosDeg(altitude)
|
||||||
@@ -111,6 +119,8 @@ func HorizontalToHourAngleDeclination(azimuth, altitude, latitude float64) (hour
|
|||||||
// 方位角 Azimuth(正北为0,顺时针增加)、高度角 Altitude、天顶距 Zenith,均为度;
|
// 方位角 Azimuth(正北为0,顺时针增加)、高度角 Altitude、天顶距 Zenith,均为度;
|
||||||
// 附带返回对应的时角 HourAngle,单位度
|
// 附带返回对应的时角 HourAngle,单位度
|
||||||
//
|
//
|
||||||
|
// Returns horizontal coordinates in degrees from local sidereal time, right ascension, declination, and site latitude.
|
||||||
|
//
|
||||||
// 例:
|
// 例:
|
||||||
//
|
//
|
||||||
// hz := coord.EquatorialToHorizontalByLocalSiderealTime(10.5, 83.6331, 22.0145, 31.2)
|
// hz := coord.EquatorialToHorizontalByLocalSiderealTime(10.5, 83.6331, 22.0145, 31.2)
|
||||||
@@ -130,6 +140,8 @@ func EquatorialToHorizontalByLocalSiderealTime(localSiderealTimeHours, ra, dec,
|
|||||||
//
|
//
|
||||||
// 赤经 RA,单位度;赤纬 Dec,单位度
|
// 赤经 RA,单位度;赤纬 Dec,单位度
|
||||||
//
|
//
|
||||||
|
// Returns right ascension and declination in degrees from local sidereal time and the supplied horizontal coordinates.
|
||||||
|
//
|
||||||
// 例:
|
// 例:
|
||||||
//
|
//
|
||||||
// eq := coord.HorizontalToEquatorialByLocalSiderealTime(10.5, 128.2, 37.6, 31.2)
|
// eq := coord.HorizontalToEquatorialByLocalSiderealTime(10.5, 128.2, 37.6, 31.2)
|
||||||
@@ -147,6 +159,8 @@ func HorizontalToEquatorialByLocalSiderealTime(localSiderealTimeHours, azimuth,
|
|||||||
// 返回:
|
// 返回:
|
||||||
//
|
//
|
||||||
// 银经 Lon,单位度;银纬 Lat,单位度
|
// 银经 Lon,单位度;银纬 Lat,单位度
|
||||||
|
//
|
||||||
|
// Returns galactic longitude and latitude in degrees from ICRS right ascension and declination.
|
||||||
func EquatorialToGalactic(ra, dec float64) Galactic {
|
func EquatorialToGalactic(ra, dec float64) Galactic {
|
||||||
vector := sphericalToVector(ra, dec)
|
vector := sphericalToVector(ra, dec)
|
||||||
rotated := matrixVectorMul(icrsToGalacticMatrix, vector)
|
rotated := matrixVectorMul(icrsToGalacticMatrix, vector)
|
||||||
@@ -162,6 +176,8 @@ func EquatorialToGalactic(ra, dec float64) Galactic {
|
|||||||
// 返回:
|
// 返回:
|
||||||
//
|
//
|
||||||
// ICRS 赤经 RA,单位度;ICRS 赤纬 Dec,单位度
|
// ICRS 赤经 RA,单位度;ICRS 赤纬 Dec,单位度
|
||||||
|
//
|
||||||
|
// Returns ICRS right ascension and declination in degrees from galactic longitude and latitude.
|
||||||
func GalacticToEquatorial(lon, lat float64) Equatorial {
|
func GalacticToEquatorial(lon, lat float64) Equatorial {
|
||||||
vector := sphericalToVector(lon, lat)
|
vector := sphericalToVector(lon, lat)
|
||||||
rotated := matrixTransposeVectorMul(icrsToGalacticMatrix, vector)
|
rotated := matrixTransposeVectorMul(icrsToGalacticMatrix, vector)
|
||||||
|
|||||||
@@ -0,0 +1,151 @@
|
|||||||
|
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/moon"
|
||||||
|
"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 TestPublicMoonPlanetConjunctionBoundaryIncludesCurrent(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: "MoonMercuryConjunction", eventUT: basic.NextMoonPlanetConjunction(eventBoundaryTT(2026), basic.MoonPlanetConjunctionMercury), funcs: eventFuncs{
|
||||||
|
last: func(date time.Time) time.Time { return moon.LastConjunctionWithPlanet(date, moon.ConjunctionMercury) },
|
||||||
|
next: func(date time.Time) time.Time { return moon.NextConjunctionWithPlanet(date, moon.ConjunctionMercury) },
|
||||||
|
}},
|
||||||
|
{name: "MoonVenusConjunction", eventUT: basic.NextMoonPlanetConjunction(eventBoundaryTT(2026), basic.MoonPlanetConjunctionVenus), funcs: eventFuncs{
|
||||||
|
last: func(date time.Time) time.Time { return moon.LastConjunctionWithPlanet(date, moon.ConjunctionVenus) },
|
||||||
|
next: func(date time.Time) time.Time { return moon.NextConjunctionWithPlanet(date, moon.ConjunctionVenus) },
|
||||||
|
}},
|
||||||
|
{name: "MoonMarsConjunction", eventUT: basic.NextMoonPlanetConjunction(eventBoundaryTT(2026), basic.MoonPlanetConjunctionMars), funcs: eventFuncs{
|
||||||
|
last: func(date time.Time) time.Time { return moon.LastConjunctionWithPlanet(date, moon.ConjunctionMars) },
|
||||||
|
next: func(date time.Time) time.Time { return moon.NextConjunctionWithPlanet(date, moon.ConjunctionMars) },
|
||||||
|
}},
|
||||||
|
{name: "MoonJupiterConjunction", eventUT: basic.NextMoonPlanetConjunction(eventBoundaryTT(2026), basic.MoonPlanetConjunctionJupiter), funcs: eventFuncs{
|
||||||
|
last: func(date time.Time) time.Time { return moon.LastConjunctionWithPlanet(date, moon.ConjunctionJupiter) },
|
||||||
|
next: func(date time.Time) time.Time { return moon.NextConjunctionWithPlanet(date, moon.ConjunctionJupiter) },
|
||||||
|
}},
|
||||||
|
{name: "MoonSaturnConjunction", eventUT: basic.NextMoonPlanetConjunction(eventBoundaryTT(2026), basic.MoonPlanetConjunctionSaturn), funcs: eventFuncs{
|
||||||
|
last: func(date time.Time) time.Time { return moon.LastConjunctionWithPlanet(date, moon.ConjunctionSaturn) },
|
||||||
|
next: func(date time.Time) time.Time { return moon.NextConjunctionWithPlanet(date, moon.ConjunctionSaturn) },
|
||||||
|
}},
|
||||||
|
{name: "MoonUranusConjunction", eventUT: basic.NextMoonPlanetConjunction(eventBoundaryTT(2026), basic.MoonPlanetConjunctionUranus), funcs: eventFuncs{
|
||||||
|
last: func(date time.Time) time.Time { return moon.LastConjunctionWithPlanet(date, moon.ConjunctionUranus) },
|
||||||
|
next: func(date time.Time) time.Time { return moon.NextConjunctionWithPlanet(date, moon.ConjunctionUranus) },
|
||||||
|
}},
|
||||||
|
{name: "MoonNeptuneConjunction", eventUT: basic.NextMoonPlanetConjunction(eventBoundaryTT(2026), basic.MoonPlanetConjunctionNeptune), funcs: eventFuncs{
|
||||||
|
last: func(date time.Time) time.Time { return moon.LastConjunctionWithPlanet(date, moon.ConjunctionNeptune) },
|
||||||
|
next: func(date time.Time) time.Time { return moon.NextConjunctionWithPlanet(date, moon.ConjunctionNeptune) },
|
||||||
|
}},
|
||||||
|
}
|
||||||
|
|
||||||
|
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())
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -18,6 +18,8 @@ const (
|
|||||||
// 返回:
|
// 返回:
|
||||||
//
|
//
|
||||||
// 峰值波长,单位米
|
// 峰值波长,单位米
|
||||||
|
//
|
||||||
|
// Returns the wavelength of maximum emission in meters for a blackbody at the supplied temperature.
|
||||||
func WienPeakWavelength(temperatureK float64) float64 {
|
func WienPeakWavelength(temperatureK float64) float64 {
|
||||||
if temperatureK <= 0 || math.IsNaN(temperatureK) || math.IsInf(temperatureK, 0) {
|
if temperatureK <= 0 || math.IsNaN(temperatureK) || math.IsInf(temperatureK, 0) {
|
||||||
return math.NaN()
|
return math.NaN()
|
||||||
@@ -32,6 +34,8 @@ func WienPeakWavelength(temperatureK float64) float64 {
|
|||||||
// 返回:
|
// 返回:
|
||||||
//
|
//
|
||||||
// 单位面积总出射度,单位 W/m^2
|
// 单位面积总出射度,单位 W/m^2
|
||||||
|
//
|
||||||
|
// Returns the total radiant exitance in W/m^2 for a blackbody at the supplied temperature.
|
||||||
func StefanBoltzmannFlux(temperatureK float64) float64 {
|
func StefanBoltzmannFlux(temperatureK float64) float64 {
|
||||||
if temperatureK < 0 || math.IsNaN(temperatureK) || math.IsInf(temperatureK, 0) {
|
if temperatureK < 0 || math.IsNaN(temperatureK) || math.IsInf(temperatureK, 0) {
|
||||||
return math.NaN()
|
return math.NaN()
|
||||||
@@ -48,6 +52,8 @@ func StefanBoltzmannFlux(temperatureK float64) float64 {
|
|||||||
//
|
//
|
||||||
// 谱辐亮度,单位 W·sr^-1·m^-3
|
// 谱辐亮度,单位 W·sr^-1·m^-3
|
||||||
//
|
//
|
||||||
|
// Returns spectral radiance in W·sr^-1·m^-3 at the supplied wavelength and temperature.
|
||||||
|
//
|
||||||
// 例:
|
// 例:
|
||||||
//
|
//
|
||||||
// b := formula.PlanckRadianceByWavelength(500e-9, 5772)
|
// b := formula.PlanckRadianceByWavelength(500e-9, 5772)
|
||||||
|
|||||||
@@ -11,6 +11,8 @@ import "math"
|
|||||||
//
|
//
|
||||||
// 会合周期,单位与输入相同
|
// 会合周期,单位与输入相同
|
||||||
//
|
//
|
||||||
|
// Returns the synodic period in the same unit as the two input periods.
|
||||||
|
//
|
||||||
// 例:
|
// 例:
|
||||||
//
|
//
|
||||||
// // 地球与金星的会合周期,单位天
|
// // 地球与金星的会合周期,单位天
|
||||||
|
|||||||
@@ -9,6 +9,8 @@ import "math"
|
|||||||
// 返回:
|
// 返回:
|
||||||
//
|
//
|
||||||
// 距离模数 m-M
|
// 距离模数 m-M
|
||||||
|
//
|
||||||
|
// Returns the distance modulus m-M for the supplied distance in parsecs.
|
||||||
func DistanceModulus(distanceParsec float64) float64 {
|
func DistanceModulus(distanceParsec float64) float64 {
|
||||||
if distanceParsec <= 0 || math.IsNaN(distanceParsec) || math.IsInf(distanceParsec, 0) {
|
if distanceParsec <= 0 || math.IsNaN(distanceParsec) || math.IsInf(distanceParsec, 0) {
|
||||||
return math.NaN()
|
return math.NaN()
|
||||||
@@ -24,6 +26,8 @@ func DistanceModulus(distanceParsec float64) float64 {
|
|||||||
// 返回:
|
// 返回:
|
||||||
//
|
//
|
||||||
// 视星等 m
|
// 视星等 m
|
||||||
|
//
|
||||||
|
// Returns apparent magnitude from absolute magnitude and distance.
|
||||||
func ApparentMagnitudeFromAbsolute(absoluteMagnitude, distanceParsec float64) float64 {
|
func ApparentMagnitudeFromAbsolute(absoluteMagnitude, distanceParsec float64) float64 {
|
||||||
modulus := DistanceModulus(distanceParsec)
|
modulus := DistanceModulus(distanceParsec)
|
||||||
if math.IsNaN(modulus) {
|
if math.IsNaN(modulus) {
|
||||||
@@ -40,6 +44,8 @@ func ApparentMagnitudeFromAbsolute(absoluteMagnitude, distanceParsec float64) fl
|
|||||||
// 返回:
|
// 返回:
|
||||||
//
|
//
|
||||||
// 绝对星等 M
|
// 绝对星等 M
|
||||||
|
//
|
||||||
|
// Returns absolute magnitude from apparent magnitude and distance.
|
||||||
func AbsoluteMagnitudeFromApparent(apparentMagnitude, distanceParsec float64) float64 {
|
func AbsoluteMagnitudeFromApparent(apparentMagnitude, distanceParsec float64) float64 {
|
||||||
modulus := DistanceModulus(distanceParsec)
|
modulus := DistanceModulus(distanceParsec)
|
||||||
if math.IsNaN(modulus) {
|
if math.IsNaN(modulus) {
|
||||||
|
|||||||
@@ -16,6 +16,8 @@ const (
|
|||||||
// 返回:
|
// 返回:
|
||||||
//
|
//
|
||||||
// 总光度,单位瓦特
|
// 总光度,单位瓦特
|
||||||
|
//
|
||||||
|
// Returns stellar luminosity in watts from radius and effective temperature.
|
||||||
func LuminosityFromRadiusTemperature(radiusM, temperatureK float64) float64 {
|
func LuminosityFromRadiusTemperature(radiusM, temperatureK float64) float64 {
|
||||||
if radiusM <= 0 || temperatureK <= 0 ||
|
if radiusM <= 0 || temperatureK <= 0 ||
|
||||||
math.IsNaN(radiusM) || math.IsInf(radiusM, 0) ||
|
math.IsNaN(radiusM) || math.IsInf(radiusM, 0) ||
|
||||||
@@ -33,6 +35,8 @@ func LuminosityFromRadiusTemperature(radiusM, temperatureK float64) float64 {
|
|||||||
// 返回:
|
// 返回:
|
||||||
//
|
//
|
||||||
// 恒星半径,单位米
|
// 恒星半径,单位米
|
||||||
|
//
|
||||||
|
// Returns stellar radius in meters from luminosity and effective temperature.
|
||||||
func RadiusFromLuminosityTemperature(luminosityW, temperatureK float64) float64 {
|
func RadiusFromLuminosityTemperature(luminosityW, temperatureK float64) float64 {
|
||||||
if luminosityW <= 0 || temperatureK <= 0 ||
|
if luminosityW <= 0 || temperatureK <= 0 ||
|
||||||
math.IsNaN(luminosityW) || math.IsInf(luminosityW, 0) ||
|
math.IsNaN(luminosityW) || math.IsInf(luminosityW, 0) ||
|
||||||
@@ -54,6 +58,8 @@ func RadiusFromLuminosityTemperature(luminosityW, temperatureK float64) float64
|
|||||||
// 返回:
|
// 返回:
|
||||||
//
|
//
|
||||||
// 恒星有效温度,单位开尔文
|
// 恒星有效温度,单位开尔文
|
||||||
|
//
|
||||||
|
// Returns stellar effective temperature in kelvin from luminosity and radius.
|
||||||
func EffectiveTemperatureFromLuminosityRadius(luminosityW, radiusM float64) float64 {
|
func EffectiveTemperatureFromLuminosityRadius(luminosityW, radiusM float64) float64 {
|
||||||
if luminosityW <= 0 || radiusM <= 0 ||
|
if luminosityW <= 0 || radiusM <= 0 ||
|
||||||
math.IsNaN(luminosityW) || math.IsInf(luminosityW, 0) ||
|
math.IsNaN(luminosityW) || math.IsInf(luminosityW, 0) ||
|
||||||
@@ -75,6 +81,8 @@ func EffectiveTemperatureFromLuminosityRadius(luminosityW, radiusM float64) floa
|
|||||||
// 返回:
|
// 返回:
|
||||||
//
|
//
|
||||||
// 总光度,单位为太阳光度 L☉
|
// 总光度,单位为太阳光度 L☉
|
||||||
|
//
|
||||||
|
// Returns luminosity in solar units from radius in solar radii and effective temperature.
|
||||||
func LuminositySolarFromRadiusTemperature(radiusSolar, temperatureK float64) float64 {
|
func LuminositySolarFromRadiusTemperature(radiusSolar, temperatureK float64) float64 {
|
||||||
if radiusSolar <= 0 || temperatureK <= 0 ||
|
if radiusSolar <= 0 || temperatureK <= 0 ||
|
||||||
math.IsNaN(radiusSolar) || math.IsInf(radiusSolar, 0) ||
|
math.IsNaN(radiusSolar) || math.IsInf(radiusSolar, 0) ||
|
||||||
@@ -92,6 +100,8 @@ func LuminositySolarFromRadiusTemperature(radiusSolar, temperatureK float64) flo
|
|||||||
// 返回:
|
// 返回:
|
||||||
//
|
//
|
||||||
// 恒星半径,单位为太阳半径 R☉
|
// 恒星半径,单位为太阳半径 R☉
|
||||||
|
//
|
||||||
|
// Returns radius in solar radii from luminosity in solar units and effective temperature.
|
||||||
func RadiusSolarFromLuminosityTemperature(luminositySolar, temperatureK float64) float64 {
|
func RadiusSolarFromLuminosityTemperature(luminositySolar, temperatureK float64) float64 {
|
||||||
if luminositySolar <= 0 || temperatureK <= 0 ||
|
if luminositySolar <= 0 || temperatureK <= 0 ||
|
||||||
math.IsNaN(luminositySolar) || math.IsInf(luminositySolar, 0) ||
|
math.IsNaN(luminositySolar) || math.IsInf(luminositySolar, 0) ||
|
||||||
@@ -110,6 +120,8 @@ func RadiusSolarFromLuminosityTemperature(luminositySolar, temperatureK float64)
|
|||||||
//
|
//
|
||||||
// 恒星有效温度,单位开尔文
|
// 恒星有效温度,单位开尔文
|
||||||
//
|
//
|
||||||
|
// Returns stellar effective temperature in kelvin from luminosity and radius expressed in solar units.
|
||||||
|
//
|
||||||
// 例:
|
// 例:
|
||||||
//
|
//
|
||||||
// // 半径 2.5 R☉、光度 20 L☉ 的主序星
|
// // 半径 2.5 R☉、光度 20 L☉ 的主序星
|
||||||
@@ -128,6 +140,8 @@ func EffectiveTemperatureFromLuminositySolarRadius(luminositySolar, radiusSolar
|
|||||||
// 返回:
|
// 返回:
|
||||||
//
|
//
|
||||||
// 太阳有效温度,单位开尔文
|
// 太阳有效温度,单位开尔文
|
||||||
|
//
|
||||||
|
// Returns the adopted solar effective temperature constant in kelvin.
|
||||||
func SolarEffectiveTemperature() float64 {
|
func SolarEffectiveTemperature() float64 {
|
||||||
return solarEffectiveTempK
|
return solarEffectiveTempK
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,6 +12,8 @@ const darkAdaptedPupilDiameterMM = 7.0
|
|||||||
// 返回:
|
// 返回:
|
||||||
//
|
//
|
||||||
// 集光力比值,等于 (diameter1MM / diameter2MM)^2
|
// 集光力比值,等于 (diameter1MM / diameter2MM)^2
|
||||||
|
//
|
||||||
|
// Returns the light-gathering power ratio, equal to (diameter1MM / diameter2MM)^2.
|
||||||
func LightGatheringPowerRatio(diameter1MM, diameter2MM float64) float64 {
|
func LightGatheringPowerRatio(diameter1MM, diameter2MM float64) float64 {
|
||||||
if diameter1MM <= 0 || diameter2MM <= 0 ||
|
if diameter1MM <= 0 || diameter2MM <= 0 ||
|
||||||
math.IsNaN(diameter1MM) || math.IsInf(diameter1MM, 0) ||
|
math.IsNaN(diameter1MM) || math.IsInf(diameter1MM, 0) ||
|
||||||
@@ -28,6 +30,8 @@ func LightGatheringPowerRatio(diameter1MM, diameter2MM float64) float64 {
|
|||||||
// 返回:
|
// 返回:
|
||||||
//
|
//
|
||||||
// Dawes 极限,单位角秒
|
// Dawes 极限,单位角秒
|
||||||
|
//
|
||||||
|
// Returns the Dawes limit in arcseconds for the supplied aperture.
|
||||||
func DawesLimitArcsec(diameterMM float64) float64 {
|
func DawesLimitArcsec(diameterMM float64) float64 {
|
||||||
if diameterMM <= 0 || math.IsNaN(diameterMM) || math.IsInf(diameterMM, 0) {
|
if diameterMM <= 0 || math.IsNaN(diameterMM) || math.IsInf(diameterMM, 0) {
|
||||||
return math.NaN()
|
return math.NaN()
|
||||||
@@ -42,6 +46,8 @@ func DawesLimitArcsec(diameterMM float64) float64 {
|
|||||||
// 返回:
|
// 返回:
|
||||||
//
|
//
|
||||||
// Rayleigh 极限,单位角秒
|
// Rayleigh 极限,单位角秒
|
||||||
|
//
|
||||||
|
// Returns the Rayleigh limit in arcseconds for the supplied aperture.
|
||||||
func RayleighLimitArcsec(diameterMM float64) float64 {
|
func RayleighLimitArcsec(diameterMM float64) float64 {
|
||||||
if diameterMM <= 0 || math.IsNaN(diameterMM) || math.IsInf(diameterMM, 0) {
|
if diameterMM <= 0 || math.IsNaN(diameterMM) || math.IsInf(diameterMM, 0) {
|
||||||
return math.NaN()
|
return math.NaN()
|
||||||
@@ -58,6 +64,8 @@ func RayleighLimitArcsec(diameterMM float64) float64 {
|
|||||||
//
|
//
|
||||||
// 经验极限星等;这是经验值,不包含天空背景、倍率、透过率和观测经验修正
|
// 经验极限星等;这是经验值,不包含天空背景、倍率、透过率和观测经验修正
|
||||||
//
|
//
|
||||||
|
// Returns an empirical limiting magnitude estimate. It does not account for sky background, magnification, transmission, or observer skill.
|
||||||
|
//
|
||||||
// 例:
|
// 例:
|
||||||
//
|
//
|
||||||
// // 70mm 小型折射镜,裸眼极限 6 等
|
// // 70mm 小型折射镜,裸眼极限 6 等
|
||||||
|
|||||||
+24
-24
@@ -207,8 +207,8 @@ 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.
|
// Returns the nearest conjunction with the Sun at or before 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 +216,8 @@ 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.
|
// Returns the nearest conjunction with the Sun at or after 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 +225,8 @@ 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.
|
// Returns the nearest opposition at or before 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 +234,8 @@ 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.
|
// Returns the nearest opposition at or after 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 +243,8 @@ 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.
|
// Returns the nearest station at or before date where motion changes from prograde to retrograde, 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 +252,8 @@ 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.
|
// Returns the nearest station at or after date where motion changes from prograde to retrograde, 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 +261,8 @@ 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.
|
// Returns the nearest station at or before date where motion changes from retrograde to prograde, 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 +270,8 @@ 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.
|
// Returns the nearest station at or after date where motion changes from retrograde to prograde, 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 +279,8 @@ 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.
|
// Returns the nearest eastern quadrature at or before 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 +288,8 @@ 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.
|
// Returns the nearest eastern quadrature at or after 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 +297,8 @@ 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.
|
// Returns the nearest western quadrature at or before 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 +306,8 @@ 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.
|
// Returns the nearest western quadrature at or after 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)
|
||||||
|
|||||||
+24
-24
@@ -207,8 +207,8 @@ 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.
|
// Returns the nearest conjunction with the Sun at or before 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 +216,8 @@ 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.
|
// Returns the nearest conjunction with the Sun at or after 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 +225,8 @@ 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.
|
// Returns the nearest opposition at or before 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 +234,8 @@ 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.
|
// Returns the nearest opposition at or after 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 +243,8 @@ 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.
|
// Returns the nearest station at or before date where motion changes from prograde to retrograde, 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 +252,8 @@ 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.
|
// Returns the nearest station at or after date where motion changes from prograde to retrograde, 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 +261,8 @@ 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.
|
// Returns the nearest station at or before date where motion changes from retrograde to prograde, 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 +270,8 @@ 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.
|
// Returns the nearest station at or after date where motion changes from retrograde to prograde, 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 +279,8 @@ 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.
|
// Returns the nearest eastern quadrature at or before 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 +288,8 @@ 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.
|
// Returns the nearest eastern quadrature at or after 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 +297,8 @@ 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.
|
// Returns the nearest western quadrature at or before 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 +306,8 @@ 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.
|
// Returns the nearest western quadrature at or after 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)
|
||||||
|
|||||||
+52
-52
@@ -207,8 +207,8 @@ 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.
|
// Returns the nearest conjunction with the Sun at or before 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 +216,8 @@ 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.
|
// Returns the nearest conjunction with the Sun at or after 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 +225,144 @@ 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.
|
// Returns the nearest inferior conjunction at or before 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.
|
// Returns the nearest inferior conjunction at or after 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.
|
// Returns the nearest superior conjunction at or before 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.
|
// Returns the nearest superior conjunction at or after 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.
|
// Returns the nearest stationary point at or before date, regardless of the direction change, 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.
|
// Returns the nearest stationary point at or after date, regardless of the direction change, 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.
|
// Returns the nearest station at or before date where motion changes from prograde to retrograde, 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.
|
// Returns the nearest station at or after date where motion changes from prograde to retrograde, 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.
|
// Returns the nearest station at or before date where motion changes from retrograde to prograde, 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.
|
// Returns the nearest station at or after date where motion changes from retrograde to prograde, 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.
|
// Returns the nearest greatest elongation at or before date, regardless of 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.
|
// Returns the nearest greatest elongation at or after date, regardless of 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.
|
// Returns the nearest eastern greatest elongation at or before 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.
|
// Returns the nearest eastern greatest elongation at or after 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.
|
// Returns the nearest western greatest elongation at or before 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.
|
// Returns the nearest western greatest elongation at or after 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,72 @@
|
|||||||
|
package moon
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"b612.me/astro/basic"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ConjunctionPlanet 月球合月目标行星 / target planet for Moon-planet conjunction.
|
||||||
|
type ConjunctionPlanet string
|
||||||
|
|
||||||
|
const (
|
||||||
|
ConjunctionMercury ConjunctionPlanet = "mercury"
|
||||||
|
ConjunctionVenus ConjunctionPlanet = "venus"
|
||||||
|
ConjunctionMars ConjunctionPlanet = "mars"
|
||||||
|
ConjunctionJupiter ConjunctionPlanet = "jupiter"
|
||||||
|
ConjunctionSaturn ConjunctionPlanet = "saturn"
|
||||||
|
ConjunctionUranus ConjunctionPlanet = "uranus"
|
||||||
|
ConjunctionNeptune ConjunctionPlanet = "neptune"
|
||||||
|
)
|
||||||
|
|
||||||
|
func conjunctionPlanetToBasic(planet ConjunctionPlanet) basic.MoonPlanetConjunctionPlanet {
|
||||||
|
switch planet {
|
||||||
|
case ConjunctionMercury:
|
||||||
|
return basic.MoonPlanetConjunctionMercury
|
||||||
|
case ConjunctionVenus:
|
||||||
|
return basic.MoonPlanetConjunctionVenus
|
||||||
|
case ConjunctionMars:
|
||||||
|
return basic.MoonPlanetConjunctionMars
|
||||||
|
case ConjunctionJupiter:
|
||||||
|
return basic.MoonPlanetConjunctionJupiter
|
||||||
|
case ConjunctionSaturn:
|
||||||
|
return basic.MoonPlanetConjunctionSaturn
|
||||||
|
case ConjunctionUranus:
|
||||||
|
return basic.MoonPlanetConjunctionUranus
|
||||||
|
case ConjunctionNeptune:
|
||||||
|
return basic.MoonPlanetConjunctionNeptune
|
||||||
|
default:
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func validConjunctionPlanet(planet ConjunctionPlanet) bool {
|
||||||
|
return conjunctionPlanetToBasic(planet) != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// LastConjunctionWithPlanet 上一次行星合月(赤经合) / previous Moon-planet conjunction.
|
||||||
|
func LastConjunctionWithPlanet(date time.Time, planet ConjunctionPlanet) time.Time {
|
||||||
|
if !validConjunctionPlanet(planet) {
|
||||||
|
return time.Time{}
|
||||||
|
}
|
||||||
|
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
|
||||||
|
return basic.JDE2DateByZone(basic.LastMoonPlanetConjunction(jde, conjunctionPlanetToBasic(planet)), date.Location(), false)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NextConjunctionWithPlanet 下一次行星合月(赤经合) / next Moon-planet conjunction.
|
||||||
|
func NextConjunctionWithPlanet(date time.Time, planet ConjunctionPlanet) time.Time {
|
||||||
|
if !validConjunctionPlanet(planet) {
|
||||||
|
return time.Time{}
|
||||||
|
}
|
||||||
|
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
|
||||||
|
return basic.JDE2DateByZone(basic.NextMoonPlanetConjunction(jde, conjunctionPlanetToBasic(planet)), date.Location(), false)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClosestConjunctionWithPlanet 最近一次行星合月(赤经合) / closest Moon-planet conjunction.
|
||||||
|
func ClosestConjunctionWithPlanet(date time.Time, planet ConjunctionPlanet) time.Time {
|
||||||
|
if !validConjunctionPlanet(planet) {
|
||||||
|
return time.Time{}
|
||||||
|
}
|
||||||
|
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
|
||||||
|
return basic.JDE2DateByZone(basic.ClosestMoonPlanetConjunction(jde, conjunctionPlanetToBasic(planet)), date.Location(), false)
|
||||||
|
}
|
||||||
@@ -0,0 +1,103 @@
|
|||||||
|
package moon
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"b612.me/astro/basic"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestConjunctionPlanetWrappersMatchBasic(t *testing.T) {
|
||||||
|
loc := time.FixedZone("CST", 8*3600)
|
||||||
|
query := time.Date(2026, 1, 15, 20, 0, 0, 0, loc)
|
||||||
|
queryTT := basic.TD2UT(basic.Date2JDE(query.UTC()), true)
|
||||||
|
|
||||||
|
cases := []struct {
|
||||||
|
name string
|
||||||
|
planet ConjunctionPlanet
|
||||||
|
basic basic.MoonPlanetConjunctionPlanet
|
||||||
|
}{
|
||||||
|
{name: "Mercury", planet: ConjunctionMercury, basic: basic.MoonPlanetConjunctionMercury},
|
||||||
|
{name: "Venus", planet: ConjunctionVenus, basic: basic.MoonPlanetConjunctionVenus},
|
||||||
|
{name: "Mars", planet: ConjunctionMars, basic: basic.MoonPlanetConjunctionMars},
|
||||||
|
{name: "Jupiter", planet: ConjunctionJupiter, basic: basic.MoonPlanetConjunctionJupiter},
|
||||||
|
{name: "Saturn", planet: ConjunctionSaturn, basic: basic.MoonPlanetConjunctionSaturn},
|
||||||
|
{name: "Uranus", planet: ConjunctionUranus, basic: basic.MoonPlanetConjunctionUranus},
|
||||||
|
{name: "Neptune", planet: ConjunctionNeptune, basic: basic.MoonPlanetConjunctionNeptune},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range cases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
assertSameConjunctionTime(t, "last", LastConjunctionWithPlanet(query, tc.planet), basic.LastMoonPlanetConjunction(queryTT, tc.basic), loc)
|
||||||
|
assertSameConjunctionTime(t, "next", NextConjunctionWithPlanet(query, tc.planet), basic.NextMoonPlanetConjunction(queryTT, tc.basic), loc)
|
||||||
|
assertSameConjunctionTime(t, "closest", ClosestConjunctionWithPlanet(query, tc.planet), basic.ClosestMoonPlanetConjunction(queryTT, tc.basic), loc)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func assertSameConjunctionTime(t *testing.T, name string, got time.Time, wantJDE float64, loc *time.Location) {
|
||||||
|
t.Helper()
|
||||||
|
want := basic.JDE2DateByZone(wantJDE, loc, false)
|
||||||
|
if got.Location() != loc {
|
||||||
|
t.Fatalf("%s location mismatch: got %q want %q", name, got.Location().String(), loc.String())
|
||||||
|
}
|
||||||
|
if !got.Equal(want) {
|
||||||
|
t.Fatalf("%s time mismatch: got %s want %s", name, got.Format(time.RFC3339Nano), want.Format(time.RFC3339Nano))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestClosestConjunctionReturnsNearestCandidate(t *testing.T) {
|
||||||
|
query := time.Date(2026, 1, 15, 12, 0, 0, 0, time.UTC)
|
||||||
|
last := LastConjunctionWithPlanet(query, ConjunctionMercury)
|
||||||
|
next := NextConjunctionWithPlanet(query, ConjunctionMercury)
|
||||||
|
got := ClosestConjunctionWithPlanet(query, ConjunctionMercury)
|
||||||
|
|
||||||
|
lastDiff := math.Abs(query.Sub(last).Seconds())
|
||||||
|
nextDiff := math.Abs(next.Sub(query).Seconds())
|
||||||
|
if lastDiff <= nextDiff {
|
||||||
|
if !got.Equal(last) {
|
||||||
|
t.Fatalf("closest should match last: got %s want %s", got.Format(time.RFC3339Nano), last.Format(time.RFC3339Nano))
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !got.Equal(next) {
|
||||||
|
t.Fatalf("closest should match next: got %s want %s", got.Format(time.RFC3339Nano), next.Format(time.RFC3339Nano))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestInvalidConjunctionPlanetReturnsZeroTime(t *testing.T) {
|
||||||
|
query := time.Date(2026, 1, 15, 12, 0, 0, 0, time.FixedZone("CST", 8*3600))
|
||||||
|
invalid := ConjunctionPlanet("pluto")
|
||||||
|
|
||||||
|
for name, fn := range map[string]func(time.Time, ConjunctionPlanet) time.Time{
|
||||||
|
"last": LastConjunctionWithPlanet,
|
||||||
|
"next": NextConjunctionWithPlanet,
|
||||||
|
"closest": ClosestConjunctionWithPlanet,
|
||||||
|
} {
|
||||||
|
if got := fn(query, invalid); !got.IsZero() {
|
||||||
|
t.Fatalf("%s should return zero time for invalid planet, got %s", name, got.Format(time.RFC3339Nano))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNextConjunctionAdvancesPastReturnedEvent(t *testing.T) {
|
||||||
|
cursor := time.Date(2026, 5, 1, 0, 0, 0, 0, time.UTC)
|
||||||
|
first := NextConjunctionWithPlanet(cursor, ConjunctionMercury)
|
||||||
|
query := first.Add(time.Second)
|
||||||
|
next := NextConjunctionWithPlanet(query, ConjunctionMercury)
|
||||||
|
|
||||||
|
if !next.After(query) {
|
||||||
|
t.Fatalf("expected next conjunction after query: query=%s next=%s delta=%v",
|
||||||
|
query.Format(time.RFC3339Nano),
|
||||||
|
next.Format(time.RFC3339Nano),
|
||||||
|
next.Sub(query),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if next.Equal(first) {
|
||||||
|
t.Fatalf("expected next conjunction to advance: first=%s next=%s",
|
||||||
|
first.Format(time.RFC3339Nano),
|
||||||
|
next.Format(time.RFC3339Nano),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
package moon
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"b612.me/astro/basic"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestGeocentricApparentRaDecComponentsMatch(t *testing.T) {
|
||||||
|
date := time.Date(2026, 1, 1, 6, 0, 0, 0, time.UTC)
|
||||||
|
|
||||||
|
ra, dec := GeocentricApparentRaDec(date)
|
||||||
|
if diff := math.Abs(ra - GeocentricApparentRa(date)); diff > 1e-12 {
|
||||||
|
t.Fatalf("RA pair mismatch: got %.15f want %.15f", ra, GeocentricApparentRa(date))
|
||||||
|
}
|
||||||
|
if diff := math.Abs(dec - GeocentricApparentDec(date)); diff > 1e-12 {
|
||||||
|
t.Fatalf("Dec pair mismatch: got %.15f want %.15f", dec, GeocentricApparentDec(date))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGeocentricApparentRaDecDiffersFromTopocentricAtSite(t *testing.T) {
|
||||||
|
date := time.Date(2026, 1, 1, 6, 0, 0, 0, time.FixedZone("CST", 8*3600))
|
||||||
|
|
||||||
|
geoRA, geoDec := GeocentricApparentRaDec(date)
|
||||||
|
topoRA, topoDec := ApparentRaDec(date, 121.4737, 31.2304)
|
||||||
|
|
||||||
|
if math.Abs(geoRA-topoRA) < 1e-6 && math.Abs(geoDec-topoDec) < 1e-6 {
|
||||||
|
t.Fatalf("geocentric apparent RA/Dec unexpectedly matches topocentric values: geo=(%.12f, %.12f) topo=(%.12f, %.12f)",
|
||||||
|
geoRA, geoDec, topoRA, topoDec)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTrueRaDecUsesBasicGeocentricTrue(t *testing.T) {
|
||||||
|
date := time.Date(2026, 1, 1, 6, 0, 0, 0, time.UTC)
|
||||||
|
|
||||||
|
wantRA, wantDec := basic.HMoonGeocentricTrueRaDec(basic.TD2UT(basic.Date2JDE(date.UTC()), true))
|
||||||
|
gotRA, gotDec := TrueRaDec(date)
|
||||||
|
if math.Abs(gotRA-wantRA) > 1e-12 || math.Abs(gotDec-wantDec) > 1e-12 {
|
||||||
|
t.Fatalf("TrueRaDec mismatch: got (%.15f, %.15f) want (%.15f, %.15f)", gotRA, gotDec, wantRA, wantDec)
|
||||||
|
}
|
||||||
|
}
|
||||||
+30
-3
@@ -84,7 +84,7 @@ func ApparentLo(date time.Time) float64 {
|
|||||||
// Returns the Moon's geocentric true right ascension at the instant represented by date, in degrees.
|
// Returns the Moon's geocentric true right ascension at the instant represented by date, in degrees.
|
||||||
func TrueRa(date time.Time) float64 {
|
func TrueRa(date time.Time) float64 {
|
||||||
jde := basic.Date2JDE(date.UTC())
|
jde := basic.Date2JDE(date.UTC())
|
||||||
return basic.HMoonTrueRa(basic.TD2UT(jde, true))
|
return basic.HMoonGeocentricTrueRa(basic.TD2UT(jde, true))
|
||||||
}
|
}
|
||||||
|
|
||||||
// TrueDec 月亮地心真赤纬 / true geocentric declination.
|
// TrueDec 月亮地心真赤纬 / true geocentric declination.
|
||||||
@@ -93,7 +93,7 @@ func TrueRa(date time.Time) float64 {
|
|||||||
// Returns the Moon's geocentric true declination at the instant represented by date, in degrees.
|
// Returns the Moon's geocentric true declination at the instant represented by date, in degrees.
|
||||||
func TrueDec(date time.Time) float64 {
|
func TrueDec(date time.Time) float64 {
|
||||||
jde := basic.Date2JDE(date.UTC())
|
jde := basic.Date2JDE(date.UTC())
|
||||||
return basic.HMoonTrueDec(basic.TD2UT(jde, true))
|
return basic.HMoonGeocentricTrueDec(basic.TD2UT(jde, true))
|
||||||
}
|
}
|
||||||
|
|
||||||
// TrueRaDec 月亮地心真赤经、真赤纬 / true geocentric right ascension and declination.
|
// TrueRaDec 月亮地心真赤经、真赤纬 / true geocentric right ascension and declination.
|
||||||
@@ -102,7 +102,34 @@ func TrueDec(date time.Time) float64 {
|
|||||||
// Returns the Moon's geocentric true right ascension and declination at the instant represented by date, in degrees.
|
// Returns the Moon's geocentric true right ascension and declination at the instant represented by date, in degrees.
|
||||||
func TrueRaDec(date time.Time) (float64, float64) {
|
func TrueRaDec(date time.Time) (float64, float64) {
|
||||||
jde := basic.Date2JDE(date.UTC())
|
jde := basic.Date2JDE(date.UTC())
|
||||||
return basic.HMoonTrueRaDec(basic.TD2UT(jde, true))
|
return basic.HMoonGeocentricTrueRaDec(basic.TD2UT(jde, true))
|
||||||
|
}
|
||||||
|
|
||||||
|
// GeocentricApparentRa 月亮地心视赤经 / apparent geocentric right ascension.
|
||||||
|
//
|
||||||
|
// 返回月亮在 date 对应绝对时刻的地心视赤经,单位度。
|
||||||
|
// Returns the Moon's apparent geocentric right ascension at the instant represented by date, in degrees.
|
||||||
|
func GeocentricApparentRa(date time.Time) float64 {
|
||||||
|
jde := basic.Date2JDE(date.UTC())
|
||||||
|
return basic.HMoonGeocentricApparentRa(basic.TD2UT(jde, true))
|
||||||
|
}
|
||||||
|
|
||||||
|
// GeocentricApparentDec 月亮地心视赤纬 / apparent geocentric declination.
|
||||||
|
//
|
||||||
|
// 返回月亮在 date 对应绝对时刻的地心视赤纬,单位度。
|
||||||
|
// Returns the Moon's apparent geocentric declination at the instant represented by date, in degrees.
|
||||||
|
func GeocentricApparentDec(date time.Time) float64 {
|
||||||
|
jde := basic.Date2JDE(date.UTC())
|
||||||
|
return basic.HMoonGeocentricApparentDec(basic.TD2UT(jde, true))
|
||||||
|
}
|
||||||
|
|
||||||
|
// GeocentricApparentRaDec 月亮地心视赤经、视赤纬 / apparent geocentric right ascension and declination.
|
||||||
|
//
|
||||||
|
// 返回月亮在 date 对应绝对时刻的地心视赤经与视赤纬,单位度。
|
||||||
|
// Returns the Moon's apparent geocentric right ascension and declination at the instant represented by date, in degrees.
|
||||||
|
func GeocentricApparentRaDec(date time.Time) (float64, float64) {
|
||||||
|
jde := basic.Date2JDE(date.UTC())
|
||||||
|
return basic.HMoonGeocentricApparentRaDec(basic.TD2UT(jde, true))
|
||||||
}
|
}
|
||||||
|
|
||||||
// ApparentRa 月亮站心视赤经 / apparent topocentric right ascension.
|
// ApparentRa 月亮站心视赤经 / apparent topocentric right ascension.
|
||||||
|
|||||||
+24
-24
@@ -207,8 +207,8 @@ 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.
|
// Returns the nearest conjunction with the Sun at or before 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 +216,8 @@ 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.
|
// Returns the nearest conjunction with the Sun at or after 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 +225,8 @@ 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.
|
// Returns the nearest opposition at or before 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 +234,8 @@ 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.
|
// Returns the nearest opposition at or after 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 +243,8 @@ 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.
|
// Returns the nearest station at or before date where motion changes from prograde to retrograde, 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 +252,8 @@ 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.
|
// Returns the nearest station at or after date where motion changes from prograde to retrograde, 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 +261,8 @@ 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.
|
// Returns the nearest station at or before date where motion changes from retrograde to prograde, 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 +270,8 @@ 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.
|
// Returns the nearest station at or after date where motion changes from retrograde to prograde, 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 +279,8 @@ 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.
|
// Returns the nearest eastern quadrature at or before 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 +288,8 @@ 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.
|
// Returns the nearest eastern quadrature at or after 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 +297,8 @@ 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.
|
// Returns the nearest western quadrature at or before 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 +306,8 @@ 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.
|
// Returns the nearest western quadrature at or after 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 {
|
||||||
|
|||||||
+33
-3
@@ -12,13 +12,17 @@ var (
|
|||||||
ERR_ORBIT_NEVER_SET = errors.New("ERROR:轨道目标今日永远在地平线上!")
|
ERR_ORBIT_NEVER_SET = errors.New("ERROR:轨道目标今日永远在地平线上!")
|
||||||
)
|
)
|
||||||
|
|
||||||
// Elements 日心二体圆锥曲线根数,参考系为 J2000 平黄道/平春分点。
|
// Elements 日心二体圆锥曲线根数 / heliocentric two-body conic elements.
|
||||||
|
// 参考系为 J2000 平黄道/平春分点。
|
||||||
|
// The reference frame is the J2000 mean ecliptic and mean equinox.
|
||||||
// EpochJD 与 TpJD 使用 TT/TDB 对应的儒略日。
|
// EpochJD 与 TpJD 使用 TT/TDB 对应的儒略日。
|
||||||
|
// EpochJD and TpJD are Julian days on the TT/TDB scale.
|
||||||
//
|
//
|
||||||
// 经典椭圆根数:A/E/I/Omega/W/M0
|
// 经典椭圆根数:A/E/I/Omega/W/M0
|
||||||
// 近日点形式:Q/E/I/Omega/W/TpJD
|
// 近日点形式:Q/E/I/Omega/W/TpJD
|
||||||
//
|
//
|
||||||
// 线性 rates 仅作用于经典椭圆根数,单位均为每天变化量。
|
// 线性 rates 仅作用于经典椭圆根数,单位均为每天变化量。
|
||||||
|
// The linear rates apply only to the classical elliptical element form and are expressed per day.
|
||||||
type Elements struct {
|
type Elements struct {
|
||||||
EpochJD float64 // 历元儒略日(TT/TDB) / epoch Julian day in TT/TDB.
|
EpochJD float64 // 历元儒略日(TT/TDB) / epoch Julian day in TT/TDB.
|
||||||
A float64 // 半长径,单位 AU / semi-major axis in AU.
|
A float64 // 半长径,单位 AU / semi-major axis in AU.
|
||||||
@@ -38,14 +42,20 @@ type Elements struct {
|
|||||||
MDot float64 // 平近点角日变化,单位 deg/day / daily rate of M.
|
MDot float64 // 平近点角日变化,单位 deg/day / daily rate of M.
|
||||||
}
|
}
|
||||||
|
|
||||||
// EclipticPosition 黄道球坐标结果,Lon/Lat 单位度,Distance 单位 AU。
|
// EclipticPosition 黄道球坐标结果 / ecliptic spherical coordinates.
|
||||||
|
//
|
||||||
|
// Lon/Lat 单位度,Distance 单位 AU。
|
||||||
|
// Lon/Lat are in degrees and Distance is in AU.
|
||||||
type EclipticPosition struct {
|
type EclipticPosition struct {
|
||||||
Lon float64
|
Lon float64
|
||||||
Lat float64
|
Lat float64
|
||||||
Distance float64
|
Distance float64
|
||||||
}
|
}
|
||||||
|
|
||||||
// EquatorialPosition 赤道球坐标结果,RA/Dec 单位度,Distance 单位 AU。
|
// EquatorialPosition 赤道球坐标结果 / equatorial spherical coordinates.
|
||||||
|
//
|
||||||
|
// RA/Dec 单位度,Distance 单位 AU。
|
||||||
|
// RA/Dec are in degrees and Distance is in AU.
|
||||||
type EquatorialPosition struct {
|
type EquatorialPosition struct {
|
||||||
RA float64
|
RA float64
|
||||||
Dec float64
|
Dec float64
|
||||||
@@ -55,6 +65,7 @@ type EquatorialPosition struct {
|
|||||||
// MeanMotion 平均角速度 / mean motion.
|
// MeanMotion 平均角速度 / mean motion.
|
||||||
//
|
//
|
||||||
// 返回平均角速度,单位度/日;对抛物线和双曲线轨道返回 `NaN`。
|
// 返回平均角速度,单位度/日;对抛物线和双曲线轨道返回 `NaN`。
|
||||||
|
// Returns mean motion in degrees per day. Parabolic and hyperbolic cases return `NaN`.
|
||||||
func MeanMotion(elements Elements) float64 {
|
func MeanMotion(elements Elements) float64 {
|
||||||
return basic.OrbitMeanMotion(toBasicElements(elements))
|
return basic.OrbitMeanMotion(toBasicElements(elements))
|
||||||
}
|
}
|
||||||
@@ -62,6 +73,7 @@ func MeanMotion(elements Elements) float64 {
|
|||||||
// MeanAnomaly 平近点角 / mean anomaly.
|
// MeanAnomaly 平近点角 / mean anomaly.
|
||||||
//
|
//
|
||||||
// 返回给定时刻的平近点角,单位度;对抛物线和双曲线轨道返回 `NaN`。
|
// 返回给定时刻的平近点角,单位度;对抛物线和双曲线轨道返回 `NaN`。
|
||||||
|
// Returns mean anomaly in degrees for the supplied instant. Parabolic and hyperbolic cases return `NaN`.
|
||||||
func MeanAnomaly(date time.Time, elements Elements) float64 {
|
func MeanAnomaly(date time.Time, elements Elements) float64 {
|
||||||
return basic.OrbitMeanAnomaly(ttJulianDay(date), toBasicElements(elements))
|
return basic.OrbitMeanAnomaly(ttJulianDay(date), toBasicElements(elements))
|
||||||
}
|
}
|
||||||
@@ -69,6 +81,7 @@ func MeanAnomaly(date time.Time, elements Elements) float64 {
|
|||||||
// TrueAnomaly 真近点角 / true anomaly.
|
// TrueAnomaly 真近点角 / true anomaly.
|
||||||
//
|
//
|
||||||
// 返回给定时刻的真近点角,单位度。
|
// 返回给定时刻的真近点角,单位度。
|
||||||
|
// Returns true anomaly in degrees for the supplied instant.
|
||||||
func TrueAnomaly(date time.Time, elements Elements) float64 {
|
func TrueAnomaly(date time.Time, elements Elements) float64 {
|
||||||
return basic.OrbitTrueAnomaly(ttJulianDay(date), toBasicElements(elements))
|
return basic.OrbitTrueAnomaly(ttJulianDay(date), toBasicElements(elements))
|
||||||
}
|
}
|
||||||
@@ -76,6 +89,7 @@ func TrueAnomaly(date time.Time, elements Elements) float64 {
|
|||||||
// HeliocentricEclipticJ2000 日心 J2000 平黄道坐标 / heliocentric J2000 ecliptic coordinates.
|
// HeliocentricEclipticJ2000 日心 J2000 平黄道坐标 / heliocentric J2000 ecliptic coordinates.
|
||||||
//
|
//
|
||||||
// 返回黄经、黄纬和距离;角度单位度,距离单位 AU。
|
// 返回黄经、黄纬和距离;角度单位度,距离单位 AU。
|
||||||
|
// Returns heliocentric J2000 ecliptic longitude, latitude, and distance. Angles are in degrees and distance is in AU.
|
||||||
func HeliocentricEclipticJ2000(date time.Time, elements Elements) EclipticPosition {
|
func HeliocentricEclipticJ2000(date time.Time, elements Elements) EclipticPosition {
|
||||||
lon, lat, distance := basic.OrbitHeliocentricEclipticJ2000(ttJulianDay(date), toBasicElements(elements))
|
lon, lat, distance := basic.OrbitHeliocentricEclipticJ2000(ttJulianDay(date), toBasicElements(elements))
|
||||||
return EclipticPosition{Lon: lon, Lat: lat, Distance: distance}
|
return EclipticPosition{Lon: lon, Lat: lat, Distance: distance}
|
||||||
@@ -84,6 +98,7 @@ func HeliocentricEclipticJ2000(date time.Time, elements Elements) EclipticPositi
|
|||||||
// HeliocentricEcliptic 日心历元黄道坐标 / heliocentric ecliptic coordinates of date.
|
// HeliocentricEcliptic 日心历元黄道坐标 / heliocentric ecliptic coordinates of date.
|
||||||
//
|
//
|
||||||
// 返回历元黄经、黄纬和距离;角度单位度,距离单位 AU。
|
// 返回历元黄经、黄纬和距离;角度单位度,距离单位 AU。
|
||||||
|
// Returns heliocentric ecliptic longitude, latitude, and distance of date. Angles are in degrees and distance is in AU.
|
||||||
func HeliocentricEcliptic(date time.Time, elements Elements) EclipticPosition {
|
func HeliocentricEcliptic(date time.Time, elements Elements) EclipticPosition {
|
||||||
lon, lat, distance := basic.OrbitHeliocentricEcliptic(ttJulianDay(date), toBasicElements(elements))
|
lon, lat, distance := basic.OrbitHeliocentricEcliptic(ttJulianDay(date), toBasicElements(elements))
|
||||||
return EclipticPosition{Lon: lon, Lat: lat, Distance: distance}
|
return EclipticPosition{Lon: lon, Lat: lat, Distance: distance}
|
||||||
@@ -92,6 +107,7 @@ func HeliocentricEcliptic(date time.Time, elements Elements) EclipticPosition {
|
|||||||
// GeocentricEclipticJ2000 地心 J2000 平黄道坐标 / geocentric J2000 ecliptic coordinates.
|
// GeocentricEclipticJ2000 地心 J2000 平黄道坐标 / geocentric J2000 ecliptic coordinates.
|
||||||
//
|
//
|
||||||
// 返回黄经、黄纬和距离;角度单位度,距离单位 AU。
|
// 返回黄经、黄纬和距离;角度单位度,距离单位 AU。
|
||||||
|
// Returns geocentric J2000 ecliptic longitude, latitude, and distance. Angles are in degrees and distance is in AU.
|
||||||
func GeocentricEclipticJ2000(date time.Time, elements Elements) EclipticPosition {
|
func GeocentricEclipticJ2000(date time.Time, elements Elements) EclipticPosition {
|
||||||
lon, lat, distance := basic.OrbitGeocentricEclipticJ2000(ttJulianDay(date), toBasicElements(elements))
|
lon, lat, distance := basic.OrbitGeocentricEclipticJ2000(ttJulianDay(date), toBasicElements(elements))
|
||||||
return EclipticPosition{Lon: lon, Lat: lat, Distance: distance}
|
return EclipticPosition{Lon: lon, Lat: lat, Distance: distance}
|
||||||
@@ -100,6 +116,7 @@ func GeocentricEclipticJ2000(date time.Time, elements Elements) EclipticPosition
|
|||||||
// GeocentricEcliptic 地心历元黄道坐标 / geocentric ecliptic coordinates of date.
|
// GeocentricEcliptic 地心历元黄道坐标 / geocentric ecliptic coordinates of date.
|
||||||
//
|
//
|
||||||
// 返回历元黄经、黄纬和距离;角度单位度,距离单位 AU。
|
// 返回历元黄经、黄纬和距离;角度单位度,距离单位 AU。
|
||||||
|
// Returns geocentric ecliptic longitude, latitude, and distance of date. Angles are in degrees and distance is in AU.
|
||||||
func GeocentricEcliptic(date time.Time, elements Elements) EclipticPosition {
|
func GeocentricEcliptic(date time.Time, elements Elements) EclipticPosition {
|
||||||
lon, lat, distance := basic.OrbitGeocentricEcliptic(ttJulianDay(date), toBasicElements(elements))
|
lon, lat, distance := basic.OrbitGeocentricEcliptic(ttJulianDay(date), toBasicElements(elements))
|
||||||
return EclipticPosition{Lon: lon, Lat: lat, Distance: distance}
|
return EclipticPosition{Lon: lon, Lat: lat, Distance: distance}
|
||||||
@@ -108,6 +125,7 @@ func GeocentricEcliptic(date time.Time, elements Elements) EclipticPosition {
|
|||||||
// GeocentricEquatorialJ2000 地心 J2000 平赤道坐标 / geocentric J2000 equatorial coordinates.
|
// GeocentricEquatorialJ2000 地心 J2000 平赤道坐标 / geocentric J2000 equatorial coordinates.
|
||||||
//
|
//
|
||||||
// 返回赤经、赤纬和距离;角度单位度,距离单位 AU。
|
// 返回赤经、赤纬和距离;角度单位度,距离单位 AU。
|
||||||
|
// Returns geocentric J2000 right ascension, declination, and distance. Angles are in degrees and distance is in AU.
|
||||||
func GeocentricEquatorialJ2000(date time.Time, elements Elements) EquatorialPosition {
|
func GeocentricEquatorialJ2000(date time.Time, elements Elements) EquatorialPosition {
|
||||||
ra, dec, distance := basic.OrbitGeocentricEquatorialJ2000(ttJulianDay(date), toBasicElements(elements))
|
ra, dec, distance := basic.OrbitGeocentricEquatorialJ2000(ttJulianDay(date), toBasicElements(elements))
|
||||||
return EquatorialPosition{RA: ra, Dec: dec, Distance: distance}
|
return EquatorialPosition{RA: ra, Dec: dec, Distance: distance}
|
||||||
@@ -116,6 +134,7 @@ func GeocentricEquatorialJ2000(date time.Time, elements Elements) EquatorialPosi
|
|||||||
// GeocentricEquatorial 地心历元平赤道坐标 / geocentric equatorial coordinates of date.
|
// GeocentricEquatorial 地心历元平赤道坐标 / geocentric equatorial coordinates of date.
|
||||||
//
|
//
|
||||||
// 返回历元赤经、赤纬和距离;角度单位度,距离单位 AU。
|
// 返回历元赤经、赤纬和距离;角度单位度,距离单位 AU。
|
||||||
|
// Returns geocentric right ascension, declination, and distance of date. Angles are in degrees and distance is in AU.
|
||||||
func GeocentricEquatorial(date time.Time, elements Elements) EquatorialPosition {
|
func GeocentricEquatorial(date time.Time, elements Elements) EquatorialPosition {
|
||||||
ra, dec, distance := basic.OrbitGeocentricEquatorial(ttJulianDay(date), toBasicElements(elements))
|
ra, dec, distance := basic.OrbitGeocentricEquatorial(ttJulianDay(date), toBasicElements(elements))
|
||||||
return EquatorialPosition{RA: ra, Dec: dec, Distance: distance}
|
return EquatorialPosition{RA: ra, Dec: dec, Distance: distance}
|
||||||
@@ -124,6 +143,7 @@ func GeocentricEquatorial(date time.Time, elements Elements) EquatorialPosition
|
|||||||
// AstrometricGeocentricEquatorialJ2000 地心测算 J2000 赤道坐标 / astrometric geocentric J2000 equatorial coordinates.
|
// AstrometricGeocentricEquatorialJ2000 地心测算 J2000 赤道坐标 / astrometric geocentric J2000 equatorial coordinates.
|
||||||
//
|
//
|
||||||
// 返回加入光行时修正后的地心 J2000 赤经、赤纬和距离;角度单位度,距离单位 AU。
|
// 返回加入光行时修正后的地心 J2000 赤经、赤纬和距离;角度单位度,距离单位 AU。
|
||||||
|
// Returns astrometric geocentric J2000 right ascension, declination, and distance after light-time correction. Angles are in degrees and distance is in AU.
|
||||||
func AstrometricGeocentricEquatorialJ2000(date time.Time, elements Elements) EquatorialPosition {
|
func AstrometricGeocentricEquatorialJ2000(date time.Time, elements Elements) EquatorialPosition {
|
||||||
ra, dec, distance := basic.OrbitAstrometricGeocentricEquatorialJ2000(ttJulianDay(date), toBasicElements(elements))
|
ra, dec, distance := basic.OrbitAstrometricGeocentricEquatorialJ2000(ttJulianDay(date), toBasicElements(elements))
|
||||||
return EquatorialPosition{RA: ra, Dec: dec, Distance: distance}
|
return EquatorialPosition{RA: ra, Dec: dec, Distance: distance}
|
||||||
@@ -132,6 +152,7 @@ func AstrometricGeocentricEquatorialJ2000(date time.Time, elements Elements) Equ
|
|||||||
// ApparentGeocentricEcliptic 地心视黄道坐标 / apparent geocentric ecliptic coordinates.
|
// ApparentGeocentricEcliptic 地心视黄道坐标 / apparent geocentric ecliptic coordinates.
|
||||||
//
|
//
|
||||||
// 返回加入光行时与章动修正后的地心视黄经、黄纬和距离;角度单位度,距离单位 AU。
|
// 返回加入光行时与章动修正后的地心视黄经、黄纬和距离;角度单位度,距离单位 AU。
|
||||||
|
// Returns apparent geocentric ecliptic longitude, latitude, and distance after light-time and nutation corrections. Angles are in degrees and distance is in AU.
|
||||||
func ApparentGeocentricEcliptic(date time.Time, elements Elements) EclipticPosition {
|
func ApparentGeocentricEcliptic(date time.Time, elements Elements) EclipticPosition {
|
||||||
lon, lat, distance := basic.OrbitApparentGeocentricEcliptic(ttJulianDay(date), toBasicElements(elements))
|
lon, lat, distance := basic.OrbitApparentGeocentricEcliptic(ttJulianDay(date), toBasicElements(elements))
|
||||||
return EclipticPosition{Lon: lon, Lat: lat, Distance: distance}
|
return EclipticPosition{Lon: lon, Lat: lat, Distance: distance}
|
||||||
@@ -140,6 +161,7 @@ func ApparentGeocentricEcliptic(date time.Time, elements Elements) EclipticPosit
|
|||||||
// ApparentGeocentricEquatorial 地心视赤道坐标 / apparent geocentric equatorial coordinates.
|
// ApparentGeocentricEquatorial 地心视赤道坐标 / apparent geocentric equatorial coordinates.
|
||||||
//
|
//
|
||||||
// 返回加入光行时与章动修正后的地心视赤经、赤纬和距离;角度单位度,距离单位 AU。
|
// 返回加入光行时与章动修正后的地心视赤经、赤纬和距离;角度单位度,距离单位 AU。
|
||||||
|
// Returns apparent geocentric right ascension, declination, and distance after light-time and nutation corrections. Angles are in degrees and distance is in AU.
|
||||||
func ApparentGeocentricEquatorial(date time.Time, elements Elements) EquatorialPosition {
|
func ApparentGeocentricEquatorial(date time.Time, elements Elements) EquatorialPosition {
|
||||||
ra, dec, distance := basic.OrbitApparentGeocentricEquatorial(ttJulianDay(date), toBasicElements(elements))
|
ra, dec, distance := basic.OrbitApparentGeocentricEquatorial(ttJulianDay(date), toBasicElements(elements))
|
||||||
return EquatorialPosition{RA: ra, Dec: dec, Distance: distance}
|
return EquatorialPosition{RA: ra, Dec: dec, Distance: distance}
|
||||||
@@ -149,6 +171,7 @@ func ApparentGeocentricEquatorial(date time.Time, elements Elements) EquatorialP
|
|||||||
//
|
//
|
||||||
// 返回加入光行时、章动和站心修正后的视赤经、赤纬和距离;
|
// 返回加入光行时、章动和站心修正后的视赤经、赤纬和距离;
|
||||||
// `observerLon` 东经为正,`observerLat` 北纬为正,`observerHeight` 单位米。
|
// `observerLon` 东经为正,`observerLat` 北纬为正,`observerHeight` 单位米。
|
||||||
|
// Returns apparent topocentric right ascension, declination, and distance after light-time, nutation, and topocentric corrections.
|
||||||
func ApparentTopocentricEquatorial(date time.Time, elements Elements, observerLon, observerLat, observerHeight float64) EquatorialPosition {
|
func ApparentTopocentricEquatorial(date time.Time, elements Elements, observerLon, observerLat, observerHeight float64) EquatorialPosition {
|
||||||
ra, dec, distance := basic.OrbitApparentTopocentricEquatorial(ttJulianDay(date), observerLon, observerLat, observerHeight, toBasicElements(elements))
|
ra, dec, distance := basic.OrbitApparentTopocentricEquatorial(ttJulianDay(date), observerLon, observerLat, observerHeight, toBasicElements(elements))
|
||||||
return EquatorialPosition{RA: ra, Dec: dec, Distance: distance}
|
return EquatorialPosition{RA: ra, Dec: dec, Distance: distance}
|
||||||
@@ -157,6 +180,7 @@ func ApparentTopocentricEquatorial(date time.Time, elements Elements, observerLo
|
|||||||
// Altitude 视高度角 / apparent altitude.
|
// Altitude 视高度角 / apparent altitude.
|
||||||
//
|
//
|
||||||
// 返回目标在观测者所在地的视高度角,单位度;经度东正西负,纬度北正南负,海拔单位米。
|
// 返回目标在观测者所在地的视高度角,单位度;经度东正西负,纬度北正南负,海拔单位米。
|
||||||
|
// Returns the apparent altitude of the target for the observing site, in degrees. Longitude is east-positive, latitude is north-positive, and height is in meters.
|
||||||
func Altitude(date time.Time, elements Elements, observerLon, observerLat, observerHeight float64) float64 {
|
func Altitude(date time.Time, elements Elements, observerLon, observerLat, observerHeight float64) float64 {
|
||||||
jde := basic.Date2JDE(date)
|
jde := basic.Date2JDE(date)
|
||||||
return basic.OrbitHeight(jde, observerLon, observerLat, observationTimezone(date), observerHeight, toBasicElements(elements))
|
return basic.OrbitHeight(jde, observerLon, observerLat, observationTimezone(date), observerHeight, toBasicElements(elements))
|
||||||
@@ -165,6 +189,7 @@ func Altitude(date time.Time, elements Elements, observerLon, observerLat, obser
|
|||||||
// Zenith 天顶距 / zenith distance.
|
// Zenith 天顶距 / zenith distance.
|
||||||
//
|
//
|
||||||
// 返回目标在观测者所在地的天顶距,单位度。
|
// 返回目标在观测者所在地的天顶距,单位度。
|
||||||
|
// Returns the zenith distance of the target for the observing site, in degrees.
|
||||||
func Zenith(date time.Time, elements Elements, observerLon, observerLat, observerHeight float64) float64 {
|
func Zenith(date time.Time, elements Elements, observerLon, observerLat, observerHeight float64) float64 {
|
||||||
return 90 - Altitude(date, elements, observerLon, observerLat, observerHeight)
|
return 90 - Altitude(date, elements, observerLon, observerLat, observerHeight)
|
||||||
}
|
}
|
||||||
@@ -172,6 +197,7 @@ func Zenith(date time.Time, elements Elements, observerLon, observerLat, observe
|
|||||||
// Azimuth 视方位角 / apparent azimuth.
|
// Azimuth 视方位角 / apparent azimuth.
|
||||||
//
|
//
|
||||||
// 返回目标在观测者所在地的视方位角,按正北为 0°、向东增加。
|
// 返回目标在观测者所在地的视方位角,按正北为 0°、向东增加。
|
||||||
|
// Returns the apparent azimuth of the target for the observing site, measured from north toward east.
|
||||||
func Azimuth(date time.Time, elements Elements, observerLon, observerLat, observerHeight float64) float64 {
|
func Azimuth(date time.Time, elements Elements, observerLon, observerLat, observerHeight float64) float64 {
|
||||||
jde := basic.Date2JDE(date)
|
jde := basic.Date2JDE(date)
|
||||||
return basic.OrbitAzimuth(jde, observerLon, observerLat, observationTimezone(date), observerHeight, toBasicElements(elements))
|
return basic.OrbitAzimuth(jde, observerLon, observerLat, observationTimezone(date), observerHeight, toBasicElements(elements))
|
||||||
@@ -180,6 +206,7 @@ func Azimuth(date time.Time, elements Elements, observerLon, observerLat, observ
|
|||||||
// HourAngle 站心视时角 / topocentric hour angle.
|
// HourAngle 站心视时角 / topocentric hour angle.
|
||||||
//
|
//
|
||||||
// 返回目标在观测者所在地的站心视时角,单位度。
|
// 返回目标在观测者所在地的站心视时角,单位度。
|
||||||
|
// Returns the apparent topocentric hour angle of the target for the observing site, in degrees.
|
||||||
func HourAngle(date time.Time, elements Elements, observerLon, observerLat, observerHeight float64) float64 {
|
func HourAngle(date time.Time, elements Elements, observerLon, observerLat, observerHeight float64) float64 {
|
||||||
jde := basic.Date2JDE(date)
|
jde := basic.Date2JDE(date)
|
||||||
return basic.OrbitHourAngle(jde, observerLon, observerLat, observationTimezone(date), observerHeight, toBasicElements(elements))
|
return basic.OrbitHourAngle(jde, observerLon, observerLat, observationTimezone(date), observerHeight, toBasicElements(elements))
|
||||||
@@ -188,6 +215,7 @@ func HourAngle(date time.Time, elements Elements, observerLon, observerLat, obse
|
|||||||
// CulminationTime 中天时刻 / culmination time.
|
// CulminationTime 中天时刻 / culmination time.
|
||||||
//
|
//
|
||||||
// 返回目标在给定当地日期内的中天时刻,结果保持输入 `date` 的时区。
|
// 返回目标在给定当地日期内的中天时刻,结果保持输入 `date` 的时区。
|
||||||
|
// Returns the culmination time of the target on the supplied local civil day. The result keeps the timezone of `date`.
|
||||||
func CulminationTime(date time.Time, elements Elements, observerLon, observerLat, observerHeight float64) time.Time {
|
func CulminationTime(date time.Time, elements Elements, observerLon, observerLat, observerHeight float64) time.Time {
|
||||||
if date.Hour() > 12 {
|
if date.Hour() > 12 {
|
||||||
date = date.Add(-12 * time.Hour)
|
date = date.Add(-12 * time.Hour)
|
||||||
@@ -201,6 +229,7 @@ func CulminationTime(date time.Time, elements Elements, observerLon, observerLat
|
|||||||
// RiseTime 升起时刻 / rise time.
|
// RiseTime 升起时刻 / rise time.
|
||||||
//
|
//
|
||||||
// 返回目标在给定当地日期内的升起时刻;`aero=true` 时加入标准大气折射修正。
|
// 返回目标在给定当地日期内的升起时刻;`aero=true` 时加入标准大气折射修正。
|
||||||
|
// Returns the rise time of the target on the supplied local civil day. When `aero` is true, standard atmospheric refraction is included.
|
||||||
func RiseTime(date time.Time, elements Elements, observerLon, observerLat, observerHeight float64, aero bool) (time.Time, error) {
|
func RiseTime(date time.Time, elements Elements, observerLon, observerLat, observerHeight float64, aero bool) (time.Time, error) {
|
||||||
var aeroFloat float64
|
var aeroFloat float64
|
||||||
if aero {
|
if aero {
|
||||||
@@ -218,6 +247,7 @@ func RiseTime(date time.Time, elements Elements, observerLon, observerLat, obser
|
|||||||
// SetTime 落下时刻 / set time.
|
// SetTime 落下时刻 / set time.
|
||||||
//
|
//
|
||||||
// 返回目标在给定当地日期内的落下时刻;`aero=true` 时加入标准大气折射修正。
|
// 返回目标在给定当地日期内的落下时刻;`aero=true` 时加入标准大气折射修正。
|
||||||
|
// Returns the set time of the target on the supplied local civil day. When `aero` is true, standard atmospheric refraction is included.
|
||||||
func SetTime(date time.Time, elements Elements, observerLon, observerLat, observerHeight float64, aero bool) (time.Time, error) {
|
func SetTime(date time.Time, elements Elements, observerLon, observerLat, observerHeight float64, aero bool) (time.Time, error) {
|
||||||
var aeroFloat float64
|
var aeroFloat float64
|
||||||
if aero {
|
if aero {
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import (
|
|||||||
// ParallacticAngle 轨道目标视差角(天顶方向角) / orbit-target parallactic angle.
|
// ParallacticAngle 轨道目标视差角(天顶方向角) / orbit-target parallactic angle.
|
||||||
//
|
//
|
||||||
// 返回轨道目标在观测者所在地的视差角,单位度;`observerLon` 东经为正,`observerLat` 北纬为正,`observerHeight` 单位米。
|
// 返回轨道目标在观测者所在地的视差角,单位度;`observerLon` 东经为正,`observerLat` 北纬为正,`observerHeight` 单位米。
|
||||||
|
// Returns the parallactic angle of the orbital target for the observing site, in degrees. `observerLon` is east-positive, `observerLat` is north-positive, and `observerHeight` is in meters.
|
||||||
func ParallacticAngle(date time.Time, elements Elements, observerLon, observerLat, observerHeight float64) float64 {
|
func ParallacticAngle(date time.Time, elements Elements, observerLon, observerLat, observerHeight float64) float64 {
|
||||||
position := ApparentTopocentricEquatorial(date, elements, observerLon, observerLat, observerHeight)
|
position := ApparentTopocentricEquatorial(date, elements, observerLon, observerLat, observerHeight)
|
||||||
return basic.ParallacticAngleByHourAngle(
|
return basic.ParallacticAngleByHourAngle(
|
||||||
|
|||||||
@@ -8,7 +8,10 @@ import (
|
|||||||
const visualBinaryDeg = 180 / math.Pi
|
const visualBinaryDeg = 180 / math.Pi
|
||||||
const visualBinaryRad = math.Pi / 180
|
const visualBinaryRad = math.Pi / 180
|
||||||
|
|
||||||
// VisualBinaryElements 视双星轨道要素,采用《天文算法》第 55 章的经典口径。
|
// VisualBinaryElements 视双星轨道要素 / visual-binary orbital elements.
|
||||||
|
//
|
||||||
|
// 采用《天文算法》第 55 章的经典口径。
|
||||||
|
// Uses the classical convention described in Chapter 55 of Astronomical Algorithms.
|
||||||
type VisualBinaryElements struct {
|
type VisualBinaryElements struct {
|
||||||
PeriodYears float64 // 周期 P,单位平太阳年 / orbital period in mean solar years.
|
PeriodYears float64 // 周期 P,单位平太阳年 / orbital period in mean solar years.
|
||||||
PeriastronYear float64 // 过近星点时刻 T,采用带小数的年 / epoch of periastron as a decimal year.
|
PeriastronYear float64 // 过近星点时刻 T,采用带小数的年 / epoch of periastron as a decimal year.
|
||||||
@@ -19,7 +22,7 @@ type VisualBinaryElements struct {
|
|||||||
PeriastronArgument float64 // 近星点角距 ω,单位度 / argument of periastron in degrees.
|
PeriastronArgument float64 // 近星点角距 ω,单位度 / argument of periastron in degrees.
|
||||||
}
|
}
|
||||||
|
|
||||||
// VisualBinaryPosition 视双星在天球上的计算结果。
|
// VisualBinaryPosition 视双星在天球上的计算结果 / computed sky-plane position of a visual binary.
|
||||||
type VisualBinaryPosition struct {
|
type VisualBinaryPosition struct {
|
||||||
Year float64 // 计算使用的小数年 / decimal year used for the evaluation.
|
Year float64 // 计算使用的小数年 / decimal year used for the evaluation.
|
||||||
MeanAnomaly float64 // 平近点角 M,单位度 / mean anomaly in degrees.
|
MeanAnomaly float64 // 平近点角 M,单位度 / mean anomaly in degrees.
|
||||||
@@ -41,6 +44,7 @@ func VisualBinary(date time.Time, elements VisualBinaryElements) VisualBinaryPos
|
|||||||
// VisualBinaryByYear 视双星位置(按小数年) / visual binary position by decimal year.
|
// VisualBinaryByYear 视双星位置(按小数年) / visual binary position by decimal year.
|
||||||
//
|
//
|
||||||
// 返回给定小数年对应的视双星位置角和角距离。
|
// 返回给定小数年对应的视双星位置角和角距离。
|
||||||
|
// Returns the position angle and apparent separation for the supplied decimal year.
|
||||||
func VisualBinaryByYear(year float64, elements VisualBinaryElements) VisualBinaryPosition {
|
func VisualBinaryByYear(year float64, elements VisualBinaryElements) VisualBinaryPosition {
|
||||||
if !validVisualBinaryElements(year, elements) {
|
if !validVisualBinaryElements(year, elements) {
|
||||||
return invalidVisualBinaryPosition(year)
|
return invalidVisualBinaryPosition(year)
|
||||||
|
|||||||
+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
|
||||||
}
|
}
|
||||||
|
|||||||
+24
-24
@@ -207,8 +207,8 @@ 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.
|
// Returns the nearest conjunction with the Sun at or before 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 +216,8 @@ 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.
|
// Returns the nearest conjunction with the Sun at or after 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 +225,8 @@ 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.
|
// Returns the nearest opposition at or before 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 +234,8 @@ 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.
|
// Returns the nearest opposition at or after 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 +243,8 @@ 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.
|
// Returns the nearest station at or before date where motion changes from prograde to retrograde, 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 +252,8 @@ 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.
|
// Returns the nearest station at or after date where motion changes from prograde to retrograde, 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 +261,8 @@ 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.
|
// Returns the nearest station at or before date where motion changes from retrograde to prograde, 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 +270,8 @@ 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.
|
// Returns the nearest station at or after date where motion changes from retrograde to prograde, 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 +279,8 @@ 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.
|
// Returns the nearest eastern quadrature at or before 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 +288,8 @@ 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.
|
// Returns the nearest eastern quadrature at or after 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 +297,8 @@ 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.
|
// Returns the nearest western quadrature at or before 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 +306,8 @@ 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.
|
// Returns the nearest western quadrature at or after 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)
|
||||||
|
|||||||
@@ -10,6 +10,8 @@ import (
|
|||||||
//
|
//
|
||||||
// ra/dec 为瞬时赤经赤纬,单位度;lon/lat 为观测者经纬度,东正西负、北正南负。
|
// ra/dec 为瞬时赤经赤纬,单位度;lon/lat 为观测者经纬度,东正西负、北正南负。
|
||||||
// 返回值为有符号视差角,单位度。
|
// 返回值为有符号视差角,单位度。
|
||||||
|
// ra/dec are apparent equatorial coordinates in degrees; lon/lat are east-positive and north-positive.
|
||||||
|
// Returns the signed parallactic angle in degrees.
|
||||||
func ParallacticAngle(date time.Time, ra, dec, lon, lat float64) float64 {
|
func ParallacticAngle(date time.Time, ra, dec, lon, lat float64) float64 {
|
||||||
jde := basic.Date2JDE(date)
|
jde := basic.Date2JDE(date)
|
||||||
_, loc := date.Zone()
|
_, loc := date.Zone()
|
||||||
|
|||||||
@@ -100,6 +100,8 @@ func DownTime(date time.Time, lon, lat, height float64, aero bool) (time.Time, e
|
|||||||
|
|
||||||
// DownTimeN 截断项日落时刻别名 / deprecated truncated sunset alias.
|
// DownTimeN 截断项日落时刻别名 / deprecated truncated sunset alias.
|
||||||
//
|
//
|
||||||
|
// Deprecated: use SetTimeN instead.
|
||||||
|
//
|
||||||
// 参数与 SetTimeN 相同,仅为兼容旧接口保留。
|
// 参数与 SetTimeN 相同,仅为兼容旧接口保留。
|
||||||
// Same as SetTimeN and kept only for backward compatibility.
|
// Same as SetTimeN and kept only for backward compatibility.
|
||||||
func DownTimeN(date time.Time, lon, lat, height float64, aero bool, n int) (time.Time, error) {
|
func DownTimeN(date time.Time, lon, lat, height float64, aero bool, n int) (time.Time, error) {
|
||||||
|
|||||||
+43
-1
@@ -15,6 +15,12 @@ import (
|
|||||||
//
|
//
|
||||||
// 坐标系沿本章通用约定:x 轴位于盘面内且保持水平,y 轴沿盘面最大坡度向上,
|
// 坐标系沿本章通用约定:x 轴位于盘面内且保持水平,y 轴沿盘面最大坡度向上,
|
||||||
// x 正向为右侧,y 正向为上坡方向。
|
// x 正向为右侧,y 正向为上坡方向。
|
||||||
|
// Latitude is geographic latitude, north positive. PlaneNormalAzimuth is the azimuth of the dial-plane normal,
|
||||||
|
// measured from north toward east. PlaneNormalZenithDistance is the zenith distance of the plane normal:
|
||||||
|
// 0 degrees for a horizontal dial and 90 degrees for a vertical dial. StylusLength is the length of the direct stylus normal to the plane.
|
||||||
|
//
|
||||||
|
// The plane coordinates follow the chapter convention: the x axis lies in the plane and remains horizontal,
|
||||||
|
// the y axis follows the steepest upward direction in the plane, x positive points to the right, and y positive points upslope.
|
||||||
type PlanarDial struct {
|
type PlanarDial struct {
|
||||||
Latitude float64
|
Latitude float64
|
||||||
PlaneNormalAzimuth float64
|
PlaneNormalAzimuth float64
|
||||||
@@ -27,6 +33,9 @@ type PlanarDial struct {
|
|||||||
// X/Y 为影尖在盘面坐标系中的坐标;DenominatorQ 对应书中公式里的 Q;
|
// X/Y 为影尖在盘面坐标系中的坐标;DenominatorQ 对应书中公式里的 Q;
|
||||||
// SunAboveHorizon 表示太阳在地平线上方;PlaneIlluminated 表示盘面被太阳照亮;
|
// SunAboveHorizon 表示太阳在地平线上方;PlaneIlluminated 表示盘面被太阳照亮;
|
||||||
// Illuminated 为前两者同时满足。
|
// Illuminated 为前两者同时满足。
|
||||||
|
// X/Y are the shadow-tip coordinates in the dial-plane coordinate system. DenominatorQ is the Q term from the book formula.
|
||||||
|
// SunAboveHorizon reports whether the Sun is above the horizon. PlaneIlluminated reports whether sunlight reaches the dial plane.
|
||||||
|
// Illuminated is true only when both conditions are satisfied.
|
||||||
type PlanarShadowPoint struct {
|
type PlanarShadowPoint struct {
|
||||||
X float64
|
X float64
|
||||||
Y float64
|
Y float64
|
||||||
@@ -41,6 +50,9 @@ type PlanarShadowPoint struct {
|
|||||||
// CenterX/CenterY 为日晷中心(极轴晷针固定点)坐标;PolarStylusLength 为极轴晷针长度;
|
// CenterX/CenterY 为日晷中心(极轴晷针固定点)坐标;PolarStylusLength 为极轴晷针长度;
|
||||||
// PolarStylusPlaneAngle 为极轴晷针与盘面的夹角。HasFiniteCenter 为 false 时,
|
// PolarStylusPlaneAngle 为极轴晷针与盘面的夹角。HasFiniteCenter 为 false 时,
|
||||||
// 表示极轴晷针与盘面平行,中心退化到无穷远处。
|
// 表示极轴晷针与盘面平行,中心退化到无穷远处。
|
||||||
|
// CenterX/CenterY are the coordinates of the dial center, where the polar stylus is fixed. PolarStylusLength is the polar-stylus length.
|
||||||
|
// PolarStylusPlaneAngle is the angle between the polar stylus and the dial plane. When HasFiniteCenter is false,
|
||||||
|
// the polar stylus is parallel to the plane and the center degenerates to infinity.
|
||||||
type PlanarGeometry struct {
|
type PlanarGeometry struct {
|
||||||
CenterX float64
|
CenterX float64
|
||||||
CenterY float64
|
CenterY float64
|
||||||
@@ -53,6 +65,8 @@ type PlanarGeometry struct {
|
|||||||
//
|
//
|
||||||
// Start/End 均为有符号太阳时角,单位度,满足 Start <= End。
|
// Start/End 均为有符号太阳时角,单位度,满足 Start <= End。
|
||||||
// 约定取值范围为 [-180, 180],用于表达一天中的一段连续时角。
|
// 约定取值范围为 [-180, 180],用于表达一天中的一段连续时角。
|
||||||
|
// Start and End are signed solar hour angles in degrees, with Start <= End.
|
||||||
|
// The intended range is [-180, 180], representing one continuous hour-angle span within a day.
|
||||||
type HourAngleInterval struct {
|
type HourAngleInterval struct {
|
||||||
Start float64
|
Start float64
|
||||||
End float64
|
End float64
|
||||||
@@ -61,6 +75,7 @@ type HourAngleInterval struct {
|
|||||||
// DeclinationCurveSample 赤纬曲线采样点 / sampled point on a declination curve.
|
// DeclinationCurveSample 赤纬曲线采样点 / sampled point on a declination curve.
|
||||||
//
|
//
|
||||||
// HourAngle 为采样点的太阳时角;Point 为该时角下的影尖位置与照明状态。
|
// HourAngle 为采样点的太阳时角;Point 为该时角下的影尖位置与照明状态。
|
||||||
|
// HourAngle is the solar hour angle of the sample. Point gives the shadow-tip position and illumination state at that hour angle.
|
||||||
type DeclinationCurveSample struct {
|
type DeclinationCurveSample struct {
|
||||||
HourAngle float64
|
HourAngle float64
|
||||||
Point PlanarShadowPoint
|
Point PlanarShadowPoint
|
||||||
@@ -69,6 +84,7 @@ type DeclinationCurveSample struct {
|
|||||||
// DeclinationCurveSegment 赤纬曲线分段 / one illuminated segment of a declination curve.
|
// DeclinationCurveSegment 赤纬曲线分段 / one illuminated segment of a declination curve.
|
||||||
//
|
//
|
||||||
// Interval 给出该段对应的连续受光时角范围;Samples 为该段内部的采样点。
|
// Interval 给出该段对应的连续受光时角范围;Samples 为该段内部的采样点。
|
||||||
|
// Interval gives the continuous illuminated hour-angle span for the segment. Samples contains the sampled points within that segment.
|
||||||
type DeclinationCurveSegment struct {
|
type DeclinationCurveSegment struct {
|
||||||
Declination float64
|
Declination float64
|
||||||
Interval HourAngleInterval
|
Interval HourAngleInterval
|
||||||
@@ -80,6 +96,9 @@ type DeclinationCurveSegment struct {
|
|||||||
// Date 为该采样点对应的绝对时刻;其日期来自输入 date,钟面时间由调用参数指定。
|
// Date 为该采样点对应的绝对时刻;其日期来自输入 date,钟面时间由调用参数指定。
|
||||||
// Declination 为该采样瞬时的太阳赤纬;HourAngle 为换算后的视太阳时角;
|
// Declination 为该采样瞬时的太阳赤纬;HourAngle 为换算后的视太阳时角;
|
||||||
// Point 为该时角下的影尖位置与照明状态。
|
// Point 为该时角下的影尖位置与照明状态。
|
||||||
|
// Date is the absolute instant of the sample. Its date comes from the input date, while the clock reading comes from the call parameters.
|
||||||
|
// Declination is the solar declination at that instant. HourAngle is the derived apparent-solar hour angle.
|
||||||
|
// Point gives the shadow-tip position and illumination state at that hour angle.
|
||||||
type TimeLineSample struct {
|
type TimeLineSample struct {
|
||||||
Date time.Time
|
Date time.Time
|
||||||
Declination float64
|
Declination float64
|
||||||
@@ -90,6 +109,7 @@ type TimeLineSample struct {
|
|||||||
// EquatorialNorthDial 北面赤道日晷 / north-face equatorial dial.
|
// EquatorialNorthDial 北面赤道日晷 / north-face equatorial dial.
|
||||||
//
|
//
|
||||||
// 北半球时用于春夏半年(太阳赤纬为正);南半球也可直接按公式使用。
|
// 北半球时用于春夏半年(太阳赤纬为正);南半球也可直接按公式使用。
|
||||||
|
// In the northern hemisphere this face is used during the spring and summer half-year, when solar declination is positive. The same formula can also be used directly in the southern hemisphere.
|
||||||
func EquatorialNorthDial(latitude, stylusLength float64) PlanarDial {
|
func EquatorialNorthDial(latitude, stylusLength float64) PlanarDial {
|
||||||
return PlanarDial{
|
return PlanarDial{
|
||||||
Latitude: latitude,
|
Latitude: latitude,
|
||||||
@@ -102,6 +122,7 @@ func EquatorialNorthDial(latitude, stylusLength float64) PlanarDial {
|
|||||||
// EquatorialSouthDial 南面赤道日晷 / south-face equatorial dial.
|
// EquatorialSouthDial 南面赤道日晷 / south-face equatorial dial.
|
||||||
//
|
//
|
||||||
// 北半球时用于秋冬半年(太阳赤纬为负);南半球也可直接按公式使用。
|
// 北半球时用于秋冬半年(太阳赤纬为负);南半球也可直接按公式使用。
|
||||||
|
// In the northern hemisphere this face is used during the autumn and winter half-year, when solar declination is negative. The same formula can also be used directly in the southern hemisphere.
|
||||||
func EquatorialSouthDial(latitude, stylusLength float64) PlanarDial {
|
func EquatorialSouthDial(latitude, stylusLength float64) PlanarDial {
|
||||||
return PlanarDial{
|
return PlanarDial{
|
||||||
Latitude: latitude,
|
Latitude: latitude,
|
||||||
@@ -114,6 +135,7 @@ func EquatorialSouthDial(latitude, stylusLength float64) PlanarDial {
|
|||||||
// HorizontalDial 水平日晷 / horizontal dial.
|
// HorizontalDial 水平日晷 / horizontal dial.
|
||||||
//
|
//
|
||||||
// 该构造器采用经典水平日晷的坐标约定:x 轴向东,y 轴向北。
|
// 该构造器采用经典水平日晷的坐标约定:x 轴向东,y 轴向北。
|
||||||
|
// This constructor follows the classical horizontal-dial coordinate convention: the x axis points east and the y axis points north.
|
||||||
func HorizontalDial(latitude, stylusLength float64) PlanarDial {
|
func HorizontalDial(latitude, stylusLength float64) PlanarDial {
|
||||||
return PlanarDial{
|
return PlanarDial{
|
||||||
Latitude: latitude,
|
Latitude: latitude,
|
||||||
@@ -127,6 +149,8 @@ func HorizontalDial(latitude, stylusLength float64) PlanarDial {
|
|||||||
//
|
//
|
||||||
// planeNormalAzimuth 为盘面法线方位角,按正北为 0°、向东增加。
|
// planeNormalAzimuth 为盘面法线方位角,按正北为 0°、向东增加。
|
||||||
// 例如:朝南墙面取 180°,朝东墙面取 90°。
|
// 例如:朝南墙面取 180°,朝东墙面取 90°。
|
||||||
|
// planeNormalAzimuth is the azimuth of the plane normal, measured from north toward east.
|
||||||
|
// For example, use 180 degrees for a south-facing wall and 90 degrees for an east-facing wall.
|
||||||
func VerticalDial(latitude, planeNormalAzimuth, stylusLength float64) PlanarDial {
|
func VerticalDial(latitude, planeNormalAzimuth, stylusLength float64) PlanarDial {
|
||||||
return PlanarDial{
|
return PlanarDial{
|
||||||
Latitude: latitude,
|
Latitude: latitude,
|
||||||
@@ -136,7 +160,10 @@ func VerticalDial(latitude, planeNormalAzimuth, stylusLength float64) PlanarDial
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Geometry 返回平面日晷的中心与极轴晷针几何量 / returns the derived planar geometry.
|
// Geometry 平面日晷中心与极轴晷针几何量 / derived planar-dial center and polar-stylus geometry.
|
||||||
|
//
|
||||||
|
// 返回平面日晷的中心与极轴晷针几何量。
|
||||||
|
// Returns the derived center and polar-stylus geometry of the planar dial.
|
||||||
func (dial PlanarDial) Geometry() PlanarGeometry {
|
func (dial PlanarDial) Geometry() PlanarGeometry {
|
||||||
geometry := PlanarGeometry{
|
geometry := PlanarGeometry{
|
||||||
CenterX: math.NaN(),
|
CenterX: math.NaN(),
|
||||||
@@ -167,6 +194,7 @@ func (dial PlanarDial) Geometry() PlanarGeometry {
|
|||||||
// ShadowPointByHourAngleDeclination 影尖坐标(按时角与赤纬) / shadow point from hour angle and declination.
|
// ShadowPointByHourAngleDeclination 影尖坐标(按时角与赤纬) / shadow point from hour angle and declination.
|
||||||
//
|
//
|
||||||
// hourAngle 为有符号太阳时角,上午为负,下午为正;declination 为太阳赤纬,单位度。
|
// hourAngle 为有符号太阳时角,上午为负,下午为正;declination 为太阳赤纬,单位度。
|
||||||
|
// hourAngle is the signed solar hour angle, negative in the morning and positive in the afternoon. declination is solar declination in degrees.
|
||||||
func (dial PlanarDial) ShadowPointByHourAngleDeclination(hourAngle, declination float64) PlanarShadowPoint {
|
func (dial PlanarDial) ShadowPointByHourAngleDeclination(hourAngle, declination float64) PlanarShadowPoint {
|
||||||
point := PlanarShadowPoint{
|
point := PlanarShadowPoint{
|
||||||
X: math.NaN(),
|
X: math.NaN(),
|
||||||
@@ -194,6 +222,7 @@ func (dial PlanarDial) ShadowPointByHourAngleDeclination(hourAngle, declination
|
|||||||
// ShadowPointAt 影尖坐标(按绝对时刻) / shadow point at an instant.
|
// ShadowPointAt 影尖坐标(按绝对时刻) / shadow point at an instant.
|
||||||
//
|
//
|
||||||
// 直接读取该时刻对应的视太阳时角和瞬时太阳赤纬,并返回平面日晷上的影尖位置。
|
// 直接读取该时刻对应的视太阳时角和瞬时太阳赤纬,并返回平面日晷上的影尖位置。
|
||||||
|
// Uses the apparent-solar hour angle and instantaneous solar declination at the supplied instant, and returns the shadow-tip position on the planar dial.
|
||||||
func (dial PlanarDial) ShadowPointAt(date time.Time, lon float64) PlanarShadowPoint {
|
func (dial PlanarDial) ShadowPointAt(date time.Time, lon float64) PlanarShadowPoint {
|
||||||
return dial.ShadowPointByHourAngleDeclination(HourAngle(date, lon), sun.ApparentDec(date))
|
return dial.ShadowPointByHourAngleDeclination(HourAngle(date, lon), sun.ApparentDec(date))
|
||||||
}
|
}
|
||||||
@@ -202,6 +231,8 @@ func (dial PlanarDial) ShadowPointAt(date time.Time, lon float64) PlanarShadowPo
|
|||||||
//
|
//
|
||||||
// date 应处于目标地点的地方平太阳时区,例如 `MeanSolarTime` 的返回值;其原有钟面时间会被忽略。
|
// date 应处于目标地点的地方平太阳时区,例如 `MeanSolarTime` 的返回值;其原有钟面时间会被忽略。
|
||||||
// meanSolarHours 为地方平太阳时钟面读数,单位小时,例如 9.5 表示 09:30。
|
// meanSolarHours 为地方平太阳时钟面读数,单位小时,例如 9.5 表示 09:30。
|
||||||
|
// date should already be expressed in the local mean-solar timezone of the site, for example a value returned by `MeanSolarTime`; its original clock fields are ignored.
|
||||||
|
// meanSolarHours is the local mean-solar clock reading in hours, for example 9.5 for 09:30.
|
||||||
func (dial PlanarDial) MeanSolarTimePoint(date time.Time, meanSolarHours float64) PlanarShadowPoint {
|
func (dial PlanarDial) MeanSolarTimePoint(date time.Time, meanSolarHours float64) PlanarShadowPoint {
|
||||||
sampleTime := dateWithClockHours(date, meanSolarHours)
|
sampleTime := dateWithClockHours(date, meanSolarHours)
|
||||||
declination := sun.ApparentDec(sampleTime)
|
declination := sun.ApparentDec(sampleTime)
|
||||||
@@ -211,6 +242,7 @@ func (dial PlanarDial) MeanSolarTimePoint(date time.Time, meanSolarHours float64
|
|||||||
// ZoneTimePoint 区时影尖位置 / shadow point for zone time.
|
// ZoneTimePoint 区时影尖位置 / shadow point for zone time.
|
||||||
//
|
//
|
||||||
// date 提供民用日期和时区,原有钟面时间会被忽略;zoneTimeHours 为该时区下的区时钟面读数。
|
// date 提供民用日期和时区,原有钟面时间会被忽略;zoneTimeHours 为该时区下的区时钟面读数。
|
||||||
|
// date provides the civil date and timezone; its original clock fields are ignored. zoneTimeHours is the civil clock reading in that timezone.
|
||||||
func (dial PlanarDial) ZoneTimePoint(date time.Time, lon, zoneTimeHours float64) PlanarShadowPoint {
|
func (dial PlanarDial) ZoneTimePoint(date time.Time, lon, zoneTimeHours float64) PlanarShadowPoint {
|
||||||
sampleTime := dateWithClockHours(date, zoneTimeHours)
|
sampleTime := dateWithClockHours(date, zoneTimeHours)
|
||||||
declination := sun.ApparentDec(sampleTime)
|
declination := sun.ApparentDec(sampleTime)
|
||||||
@@ -221,6 +253,8 @@ func (dial PlanarDial) ZoneTimePoint(date time.Time, lon, zoneTimeHours float64)
|
|||||||
//
|
//
|
||||||
// dates 由调用者自行决定取样日期密度,例如每月或每日;每个 date 都应处于目标地点的地方平太阳时区,
|
// dates 由调用者自行决定取样日期密度,例如每月或每日;每个 date 都应处于目标地点的地方平太阳时区,
|
||||||
// 例如先通过 `MeanSolarTime` 得到对应地点的地方平太阳时再取其年月日。meanSolarHours 为地方平太阳时钟面读数。
|
// 例如先通过 `MeanSolarTime` 得到对应地点的地方平太阳时再取其年月日。meanSolarHours 为地方平太阳时钟面读数。
|
||||||
|
// dates define the sampling cadence, for example monthly or daily. Each date should already be in the site's local mean-solar timezone,
|
||||||
|
// for example by first calling `MeanSolarTime` and then keeping its year, month, and day. meanSolarHours is the local mean-solar clock reading.
|
||||||
func (dial PlanarDial) MeanSolarTimeLine(dates []time.Time, meanSolarHours float64) []TimeLineSample {
|
func (dial PlanarDial) MeanSolarTimeLine(dates []time.Time, meanSolarHours float64) []TimeLineSample {
|
||||||
if !isFinite(meanSolarHours) {
|
if !isFinite(meanSolarHours) {
|
||||||
return nil
|
return nil
|
||||||
@@ -244,6 +278,8 @@ func (dial PlanarDial) MeanSolarTimeLine(dates []time.Time, meanSolarHours float
|
|||||||
//
|
//
|
||||||
// dates 由调用者自行决定取样日期密度;zoneTimeHours 为 date 所在时区的区时钟面读数。
|
// dates 由调用者自行决定取样日期密度;zoneTimeHours 为 date 所在时区的区时钟面读数。
|
||||||
// 每个 date 的原有钟面时间都会被 zoneTimeHours 替换。
|
// 每个 date 的原有钟面时间都会被 zoneTimeHours 替换。
|
||||||
|
// dates define the sampling cadence. zoneTimeHours is the civil clock reading in the timezone carried by each date.
|
||||||
|
// The original clock fields of every date are replaced by zoneTimeHours.
|
||||||
func (dial PlanarDial) ZoneTimeLine(dates []time.Time, lon, zoneTimeHours float64) []TimeLineSample {
|
func (dial PlanarDial) ZoneTimeLine(dates []time.Time, lon, zoneTimeHours float64) []TimeLineSample {
|
||||||
if !isFinite(zoneTimeHours) || !isFinite(lon) {
|
if !isFinite(zoneTimeHours) || !isFinite(lon) {
|
||||||
return nil
|
return nil
|
||||||
@@ -266,6 +302,7 @@ func (dial PlanarDial) ZoneTimeLine(dates []time.Time, lon, zoneTimeHours float6
|
|||||||
// PlaneIlluminatedHourAngleIntervals 盘面受光时角区间 / plane-illuminated hour-angle intervals.
|
// PlaneIlluminatedHourAngleIntervals 盘面受光时角区间 / plane-illuminated hour-angle intervals.
|
||||||
//
|
//
|
||||||
// declination 为太阳赤纬,单位度。返回的区间只考虑盘面受光,不判断太阳是否在地平线上方。
|
// declination 为太阳赤纬,单位度。返回的区间只考虑盘面受光,不判断太阳是否在地平线上方。
|
||||||
|
// declination is solar declination in degrees. The returned intervals consider only whether the dial plane is illuminated and do not test whether the Sun is above the horizon.
|
||||||
func (dial PlanarDial) PlaneIlluminatedHourAngleIntervals(declination float64) []HourAngleInterval {
|
func (dial PlanarDial) PlaneIlluminatedHourAngleIntervals(declination float64) []HourAngleInterval {
|
||||||
if !dial.isFinite() || !isFinite(declination) {
|
if !dial.isFinite() || !isFinite(declination) {
|
||||||
return nil
|
return nil
|
||||||
@@ -277,6 +314,7 @@ func (dial PlanarDial) PlaneIlluminatedHourAngleIntervals(declination float64) [
|
|||||||
// IlluminatedHourAngleIntervals 可见且受光时角区间 / illuminated hour-angle intervals.
|
// IlluminatedHourAngleIntervals 可见且受光时角区间 / illuminated hour-angle intervals.
|
||||||
//
|
//
|
||||||
// declination 为太阳赤纬,单位度。结果可直接用于日晷绘图时筛掉无效时线。
|
// declination 为太阳赤纬,单位度。结果可直接用于日晷绘图时筛掉无效时线。
|
||||||
|
// declination is solar declination in degrees. The result can be used directly to discard invalid hour-line segments in sundial plotting.
|
||||||
func (dial PlanarDial) IlluminatedHourAngleIntervals(declination float64) []HourAngleInterval {
|
func (dial PlanarDial) IlluminatedHourAngleIntervals(declination float64) []HourAngleInterval {
|
||||||
aboveHorizon := SunAboveHorizonHourAngleIntervals(dial.Latitude, declination)
|
aboveHorizon := SunAboveHorizonHourAngleIntervals(dial.Latitude, declination)
|
||||||
planeLit := dial.PlaneIlluminatedHourAngleIntervals(declination)
|
planeLit := dial.PlaneIlluminatedHourAngleIntervals(declination)
|
||||||
@@ -287,6 +325,8 @@ func (dial PlanarDial) IlluminatedHourAngleIntervals(declination float64) []Hour
|
|||||||
//
|
//
|
||||||
// declination 为太阳赤纬,单位度;hourAngleStep 为采样步长,单位度,常用值是 15°(每小时一格)。
|
// declination 为太阳赤纬,单位度;hourAngleStep 为采样步长,单位度,常用值是 15°(每小时一格)。
|
||||||
// 返回值按受光区间分段,每段都带有精确的时角范围;Samples 只包含区间内部的有效采样点。
|
// 返回值按受光区间分段,每段都带有精确的时角范围;Samples 只包含区间内部的有效采样点。
|
||||||
|
// declination is solar declination in degrees. hourAngleStep is the sampling step in degrees; 15 degrees is a common one-hour spacing.
|
||||||
|
// The return value is split by illuminated intervals. Each segment carries the exact hour-angle bounds, and Samples contains only valid interior sample points.
|
||||||
func (dial PlanarDial) DeclinationCurve(declination, hourAngleStep float64) []DeclinationCurveSegment {
|
func (dial PlanarDial) DeclinationCurve(declination, hourAngleStep float64) []DeclinationCurveSegment {
|
||||||
if !dial.isFinite() || !isFinite(declination) || !isFinite(hourAngleStep) || hourAngleStep <= 0 {
|
if !dial.isFinite() || !isFinite(declination) || !isFinite(hourAngleStep) || hourAngleStep <= 0 {
|
||||||
return nil
|
return nil
|
||||||
@@ -315,6 +355,7 @@ func (dial PlanarDial) DeclinationCurve(declination, hourAngleStep float64) []De
|
|||||||
// DeclinationCurveAt 瞬时赤纬曲线采样 / declination-curve samples at an instant.
|
// DeclinationCurveAt 瞬时赤纬曲线采样 / declination-curve samples at an instant.
|
||||||
//
|
//
|
||||||
// 用 date 对应瞬时太阳赤纬生成日晷分段曲线采样。
|
// 用 date 对应瞬时太阳赤纬生成日晷分段曲线采样。
|
||||||
|
// Builds the segmented declination-curve samples from the instantaneous solar declination at date.
|
||||||
func (dial PlanarDial) DeclinationCurveAt(date time.Time, hourAngleStep float64) []DeclinationCurveSegment {
|
func (dial PlanarDial) DeclinationCurveAt(date time.Time, hourAngleStep float64) []DeclinationCurveSegment {
|
||||||
return dial.DeclinationCurve(sun.ApparentDec(date), hourAngleStep)
|
return dial.DeclinationCurve(sun.ApparentDec(date), hourAngleStep)
|
||||||
}
|
}
|
||||||
@@ -323,6 +364,7 @@ func (dial PlanarDial) DeclinationCurveAt(date time.Time, hourAngleStep float64)
|
|||||||
//
|
//
|
||||||
// latitude 为地理纬度,declination 为太阳赤纬,单位度。结果只反映太阳是否升到地平线上方,
|
// latitude 为地理纬度,declination 为太阳赤纬,单位度。结果只反映太阳是否升到地平线上方,
|
||||||
// 不包含盘面朝向的影响。
|
// 不包含盘面朝向的影响。
|
||||||
|
// latitude is geographic latitude and declination is solar declination, both in degrees. The result reflects only whether the Sun is above the horizon and does not include dial-plane orientation.
|
||||||
func SunAboveHorizonHourAngleIntervals(latitude, declination float64) []HourAngleInterval {
|
func SunAboveHorizonHourAngleIntervals(latitude, declination float64) []HourAngleInterval {
|
||||||
if !isFinite(latitude) || !isFinite(declination) {
|
if !isFinite(latitude) || !isFinite(declination) {
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -39,6 +39,9 @@ func HourAngle(date time.Time, lon float64) float64 {
|
|||||||
// date 负责提供地方平太阳时日期与时区,原有钟面时间会被 meanSolarHours 替换;
|
// date 负责提供地方平太阳时日期与时区,原有钟面时间会被 meanSolarHours 替换;
|
||||||
// meanSolarHours 为地方平太阳时钟面读数,单位小时,例如 9.5 表示地方平太阳时 09:30。
|
// meanSolarHours 为地方平太阳时钟面读数,单位小时,例如 9.5 表示地方平太阳时 09:30。
|
||||||
// 返回对应的视太阳时角,单位度,上午为负,下午为正。
|
// 返回对应的视太阳时角,单位度,上午为负,下午为正。
|
||||||
|
// date provides the local mean-solar date and timezone, while its clock fields are replaced by meanSolarHours.
|
||||||
|
// meanSolarHours is the local mean-solar clock reading in hours, for example 9.5 for 09:30.
|
||||||
|
// Returns the corresponding apparent-solar hour angle in degrees, negative in the morning and positive in the afternoon.
|
||||||
func MeanSolarHourAngle(date time.Time, meanSolarHours float64) float64 {
|
func MeanSolarHourAngle(date time.Time, meanSolarHours float64) float64 {
|
||||||
if !isFinite(meanSolarHours) {
|
if !isFinite(meanSolarHours) {
|
||||||
return math.NaN()
|
return math.NaN()
|
||||||
@@ -52,6 +55,9 @@ func MeanSolarHourAngle(date time.Time, meanSolarHours float64) float64 {
|
|||||||
// zoneTimeHours 为 date 所在时区下的钟面读数,单位小时;lon 为当地经度,东正西负。
|
// zoneTimeHours 为 date 所在时区下的钟面读数,单位小时;lon 为当地经度,东正西负。
|
||||||
// 返回该区时在给定经度上对应的视太阳时角,单位度,上午为负,下午为正。
|
// 返回该区时在给定经度上对应的视太阳时角,单位度,上午为负,下午为正。
|
||||||
// date 提供民用日期和时区;其原有钟面时间会被 zoneTimeHours 替换。
|
// date 提供民用日期和时区;其原有钟面时间会被 zoneTimeHours 替换。
|
||||||
|
// zoneTimeHours is the civil clock reading in the timezone carried by date, in hours; lon is east-positive longitude.
|
||||||
|
// Returns the apparent-solar hour angle for that civil time and longitude, in degrees, negative in the morning and positive in the afternoon.
|
||||||
|
// date provides the civil date and timezone, and its original clock fields are replaced by zoneTimeHours.
|
||||||
func ZoneTimeHourAngle(date time.Time, lon, zoneTimeHours float64) float64 {
|
func ZoneTimeHourAngle(date time.Time, lon, zoneTimeHours float64) float64 {
|
||||||
if !isFinite(zoneTimeHours) || !isFinite(lon) {
|
if !isFinite(zoneTimeHours) || !isFinite(lon) {
|
||||||
return math.NaN()
|
return math.NaN()
|
||||||
@@ -79,6 +85,7 @@ func HorizontalHourLineAngle(lat, hourAngle float64) float64 {
|
|||||||
// HorizontalHourLineAngleAt 水平日晷时线角 / horizontal sundial hour-line angle.
|
// HorizontalHourLineAngleAt 水平日晷时线角 / horizontal sundial hour-line angle.
|
||||||
//
|
//
|
||||||
// 先按给定时刻和经度求瞬时视太阳时角,再结合纬度返回水平日晷的时线角。
|
// 先按给定时刻和经度求瞬时视太阳时角,再结合纬度返回水平日晷的时线角。
|
||||||
|
// First derives the apparent-solar hour angle for the supplied instant and longitude, then returns the horizontal-dial hour-line angle for the given latitude.
|
||||||
func HorizontalHourLineAngleAt(date time.Time, lon, lat float64) float64 {
|
func HorizontalHourLineAngleAt(date time.Time, lon, lat float64) float64 {
|
||||||
return HorizontalHourLineAngle(lat, HourAngle(date, lon))
|
return HorizontalHourLineAngle(lat, HourAngle(date, lon))
|
||||||
}
|
}
|
||||||
|
|||||||
+24
-24
@@ -207,8 +207,8 @@ 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.
|
// Returns the nearest conjunction with the Sun at or before 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 +216,8 @@ 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.
|
// Returns the nearest conjunction with the Sun at or after 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 +225,8 @@ 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.
|
// Returns the nearest opposition at or before 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 +234,8 @@ 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.
|
// Returns the nearest opposition at or after 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 +243,8 @@ 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.
|
// Returns the nearest station at or before date where motion changes from prograde to retrograde, 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 +252,8 @@ 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.
|
// Returns the nearest station at or after date where motion changes from prograde to retrograde, 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 +261,8 @@ 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.
|
// Returns the nearest station at or before date where motion changes from retrograde to prograde, 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 +270,8 @@ 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.
|
// Returns the nearest station at or after date where motion changes from retrograde to prograde, 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 +279,8 @@ 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.
|
// Returns the nearest eastern quadrature at or before 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 +288,8 @@ 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.
|
// Returns the nearest eastern quadrature at or after 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 +297,8 @@ 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.
|
// Returns the nearest western quadrature at or before 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 +306,8 @@ 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.
|
// Returns the nearest western quadrature at or after 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)
|
||||||
|
|||||||
+52
-52
@@ -207,8 +207,8 @@ 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.
|
// Returns the nearest conjunction with the Sun at or before 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 +216,8 @@ 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.
|
// Returns the nearest conjunction with the Sun at or after 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 +225,144 @@ 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.
|
// Returns the nearest inferior conjunction at or before 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.
|
// Returns the nearest inferior conjunction at or after 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.
|
// Returns the nearest superior conjunction at or before 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.
|
// Returns the nearest superior conjunction at or after 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.
|
// Returns the nearest stationary point at or before date, regardless of the direction change, 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.
|
// Returns the nearest stationary point at or after date, regardless of the direction change, 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.
|
// Returns the nearest station at or before date where motion changes from prograde to retrograde, 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.
|
// Returns the nearest station at or after date where motion changes from prograde to retrograde, 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.
|
// Returns the nearest station at or before date where motion changes from retrograde to prograde, 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.
|
// Returns the nearest station at or after date where motion changes from retrograde to prograde, 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.
|
// Returns the nearest greatest elongation at or before date, regardless of 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.
|
// Returns the nearest greatest elongation at or after date, regardless of 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.
|
// Returns the nearest eastern greatest elongation at or before 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.
|
// Returns the nearest eastern greatest elongation at or after 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.
|
// Returns the nearest western greatest elongation at or before 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.
|
// Returns the nearest western greatest elongation at or after 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