fix: 修正行星事件边界与留点计算

- 统一 UT 事件时刻与 TT 查询时刻的边界判断
- 将外行星留点搜索锚定到对应冲日周期
- 修正水星、金星合日、留、大距事件选择
- 统一七大行星视位置计算辅助逻辑
- 增加公开 Last/Next 边界和 JPL/NAOJ 基线回归测试
This commit is contained in:
兔子 2026-05-22 12:24:41 +08:00
parent d40c4dfcd9
commit 34ff6a36ae
Signed by: b612
GPG Key ID: 99DD2222B612B612
51 changed files with 33522 additions and 10994 deletions

View File

@ -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°330.25″ // apparent RA and Dec of the Sun RA: 18h43m34.82s Dec: -23°330.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°1918.57″ // topocentric apparent RA and Dec of the Moon
RA: 23h17m51.93s Dec: -10°1917.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°1926.25″ // declination of Vega in year 13600 84°1926.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
``` ```

152
README.md
View File

@ -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°330.25 赤经: 18h43m34.82s 赤纬: -23°330.27
人马座 人马座
方位角: 120.19483856399326 高度角: 2.4014324584398516 天顶距: 87.59856754156014 方位角: 120.19477090015224 高度角: 2.4014437419430097 天顶距: 87.59855625805699
0.9832929365443133 0.983292937163176
赤经: 23h17m53.15s 赤纬: -10°1918.57″
赤经: 23h17m51.93s 赤纬: -10°1917.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°1926.25″ // 织女一在公元 13600 年的赤纬 84°1926.13″ // 织女一在公元 13600 年的赤纬
天狼 Sirius -1.46 // 最亮恒星表第一项:中文名、英文常用名、视星等 天狼 Sirius -1.46 // 最亮恒星表第一项:中文名、英文常用名、视星等
``` ```

76
basic/event_boundary.go Normal file
View File

@ -0,0 +1,76 @@
package basic
import "math"
const exactEventTolerance = 2.0 / 86400.0
func sameEventJD(a, b float64) bool {
return math.Abs(a-b) <= exactEventTolerance
}
func sameEventUTQueryTT(eventUT, queryTT float64) bool {
return math.Abs(eventUTQueryTTDelta(eventUT, queryTT)) <= exactEventTolerance
}
func closestEventUTToQueryTT(queryTT, best float64, candidates ...float64) float64 {
bestAbs := math.Abs(eventUTQueryTTDelta(best, queryTT))
for _, candidate := range candidates {
candidateAbs := math.Abs(eventUTQueryTTDelta(candidate, queryTT))
if candidateAbs < bestAbs {
best = candidate
bestAbs = candidateAbs
}
}
return best
}
type phaseEventSearchFunc func(jde, degree float64, next uint8) float64
type simpleEventSearchFunc func(jde float64) float64
func inclusiveLastPhaseEvent(jde, degree float64, fn phaseEventSearchFunc) float64 {
last := fn(jde, degree, 0)
next := fn(jde, degree, 1)
if eventUTQueryBeforeOrEqual(next, jde) && eventUTQueryAfterOrEqual(next, jde) {
return next
}
if eventUTQueryBeforeOrEqual(last, jde) {
return last
}
return last
}
func inclusiveNextPhaseEvent(jde, degree float64, fn phaseEventSearchFunc) float64 {
last := fn(jde, degree, 0)
if eventUTQueryBeforeOrEqual(last, jde) && eventUTQueryAfterOrEqual(last, jde) {
return last
}
next := fn(jde, degree, 1)
if eventUTQueryAfterOrEqual(next, jde) {
return next
}
return next
}
func inclusiveLastSimpleEvent(jde float64, lastFn, nextFn simpleEventSearchFunc) float64 {
last := lastFn(jde)
next := nextFn(jde)
if eventUTQueryBeforeOrEqual(next, jde) && eventUTQueryAfterOrEqual(next, jde) {
return next
}
if eventUTQueryBeforeOrEqual(last, jde) {
return last
}
return last
}
func inclusiveNextSimpleEvent(jde float64, lastFn, nextFn simpleEventSearchFunc) float64 {
last := lastFn(jde)
if eventUTQueryBeforeOrEqual(last, jde) && eventUTQueryAfterOrEqual(last, jde) {
return last
}
next := nextFn(jde)
if eventUTQueryAfterOrEqual(next, jde) {
return next
}
return next
}

View File

@ -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

183
basic/inner_event_window.go Normal file
View File

@ -0,0 +1,183 @@
package basic
import "math"
const innerEventEpsilon = 4.0 / 86400.0
func eventQueryTTAsUT(queryTT float64) float64 {
return TD2UT(queryTT, false)
}
func eventUTQueryTTDelta(eventUT, queryTT float64) float64 {
return eventUT - eventQueryTTAsUT(queryTT)
}
func eventUTQueryBeforeOrEqual(eventUT, queryTT float64) bool {
return eventUTQueryTTDelta(eventUT, queryTT) <= innerEventEpsilon
}
func eventUTQueryAfterOrEqual(eventUT, queryTT float64) bool {
return eventUTQueryTTDelta(eventUT, queryTT) >= -innerEventEpsilon
}
func eventUTNextQueryTT(eventUT float64) float64 {
return TD2UT(eventUT, true) + 1.0
}
func eventUTLastQueryTT(eventUT float64) float64 {
return TD2UT(eventUT, true) - 1.0
}
func innerNextCycleOffset(delta, period float64) float64 {
if delta <= 0 {
return -delta * period / 360.0
}
return (360.0 - delta) * period / 360.0
}
func innerLastCycleOffset(delta, period float64) float64 {
if delta >= 0 {
return delta * period / 360.0
}
return (360.0 + delta) * period / 360.0
}
func clampFloat64(v, min, max float64) float64 {
if v < min {
return min
}
if v > max {
return max
}
return v
}
func scanWindowForMinAbs(start, end, step float64, fn func(float64) float64) float64 {
if end < start {
start, end = end, start
}
if step <= 0 || end == start {
return start
}
bestJD := start
bestAbs := math.Abs(fn(start))
for jd := start + step; jd < end; jd += step {
candidateAbs := math.Abs(fn(jd))
if candidateAbs < bestAbs {
bestAbs = candidateAbs
bestJD = jd
}
}
endAbs := math.Abs(fn(end))
if endAbs < bestAbs {
return end
}
return bestJD
}
func scanWindowForMax(start, end, step float64, fn func(float64) float64) float64 {
if end < start {
start, end = end, start
}
if step <= 0 || end == start {
return start
}
bestJD := start
bestVal := fn(start)
for jd := start + step; jd < end; jd += step {
candidateVal := fn(jd)
if candidateVal > bestVal {
bestVal = candidateVal
bestJD = jd
}
}
endVal := fn(end)
if endVal > bestVal {
return end
}
return bestJD
}
func boundedEventZeroRefine(seed, start, end, halfWindow, step float64, fn func(float64) float64) float64 {
if end < start {
start, end = end, start
}
if end <= start {
return start
}
maxHalfWindow := (end - start) / 2
if halfWindow > maxHalfWindow {
halfWindow = maxHalfWindow
}
if halfWindow <= 0 {
return clampFloat64(seed, start, end)
}
seed = clampFloat64(seed, start+halfWindow, end-halfWindow)
return eventZeroRefine(seed, halfWindow, step, fn)
}
func zeroEventInWindow(start, end, coarseStep, halfWindow, refineStep float64, coarseFn, exactFn func(float64) float64) float64 {
if end < start {
start, end = end, start
}
if end <= start {
return start
}
rangeDays := end - start
if coarseStep <= 0 || coarseStep > rangeDays {
coarseStep = rangeDays / 6.0
}
if coarseStep < 0.5 {
coarseStep = 0.5
}
if refineStep <= 0 {
refineStep = 0.5 / 86400.0
}
if halfWindow <= 0 {
halfWindow = coarseStep
}
guess := scanWindowForMinAbs(start, end, coarseStep, coarseFn)
return boundedEventZeroRefine(guess, start, end, halfWindow, refineStep, exactFn)
}
func maximizeInWindow(start, end, coarseStep float64, coarseFn, exactFn func(float64) float64) float64 {
if end < start {
start, end = end, start
}
if end <= start {
return start
}
rangeDays := end - start
if coarseStep <= 0 || coarseStep > rangeDays {
coarseStep = rangeDays / 6.0
}
if coarseStep < 0.5 {
coarseStep = 0.5
}
guess := scanWindowForMax(start, end, coarseStep, coarseFn)
left := clampFloat64(guess-coarseStep, start, end)
right := clampFloat64(guess+coarseStep, start, end)
if right-left <= innerEventEpsilon {
return guess
}
for i := 0; i < 20; i++ {
third := (right - left) / 3.0
leftThird := left + third
rightThird := right - third
if exactFn(leftThird) <= exactFn(rightThird) {
left = leftThird
continue
}
right = rightThird
}
bestJD := guess
bestVal := exactFn(bestJD)
for _, jd := range []float64{left, (left + right) / 2.0, right} {
candidateVal := exactFn(jd)
if candidateVal > bestVal {
bestVal = candidateVal
bestJD = jd
}
}
return bestJD
}

View File

@ -0,0 +1,45 @@
package basic
import "testing"
func TestInnerPlanetExactEventBoundaryIncludesCurrent(t *testing.T) {
cases := []struct {
name string
seed float64
lastFn func(float64) float64
nextFn func(float64) float64
}{
{name: "MercuryConjunction", seed: NextMercuryConjunction(ttjdUTC(2026, 1, 1, 0, 0, 0)), lastFn: LastMercuryConjunction, nextFn: NextMercuryConjunction},
{name: "MercuryInferior", seed: NextMercuryInferiorConjunctionInclusive(ttjdUTC(2026, 1, 1, 0, 0, 0)), lastFn: LastMercuryInferiorConjunctionInclusive, nextFn: NextMercuryInferiorConjunctionInclusive},
{name: "MercurySuperior", seed: NextMercurySuperiorConjunctionInclusive(ttjdUTC(2026, 1, 1, 0, 0, 0)), lastFn: LastMercurySuperiorConjunctionInclusive, nextFn: NextMercurySuperiorConjunctionInclusive},
{name: "MercuryRetrograde", seed: NextMercuryRetrogradeInclusive(ttjdUTC(2026, 1, 1, 0, 0, 0)), lastFn: LastMercuryRetrogradeInclusive, nextFn: NextMercuryRetrogradeInclusive},
{name: "MercuryP2R", seed: NextMercuryProgradeToRetrogradeInclusive(ttjdUTC(2026, 1, 1, 0, 0, 0)), lastFn: LastMercuryProgradeToRetrogradeInclusive, nextFn: NextMercuryProgradeToRetrogradeInclusive},
{name: "MercuryR2P", seed: NextMercuryRetrogradeToProgradeInclusive(ttjdUTC(2026, 1, 1, 0, 0, 0)), lastFn: LastMercuryRetrogradeToProgradeInclusive, nextFn: NextMercuryRetrogradeToProgradeInclusive},
{name: "MercuryGreatestElongation", seed: NextMercuryGreatestElongationInclusive(ttjdUTC(2026, 1, 1, 0, 0, 0)), lastFn: LastMercuryGreatestElongationInclusive, nextFn: NextMercuryGreatestElongationInclusive},
{name: "MercuryEastElongation", seed: NextMercuryGreatestElongationEastInclusive(ttjdUTC(2026, 1, 1, 0, 0, 0)), lastFn: LastMercuryGreatestElongationEastInclusive, nextFn: NextMercuryGreatestElongationEastInclusive},
{name: "MercuryWestElongation", seed: NextMercuryGreatestElongationWestInclusive(ttjdUTC(2026, 1, 1, 0, 0, 0)), lastFn: LastMercuryGreatestElongationWestInclusive, nextFn: NextMercuryGreatestElongationWestInclusive},
{name: "VenusConjunction", seed: NextVenusConjunction(ttjdUTC(2026, 1, 1, 0, 0, 0)), lastFn: LastVenusConjunction, nextFn: NextVenusConjunction},
{name: "VenusInferior", seed: NextVenusInferiorConjunctionInclusive(ttjdUTC(2026, 1, 1, 0, 0, 0)), lastFn: LastVenusInferiorConjunctionInclusive, nextFn: NextVenusInferiorConjunctionInclusive},
{name: "VenusSuperior", seed: NextVenusSuperiorConjunctionInclusive(ttjdUTC(2026, 1, 1, 0, 0, 0)), lastFn: LastVenusSuperiorConjunctionInclusive, nextFn: NextVenusSuperiorConjunctionInclusive},
{name: "VenusRetrograde", seed: NextVenusRetrogradeInclusive(ttjdUTC(2026, 1, 1, 0, 0, 0)), lastFn: LastVenusRetrogradeInclusive, nextFn: NextVenusRetrogradeInclusive},
{name: "VenusP2R", seed: NextVenusProgradeToRetrogradeInclusive(ttjdUTC(2026, 1, 1, 0, 0, 0)), lastFn: LastVenusProgradeToRetrogradeInclusive, nextFn: NextVenusProgradeToRetrogradeInclusive},
{name: "VenusR2P", seed: NextVenusRetrogradeToProgradeInclusive(ttjdUTC(2026, 1, 1, 0, 0, 0)), lastFn: LastVenusRetrogradeToProgradeInclusive, nextFn: NextVenusRetrogradeToProgradeInclusive},
{name: "VenusGreatestElongation", seed: NextVenusGreatestElongationInclusive(ttjdUTC(2026, 1, 1, 0, 0, 0)), lastFn: LastVenusGreatestElongationInclusive, nextFn: NextVenusGreatestElongationInclusive},
{name: "VenusEastElongation", seed: NextVenusGreatestElongationEastInclusive(ttjdUTC(2026, 1, 1, 0, 0, 0)), lastFn: LastVenusGreatestElongationEastInclusive, nextFn: NextVenusGreatestElongationEastInclusive},
{name: "VenusWestElongation", seed: NextVenusGreatestElongationWestInclusive(ttjdUTC(2026, 1, 1, 0, 0, 0)), lastFn: LastVenusGreatestElongationWestInclusive, nextFn: NextVenusGreatestElongationWestInclusive},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
queryTT := TD2UT(tc.seed, true)
last := tc.lastFn(queryTT)
next := tc.nextFn(queryTT)
if !sameEventJD(last, tc.seed) {
t.Fatalf("last exact boundary mismatch: got %.12f want %.12f", last, tc.seed)
}
if !sameEventJD(next, tc.seed) {
t.Fatalf("next exact boundary mismatch: got %.12f want %.12f", next, tc.seed)
}
})
}
}

View File

@ -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)
})
}
}

View File

@ -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 {

View File

@ -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 date
return jupiterRetrograde(oppositionJD-10, false)
} }
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 date
return jupiterRetrograde(oppositionJD-10, true)
} }
return date lastOppositionJD := jupiterConjunctionFull(jde, 180, 0)
return jupiterRetrogradeAroundOpposition(lastOppositionJD, true)
} }

View File

@ -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 {

View File

@ -116,40 +116,39 @@ func marsConjunction(jde, degree float64, next uint8) float64 {
} }
func LastMarsConjunction(jde float64) float64 { func LastMarsConjunction(jde float64) float64 {
return marsConjunction(jde, 0, 0) return inclusiveLastPhaseEvent(jde, 0, marsConjunction)
} }
func NextMarsConjunction(jde float64) float64 { func NextMarsConjunction(jde float64) float64 {
return marsConjunction(jde, 0, 1) return inclusiveNextPhaseEvent(jde, 0, marsConjunction)
} }
func LastMarsOpposition(jde float64) float64 { func LastMarsOpposition(jde float64) float64 {
return marsConjunction(jde, 180, 0) return inclusiveLastPhaseEvent(jde, 180, marsConjunction)
} }
func NextMarsOpposition(jde float64) float64 { func NextMarsOpposition(jde float64) float64 {
return marsConjunction(jde, 180, 1) return inclusiveNextPhaseEvent(jde, 180, marsConjunction)
} }
func NextMarsEasternQuadrature(jde float64) float64 { func NextMarsEasternQuadrature(jde float64) float64 {
return marsConjunction(jde, 90, 1) return inclusiveNextPhaseEvent(jde, 90, marsConjunction)
} }
func LastMarsEasternQuadrature(jde float64) float64 { func LastMarsEasternQuadrature(jde float64) float64 {
return marsConjunction(jde, 90, 0) return inclusiveLastPhaseEvent(jde, 90, marsConjunction)
} }
func NextMarsWesternQuadrature(jde float64) float64 { func NextMarsWesternQuadrature(jde float64) float64 {
return marsConjunction(jde, 270, 1) return inclusiveNextPhaseEvent(jde, 270, marsConjunction)
} }
func LastMarsWesternQuadrature(jde float64) float64 { func LastMarsWesternQuadrature(jde float64) float64 {
return marsConjunction(jde, 270, 0) return inclusiveLastPhaseEvent(jde, 270, marsConjunction)
} }
func marsRetrograde(jde float64, searchBeforeOpposition bool) float64 { func marsRetrogradeAroundOpposition(oppositionJD float64, searchBeforeOpposition bool) float64 {
//0=last 1=next jde := oppositionJD
jde = marsConjunctionFull(jde, 180, 1)
if searchBeforeOpposition { if searchBeforeOpposition {
jde -= 60 jde -= 60
} else { } else {
@ -179,40 +178,66 @@ func marsRetrograde(jde float64, searchBeforeOpposition bool) float64 {
return TD2UT(bestJD, false) return TD2UT(bestJD, false)
} }
func marsOppositionFromBefore(oppositionJD float64) float64 {
return marsConjunctionFull(eventUTLastQueryTT(oppositionJD), 180, 1)
}
func marsOppositionFromAfter(oppositionJD float64) float64 {
return marsConjunctionFull(eventUTNextQueryTT(oppositionJD), 180, 0)
}
func NextMarsRetrogradeToPrograde(jde float64) float64 { func NextMarsRetrogradeToPrograde(jde float64) float64 {
date := marsRetrograde(jde, false) lastOppositionJD := marsConjunctionFull(jde, 180, 0)
if date < jde { date := marsRetrogradeAroundOpposition(lastOppositionJD, false)
oppositionJD := marsConjunctionFull(jde, 180, 1) if sameEventUTQueryTT(date, jde) {
return marsRetrograde(oppositionJD+10, false) sameOppositionJD := marsOppositionFromBefore(lastOppositionJD)
return closestEventUTToQueryTT(jde, date, marsRetrogradeAroundOpposition(sameOppositionJD, false))
}
if !eventUTQueryAfterOrEqual(date, jde) {
nextOppositionJD := marsConjunctionFull(jde, 180, 1)
return marsRetrogradeAroundOpposition(nextOppositionJD, false)
} }
return date return date
} }
func LastMarsRetrogradeToPrograde(jde float64) float64 { func LastMarsRetrogradeToPrograde(jde float64) float64 {
jde = marsConjunctionFull(jde, 180, 0) - 10 lastOppositionJD := marsConjunctionFull(jde, 180, 0)
date := marsRetrograde(jde, false) date := marsRetrogradeAroundOpposition(lastOppositionJD, false)
if date > jde { if sameEventUTQueryTT(date, jde) {
oppositionJD := marsConjunctionFull(jde, 180, 0) sameOppositionJD := marsOppositionFromBefore(lastOppositionJD)
return marsRetrograde(oppositionJD-10, false) return closestEventUTToQueryTT(jde, date, marsRetrogradeAroundOpposition(sameOppositionJD, false))
}
if !eventUTQueryBeforeOrEqual(date, jde) {
previousOppositionJD := marsConjunctionFull(eventUTLastQueryTT(lastOppositionJD), 180, 0)
return marsRetrogradeAroundOpposition(previousOppositionJD, false)
} }
return date return date
} }
func NextMarsProgradeToRetrograde(jde float64) float64 { func NextMarsProgradeToRetrograde(jde float64) float64 {
date := marsRetrograde(jde, true) nextOppositionJD := marsConjunctionFull(jde, 180, 1)
if date < jde { date := marsRetrogradeAroundOpposition(nextOppositionJD, true)
oppositionJD := marsConjunctionFull(jde, 180, 1) if sameEventUTQueryTT(date, jde) {
return marsRetrograde(oppositionJD+10, true) sameOppositionJD := marsOppositionFromAfter(nextOppositionJD)
return closestEventUTToQueryTT(jde, date, marsRetrogradeAroundOpposition(sameOppositionJD, true))
}
if !eventUTQueryAfterOrEqual(date, jde) {
followingOppositionJD := marsConjunctionFull(eventUTNextQueryTT(nextOppositionJD), 180, 1)
return marsRetrogradeAroundOpposition(followingOppositionJD, true)
} }
return date return date
} }
func LastMarsProgradeToRetrograde(jde float64) float64 { func LastMarsProgradeToRetrograde(jde float64) float64 {
jde = marsConjunctionFull(jde, 180, 0) - 10 nextOppositionJD := marsConjunctionFull(jde, 180, 1)
date := marsRetrograde(jde, true) date := marsRetrogradeAroundOpposition(nextOppositionJD, true)
if date > jde { if sameEventUTQueryTT(date, jde) {
oppositionJD := marsConjunctionFull(jde, 180, 0) sameOppositionJD := marsOppositionFromAfter(nextOppositionJD)
return marsRetrograde(oppositionJD-10, true) return closestEventUTToQueryTT(jde, date, marsRetrogradeAroundOpposition(sameOppositionJD, true))
}
if !eventUTQueryBeforeOrEqual(date, jde) {
lastOppositionJD := marsConjunctionFull(jde, 180, 0)
return marsRetrogradeAroundOpposition(lastOppositionJD, true)
} }
return date return date
} }

View File

@ -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 {

View File

@ -11,6 +11,7 @@ const (
MERCURY_S_PERIOD = 1 / ((1 / 87.9691) - (1 / 365.256363004)) MERCURY_S_PERIOD = 1 / ((1 / 87.9691) - (1 / 365.256363004))
mercuryConjunctionDerivativeStepDay = 2e-5 * 36525.0 mercuryConjunctionDerivativeStepDay = 2e-5 * 36525.0
mercuryLightTimeDaysPerAU = 0.0057755183 mercuryLightTimeDaysPerAU = 0.0057755183
mercuryEventSearchN = 16
) )
type mercuryConjunctionLBR struct { type mercuryConjunctionLBR struct {
@ -206,41 +207,49 @@ func mercuryConjunction(jde float64, next uint8) float64 {
} }
func LastMercuryConjunction(jde float64) float64 { func LastMercuryConjunction(jde float64) float64 {
return mercuryConjunction(jde, 0) return inclusiveLastSimpleEvent(jde, LastMercuryConjunctionStrict, NextMercuryConjunctionStrict)
} }
func NextMercuryConjunction(jde float64) float64 { func NextMercuryConjunction(jde float64) float64 {
return inclusiveNextSimpleEvent(jde, LastMercuryConjunctionStrict, NextMercuryConjunctionStrict)
}
func LastMercuryConjunctionStrict(jde float64) float64 {
return mercuryConjunction(jde, 0)
}
func NextMercuryConjunctionStrict(jde float64) float64 {
return mercuryConjunction(jde, 1) return mercuryConjunction(jde, 1)
} }
func NextMercuryInferiorConjunction(jde float64) float64 { func NextMercuryInferiorConjunction(jde float64) float64 {
date := NextMercuryConjunction(jde) date := NextMercuryConjunctionStrict(jde)
if EarthMercuryAway(date) > EarthAway(date) { if EarthMercuryAway(date) > EarthAway(date) {
return NextMercuryConjunction(date + 2) return NextMercuryConjunctionStrict(date + 2)
} }
return date return date
} }
func NextMercurySuperiorConjunction(jde float64) float64 { func NextMercurySuperiorConjunction(jde float64) float64 {
date := NextMercuryConjunction(jde) date := NextMercuryConjunctionStrict(jde)
if EarthMercuryAway(date) < EarthAway(date) { if EarthMercuryAway(date) < EarthAway(date) {
return NextMercuryConjunction(date + 2) return NextMercuryConjunctionStrict(date + 2)
} }
return date return date
} }
func LastMercuryInferiorConjunction(jde float64) float64 { func LastMercuryInferiorConjunction(jde float64) float64 {
date := LastMercuryConjunction(jde) date := LastMercuryConjunctionStrict(jde)
if EarthMercuryAway(date) > EarthAway(date) { if EarthMercuryAway(date) > EarthAway(date) {
return LastMercuryConjunction(date - 2) return LastMercuryConjunctionStrict(date - 2)
} }
return date return date
} }
func LastMercurySuperiorConjunction(jde float64) float64 { func LastMercurySuperiorConjunction(jde float64) float64 {
date := LastMercuryConjunction(jde) date := LastMercuryConjunctionStrict(jde)
if EarthMercuryAway(date) < EarthAway(date) { if EarthMercuryAway(date) < EarthAway(date) {
return LastMercuryConjunction(date - 2) return LastMercuryConjunctionStrict(date - 2)
} }
return date return date
} }
@ -257,16 +266,6 @@ func mercuryRetrograde(jde float64) float64 {
} }
return sub return sub
} }
raRate := func(jde float64, delta float64) float64 {
sub := MercuryApparentRa(jde+delta) - MercuryApparentRa(jde-delta)
if sub > 180 {
sub -= 360
}
if sub < -180 {
sub += 360
}
return sub / (2 * delta)
}
lastConjunction := mercuryConjunctionLegacy(jde, 0) lastConjunction := mercuryConjunctionLegacy(jde, 0)
nextConjunction := mercuryConjunctionLegacy(jde, 1) nextConjunction := mercuryConjunctionLegacy(jde, 1)
currentRADelta := solarRADelta(jde) currentRADelta := solarRADelta(jde)
@ -276,7 +275,7 @@ func mercuryRetrograde(jde float64) float64 {
jde = lastConjunction + ((nextConjunction - lastConjunction) / 5.5) jde = lastConjunction + ((nextConjunction - lastConjunction) / 5.5)
} }
for { for {
currentRate := raRate(jde, 1.0/86400.0) currentRate := mercuryRADerivative(jde, 1.0/86400.0)
if math.Abs(currentRate) > 0.55 { if math.Abs(currentRate) > 0.55 {
jde += 2 jde += 2
continue continue
@ -286,82 +285,212 @@ func mercuryRetrograde(jde float64) float64 {
estimateJD := jde estimateJD := jde
for { for {
prevJD := estimateJD prevJD := estimateJD
rateValue := raRate(prevJD, 2.0/86400.0) rateValue := mercuryRADerivative(prevJD, 2.0/86400.0)
rateSlope := (raRate(prevJD+15.0/86400.0, 2.0/86400.0) - raRate(prevJD-15.0/86400.0, 2.0/86400.0)) / (30.0 / 86400.0) rateSlope := (mercuryRADerivative(prevJD+15.0/86400.0, 2.0/86400.0) - mercuryRADerivative(prevJD-15.0/86400.0, 2.0/86400.0)) / (30.0 / 86400.0)
estimateJD = prevJD - rateValue/rateSlope estimateJD = prevJD - rateValue/rateSlope
if math.Abs(estimateJD-prevJD) <= 30.0/86400.0 { if math.Abs(estimateJD-prevJD) <= 30.0/86400.0 {
break break
} }
} }
bestJD := eventZeroRefine(estimateJD, 15.0/86400.0, 0.5/86400.0, func(jd float64) float64 { bestJD := eventZeroRefine(estimateJD, 15.0/86400.0, 0.5/86400.0, func(jd float64) float64 {
return raRate(jd, 0.5/86400.0) return mercuryRADerivative(jd, 0.5/86400.0)
}) })
//fmt.Println((bestJD - lastConjunction) / (nextConjunction - lastConjunction)) //fmt.Println((bestJD - lastConjunction) / (nextConjunction - lastConjunction))
return TD2UT(bestJD, false) return TD2UT(bestJD, false)
} }
func mercuryRADerivative(jde, delta float64) float64 {
sub := MercuryApparentRa(jde+delta) - MercuryApparentRa(jde-delta)
if sub > 180 {
sub -= 360
}
if sub < -180 {
sub += 360
}
return sub / (2 * delta)
}
func mercuryStationIsProgradeToRetrograde(eventUT float64) bool {
for _, offset := range []float64{0.25, 0.5, 1.0} {
before := mercuryRADerivative(eventUT-offset, 0.5/86400.0)
after := mercuryRADerivative(eventUT+offset, 0.5/86400.0)
if before > 0 && after < 0 {
return true
}
if before < 0 && after > 0 {
return false
}
}
before := mercuryRADerivative(eventUT-0.25, 0.5/86400.0)
after := mercuryRADerivative(eventUT+0.25, 0.5/86400.0)
return before > after
}
func nextMercuryTypedStation(jde float64, progradeToRetrograde bool) float64 {
date := NextMercuryRetrogradeStrict(jde)
for mercuryStationIsProgradeToRetrograde(date) != progradeToRetrograde {
date = NextMercuryRetrogradeStrict(eventUTNextQueryTT(date))
}
return date
}
func lastMercuryTypedStation(jde float64, progradeToRetrograde bool) float64 {
date := LastMercuryRetrogradeStrict(jde)
for mercuryStationIsProgradeToRetrograde(date) != progradeToRetrograde {
date = LastMercuryRetrogradeStrict(eventUTLastQueryTT(date))
}
return date
}
func NextMercuryRetrograde(jde float64) float64 { func NextMercuryRetrograde(jde float64) float64 {
date := mercuryRetrograde(jde) date := mercuryRetrograde(jde)
if date < jde { if !eventUTQueryAfterOrEqual(date, jde) {
nextConjunction := mercuryConjunctionLegacy(jde, 1) nextConjunction := NextMercuryConjunctionStrict(jde)
return mercuryRetrograde(nextConjunction + 2) return mercuryRetrograde(nextConjunction + 2)
} }
return date return date
} }
func LastMercuryRetrograde(jde float64) float64 { func LastMercuryRetrograde(jde float64) float64 {
lastConjunction := mercuryConjunctionLegacy(jde, 0) lastConjunction := LastMercuryConjunctionStrict(jde)
date := mercuryRetrograde(lastConjunction + 2) date := mercuryRetrograde(lastConjunction + 2)
if date > jde { if !eventUTQueryBeforeOrEqual(date, jde) {
previousConjunction := mercuryConjunctionLegacy(lastConjunction-2, 0) previousConjunction := LastMercuryConjunctionStrict(eventUTLastQueryTT(lastConjunction))
return mercuryRetrograde(previousConjunction + 2) return mercuryRetrograde(previousConjunction + 2)
} }
return date return date
} }
func 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 + innerEventEpsilon, inferior - innerEventEpsilon
}
func mercuryWestElongationWindowEndingAt(superior float64) (float64, float64) {
lastInferior := LastMercuryInferiorConjunction(eventUTLastQueryTT(superior))
return lastInferior + innerEventEpsilon, superior - innerEventEpsilon
}
func mercuryEastElongationWindowContaining(jde float64) (float64, float64) {
nextInferior := NextMercuryInferiorConjunction(jde)
start, end := mercuryEastElongationWindowEndingAt(nextInferior)
if eventUTQueryBeforeOrEqual(start, jde) {
return start, end
}
currentInferior := LastMercuryInferiorConjunction(jde)
return mercuryEastElongationWindowEndingAt(currentInferior)
}
func mercuryWestElongationWindowContaining(jde float64) (float64, float64) {
nextSuperior := NextMercurySuperiorConjunction(jde)
start, end := mercuryWestElongationWindowEndingAt(nextSuperior)
if eventUTQueryBeforeOrEqual(start, jde) {
return start, end
}
currentSuperior := LastMercurySuperiorConjunction(jde)
return mercuryWestElongationWindowEndingAt(currentSuperior)
}
func nextMercuryGreatestElongationTyped(jde float64, east bool) float64 {
if east {
start, windowEnd := mercuryEastElongationWindowContaining(jde)
for {
date := mercuryGreatestElongationInWindow(start, windowEnd)
if eventUTQueryAfterOrEqual(date, jde) {
return date
}
nextInferior := NextMercuryInferiorConjunction(eventUTNextQueryTT(windowEnd))
start, windowEnd = mercuryEastElongationWindowEndingAt(nextInferior)
}
}
start, windowEnd := mercuryWestElongationWindowContaining(jde)
for {
date := mercuryGreatestElongationInWindow(start, windowEnd)
if eventUTQueryAfterOrEqual(date, jde) {
return date
}
nextSuperior := NextMercurySuperiorConjunction(eventUTNextQueryTT(windowEnd))
start, windowEnd = mercuryWestElongationWindowEndingAt(nextSuperior)
}
}
func lastMercuryGreatestElongationTyped(jde float64, east bool) float64 {
if east {
start, windowEnd := mercuryEastElongationWindowContaining(jde)
for {
date := mercuryGreatestElongationInWindow(start, windowEnd)
if eventUTQueryBeforeOrEqual(date, jde) {
return date
}
prevInferior := LastMercuryInferiorConjunction(eventUTLastQueryTT(start))
start, windowEnd = mercuryEastElongationWindowEndingAt(prevInferior)
}
}
start, windowEnd := mercuryWestElongationWindowContaining(jde)
for {
date := mercuryGreatestElongationInWindow(start, windowEnd)
if eventUTQueryBeforeOrEqual(date, jde) {
return date
}
prevSuperior := LastMercurySuperiorConjunction(eventUTLastQueryTT(start))
start, windowEnd = mercuryWestElongationWindowEndingAt(prevSuperior)
}
}
func mercuryGreatestElongation(jde float64) float64 { func mercuryGreatestElongation(jde float64) float64 {
solarRADelta := func(jde float64) float64 { solarRADelta := func(jde float64) float64 {
sub := Limit360(MercuryApparentRa(jde) - SunApparentRa(jde)) sub := Limit360(MercuryApparentRa(jde) - SunApparentRa(jde))
@ -383,8 +512,8 @@ func mercuryGreatestElongation(jde float64) float64 {
} }
return sub / (2 * delta) return sub / (2 * delta)
} }
lastConjunction := mercuryConjunctionLegacy(jde, 0) lastConjunction := LastMercuryConjunctionStrict(jde)
nextConjunction := mercuryConjunctionLegacy(jde, 1) nextConjunction := NextMercuryConjunctionStrict(jde)
currentRADelta := solarRADelta(jde) currentRADelta := solarRADelta(jde)
if currentRADelta > 0 { if currentRADelta > 0 {
jde = lastConjunction + ((nextConjunction - lastConjunction) / 5.0 * 2.0) jde = lastConjunction + ((nextConjunction - lastConjunction) / 5.0 * 2.0)
@ -417,56 +546,105 @@ func mercuryGreatestElongation(jde float64) float64 {
} }
func NextMercuryGreatestElongation(jde float64) float64 { func NextMercuryGreatestElongation(jde float64) float64 {
date := mercuryGreatestElongation(jde) east := NextMercuryGreatestElongationEast(jde)
if date < jde { west := NextMercuryGreatestElongationWest(jde)
nextConjunction := mercuryConjunctionLegacy(jde, 1) if sameEventJD(east, west) {
return mercuryGreatestElongation(nextConjunction + 2) return east
} }
return date if east < west {
return east
}
return west
} }
func LastMercuryGreatestElongation(jde float64) float64 { func LastMercuryGreatestElongation(jde float64) float64 {
lastConjunction := mercuryConjunctionLegacy(jde, 0) east := LastMercuryGreatestElongationEast(jde)
date := mercuryGreatestElongation(lastConjunction + 2) west := LastMercuryGreatestElongationWest(jde)
if date > jde { if sameEventJD(east, west) {
previousConjunction := mercuryConjunctionLegacy(lastConjunction-2, 0) return east
return mercuryGreatestElongation(previousConjunction + 2)
} }
return date if east > west {
return east
}
return west
}
func LastMercuryInferiorConjunctionInclusive(jde float64) float64 {
return inclusiveLastSimpleEvent(jde, LastMercuryInferiorConjunction, NextMercuryInferiorConjunction)
}
func NextMercuryInferiorConjunctionInclusive(jde float64) float64 {
return inclusiveNextSimpleEvent(jde, LastMercuryInferiorConjunction, NextMercuryInferiorConjunction)
}
func LastMercurySuperiorConjunctionInclusive(jde float64) float64 {
return inclusiveLastSimpleEvent(jde, LastMercurySuperiorConjunction, NextMercurySuperiorConjunction)
}
func NextMercurySuperiorConjunctionInclusive(jde float64) float64 {
return inclusiveNextSimpleEvent(jde, LastMercurySuperiorConjunction, NextMercurySuperiorConjunction)
}
func LastMercuryRetrogradeInclusive(jde float64) float64 {
return inclusiveLastSimpleEvent(jde, LastMercuryRetrograde, NextMercuryRetrograde)
}
func NextMercuryRetrogradeInclusive(jde float64) float64 {
return inclusiveNextSimpleEvent(jde, LastMercuryRetrograde, NextMercuryRetrograde)
}
func LastMercuryProgradeToRetrogradeInclusive(jde float64) float64 {
return inclusiveLastSimpleEvent(jde, LastMercuryProgradeToRetrograde, NextMercuryProgradeToRetrograde)
}
func NextMercuryProgradeToRetrogradeInclusive(jde float64) float64 {
return inclusiveNextSimpleEvent(jde, LastMercuryProgradeToRetrograde, NextMercuryProgradeToRetrograde)
}
func LastMercuryRetrogradeToProgradeInclusive(jde float64) float64 {
return inclusiveLastSimpleEvent(jde, LastMercuryRetrogradeToPrograde, NextMercuryRetrogradeToPrograde)
}
func NextMercuryRetrogradeToProgradeInclusive(jde float64) float64 {
return inclusiveNextSimpleEvent(jde, LastMercuryRetrogradeToPrograde, NextMercuryRetrogradeToPrograde)
}
func LastMercuryGreatestElongationInclusive(jde float64) float64 {
return inclusiveLastSimpleEvent(jde, LastMercuryGreatestElongation, NextMercuryGreatestElongation)
}
func NextMercuryGreatestElongationInclusive(jde float64) float64 {
return inclusiveNextSimpleEvent(jde, LastMercuryGreatestElongation, NextMercuryGreatestElongation)
}
func LastMercuryGreatestElongationEastInclusive(jde float64) float64 {
return inclusiveLastSimpleEvent(jde, LastMercuryGreatestElongationEast, NextMercuryGreatestElongationEast)
}
func NextMercuryGreatestElongationEastInclusive(jde float64) float64 {
return inclusiveNextSimpleEvent(jde, LastMercuryGreatestElongationEast, NextMercuryGreatestElongationEast)
}
func LastMercuryGreatestElongationWestInclusive(jde float64) float64 {
return inclusiveLastSimpleEvent(jde, LastMercuryGreatestElongationWest, NextMercuryGreatestElongationWest)
}
func NextMercuryGreatestElongationWestInclusive(jde float64) float64 {
return inclusiveNextSimpleEvent(jde, LastMercuryGreatestElongationWest, NextMercuryGreatestElongationWest)
} }
func NextMercuryGreatestElongationEast(jde float64) float64 { func NextMercuryGreatestElongationEast(jde float64) float64 {
date := NextMercuryGreatestElongation(jde) return nextMercuryGreatestElongationTyped(jde, true)
sub := Limit360(MercuryApparentRa(date) - SunApparentRa(date))
if sub > 180 {
return NextMercuryGreatestElongation(date + 1)
}
return date
} }
func NextMercuryGreatestElongationWest(jde float64) float64 { func NextMercuryGreatestElongationWest(jde float64) float64 {
date := NextMercuryGreatestElongation(jde) return nextMercuryGreatestElongationTyped(jde, false)
sub := Limit360(MercuryApparentRa(date) - SunApparentRa(date))
if sub < 180 {
return NextMercuryGreatestElongation(date + 1)
}
return date
} }
func LastMercuryGreatestElongationEast(jde float64) float64 { func LastMercuryGreatestElongationEast(jde float64) float64 {
date := LastMercuryGreatestElongation(jde) return lastMercuryGreatestElongationTyped(jde, true)
sub := Limit360(MercuryApparentRa(date) - SunApparentRa(date))
if sub > 180 {
return LastMercuryGreatestElongation(date - 1)
}
return date
} }
func LastMercuryGreatestElongationWest(jde float64) float64 { func LastMercuryGreatestElongationWest(jde float64) float64 {
date := LastMercuryGreatestElongation(jde) return lastMercuryGreatestElongationTyped(jde, false)
sub := Limit360(MercuryApparentRa(date) - SunApparentRa(date))
if sub < 180 {
return LastMercuryGreatestElongation(date - 1)
}
return date
} }

View File

@ -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))
}
}

View File

@ -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 {

View File

@ -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 date
return neptuneRetrograde(oppositionJD-10, false)
} }
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 date
return neptuneRetrograde(oppositionJD-10, true)
} }
return date lastOppositionJD := neptuneConjunctionFull(jde, 180, 0)
return neptuneRetrogradeAroundOpposition(lastOppositionJD, true)
} }

View File

@ -0,0 +1,55 @@
package basic
import (
"testing"
"time"
)
func TestOuterPlanetExactEventBoundaryIncludesCurrent(t *testing.T) {
cases := []struct {
name string
seed float64
lastFn func(float64) float64
nextFn func(float64) float64
}{
{name: "JupiterConjunction", seed: NextJupiterConjunction(ttjdUTC(2026, 1, 1, 0, 0, 0)), lastFn: LastJupiterConjunction, nextFn: NextJupiterConjunction},
{name: "JupiterOpposition", seed: NextJupiterOpposition(ttjdUTC(2026, 1, 1, 0, 0, 0)), lastFn: LastJupiterOpposition, nextFn: NextJupiterOpposition},
{name: "JupiterEasternQuadrature", seed: NextJupiterEasternQuadrature(ttjdUTC(2026, 1, 1, 0, 0, 0)), lastFn: LastJupiterEasternQuadrature, nextFn: NextJupiterEasternQuadrature},
{name: "JupiterWesternQuadrature", seed: NextJupiterWesternQuadrature(ttjdUTC(2026, 1, 1, 0, 0, 0)), lastFn: LastJupiterWesternQuadrature, nextFn: NextJupiterWesternQuadrature},
{name: "JupiterP2R", seed: NextJupiterProgradeToRetrograde(ttjdUTC(2026, 1, 1, 0, 0, 0)), lastFn: LastJupiterProgradeToRetrograde, nextFn: NextJupiterProgradeToRetrograde},
{name: "JupiterR2P", seed: NextJupiterRetrogradeToPrograde(ttjdUTC(2026, 1, 1, 0, 0, 0)), lastFn: LastJupiterRetrogradeToPrograde, nextFn: NextJupiterRetrogradeToPrograde},
{name: "SaturnOpposition", seed: NextSaturnOpposition(ttjdUTC(2025, 1, 1, 0, 0, 0)), lastFn: LastSaturnOpposition, nextFn: NextSaturnOpposition},
{name: "SaturnP2R", seed: NextSaturnProgradeToRetrograde(ttjdUTC(2025, 1, 1, 0, 0, 0)), lastFn: LastSaturnProgradeToRetrograde, nextFn: NextSaturnProgradeToRetrograde},
{name: "SaturnR2P", seed: NextSaturnRetrogradeToPrograde(ttjdUTC(2025, 1, 1, 0, 0, 0)), lastFn: LastSaturnRetrogradeToPrograde, nextFn: NextSaturnRetrogradeToPrograde},
{name: "UranusOpposition", seed: NextUranusOpposition(ttjdUTC(2025, 1, 1, 0, 0, 0)), lastFn: LastUranusOpposition, nextFn: NextUranusOpposition},
{name: "UranusP2R", seed: NextUranusProgradeToRetrograde(ttjdUTC(2025, 1, 1, 0, 0, 0)), lastFn: LastUranusProgradeToRetrograde, nextFn: NextUranusProgradeToRetrograde},
{name: "UranusR2P", seed: NextUranusRetrogradeToPrograde(ttjdUTC(2025, 1, 1, 0, 0, 0)), lastFn: LastUranusRetrogradeToPrograde, nextFn: NextUranusRetrogradeToPrograde},
{name: "NeptuneOpposition", seed: NextNeptuneOpposition(ttjdUTC(2026, 1, 1, 0, 0, 0)), lastFn: LastNeptuneOpposition, nextFn: NextNeptuneOpposition},
{name: "NeptuneP2R", seed: NextNeptuneProgradeToRetrograde(ttjdUTC(2026, 1, 1, 0, 0, 0)), lastFn: LastNeptuneProgradeToRetrograde, nextFn: NextNeptuneProgradeToRetrograde},
{name: "NeptuneR2P", seed: NextNeptuneRetrogradeToPrograde(ttjdUTC(2026, 1, 1, 0, 0, 0)), lastFn: LastNeptuneRetrogradeToPrograde, nextFn: NextNeptuneRetrogradeToPrograde},
{name: "MarsConjunction", seed: NextMarsConjunction(ttjdUTC(2026, 1, 1, 0, 0, 0)), lastFn: LastMarsConjunction, nextFn: NextMarsConjunction},
{name: "MarsOpposition", seed: NextMarsOpposition(ttjdUTC(2026, 1, 1, 0, 0, 0)), lastFn: LastMarsOpposition, nextFn: NextMarsOpposition},
{name: "MarsEasternQuadrature", seed: NextMarsEasternQuadrature(ttjdUTC(2026, 1, 1, 0, 0, 0)), lastFn: LastMarsEasternQuadrature, nextFn: NextMarsEasternQuadrature},
{name: "MarsWesternQuadrature", seed: NextMarsWesternQuadrature(ttjdUTC(2026, 1, 1, 0, 0, 0)), lastFn: LastMarsWesternQuadrature, nextFn: NextMarsWesternQuadrature},
{name: "MarsP2R", seed: NextMarsProgradeToRetrograde(ttjdUTC(2026, 1, 1, 0, 0, 0)), lastFn: LastMarsProgradeToRetrograde, nextFn: NextMarsProgradeToRetrograde},
{name: "MarsR2P", seed: NextMarsRetrogradeToPrograde(ttjdUTC(2026, 1, 1, 0, 0, 0)), lastFn: LastMarsRetrogradeToPrograde, nextFn: NextMarsRetrogradeToPrograde},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
queryTT := TD2UT(tc.seed, true)
last := tc.lastFn(queryTT)
next := tc.nextFn(queryTT)
if !sameEventJD(last, tc.seed) {
t.Fatalf("last exact boundary mismatch: got %.12f want %.12f", last, tc.seed)
}
if !sameEventJD(next, tc.seed) {
t.Fatalf("next exact boundary mismatch: got %.12f want %.12f", next, tc.seed)
}
})
}
}
func ttjdUTC(year, month, day, hour, min, sec int) float64 {
return TD2UT(Date2JDE(time.Date(year, time.Month(month), day, hour, min, sec, 0, time.UTC)), true)
}

View File

@ -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")
}
}

84
basic/planet_apparent.go Normal file
View File

@ -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)
}

View File

@ -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)
}
}

View File

@ -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 {

View File

@ -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 {

View File

@ -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 date
return saturnRetrograde(oppositionJD-10, false)
} }
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 date
return saturnRetrograde(oppositionJD-10, true)
} }
return date lastOppositionJD := saturnConjunctionFull(jde, 180, 0)
return saturnRetrogradeAroundOpposition(lastOppositionJD, true)
} }

207
basic/station_truth_test.go Normal file
View File

@ -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
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -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
}
]

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -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 {

View File

@ -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 date
return uranusRetrograde(oppositionJD-10, false)
} }
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 date
return uranusRetrograde(oppositionJD-10, true)
} }
return date lastOppositionJD := uranusConjunctionFull(jde, 180, 0)
return uranusRetrogradeAroundOpposition(lastOppositionJD, true)
} }

View File

@ -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 {

View File

@ -3,6 +3,7 @@ package basic
import ( import (
"math" "math"
"b612.me/astro/planet"
. "b612.me/astro/tools" . "b612.me/astro/tools"
) )
@ -33,6 +34,23 @@ func venusSunLongitudeDeltaN(jde float64, n int) float64 {
return sub return sub
} }
func venusConjunctionAngleDelta(diff float64) float64 {
diff = Limit360(diff)
if diff > 180 {
diff -= 360
}
if diff < -180 {
diff += 360
}
return diff
}
func venusConjunctionHeliocentricDelta(jd, targetDeg float64, n int) float64 {
planetLo := planet.WherePlanetN(2, 0, jd, n)
earthLo := planet.WherePlanetN(-1, 0, jd, n)
return venusConjunctionAngleDelta(planetLo - earthLo - targetDeg)
}
func venusSunRADelta(jde float64) float64 { func venusSunRADelta(jde float64) float64 {
sub := Limit360(VenusApparentRa(jde) - SunApparentRa(jde)) sub := Limit360(VenusApparentRa(jde) - SunApparentRa(jde))
if sub > 180 { if sub > 180 {
@ -66,13 +84,94 @@ func venusRADerivativeN(jde, val float64, n int) float64 {
return sub / (2 * val) return sub / (2 * val)
} }
func venusRAContinuousForMax(jde float64) float64 {
ra := VenusApparentRa(jde)
if ra < 180 {
return ra + 360
}
return ra
}
func venusRAContinuousForMaxN(jde float64, n int) float64 {
ra := VenusApparentRaN(jde, n)
if ra < 180 {
return ra + 360
}
return ra
}
func venusRAContinuousForMin(jde float64) float64 {
ra := VenusApparentRa(jde)
if ra > 180 {
return ra - 360
}
return ra
}
func venusRAContinuousForMinN(jde float64, n int) float64 {
ra := VenusApparentRaN(jde, n)
if ra > 180 {
return ra - 360
}
return ra
}
func venusRAExtremumRefine(seed, start, end, step float64, fn func(float64) float64) float64 {
centerJD := clampFloat64(seed, start, end)
halfStep := step
bestJD := centerJD
bestVal := fn(centerJD)
for i := 0; i < 8; i++ {
leftJD := clampFloat64(centerJD-halfStep, start, end)
rightJD := clampFloat64(centerJD+halfStep, start, end)
leftVal := fn(leftJD)
centerVal := fn(centerJD)
rightVal := fn(rightJD)
if leftVal > bestVal {
bestVal = leftVal
bestJD = leftJD
}
if centerVal > bestVal {
bestVal = centerVal
bestJD = centerJD
}
if rightVal > bestVal {
bestVal = rightVal
bestJD = rightJD
}
denominator := leftVal - 2*centerVal + rightVal
if denominator == 0 {
centerJD = bestJD
halfStep /= 2
continue
}
vertexJD := centerJD + 0.5*halfStep*(leftVal-rightVal)/denominator
vertexJD = clampFloat64(vertexJD, leftJD, rightJD)
vertexVal := fn(vertexJD)
if vertexVal > bestVal {
bestVal = vertexVal
bestJD = vertexJD
}
centerJD = bestJD
halfStep /= 2
}
return bestJD
}
func venusSunElongationN(jde float64, n int) float64 { func venusSunElongationN(jde float64, n int) float64 {
lo1, bo1 := VenusApparentLoBoN(jde, n) lo1, bo1 := VenusApparentLoBoN(jde, n)
lo2 := SunApparentLo(jde) lo2 := HSunApparentLoN(jde, n)
bo2 := HSunTrueBoN(jde, n) bo2 := HSunTrueBoN(jde, n)
return StarAngularSeparation(lo1, bo1, lo2, bo2) return StarAngularSeparation(lo1, bo1, lo2, bo2)
} }
func venusTrueElongationN(jde float64, n int) float64 {
earth := mercuryHelioN(-1, jde, n)
planetPos := mercuryHelioN(2, jde, n)
geo := mercuryGeocentric(planetPos, earth)
return StarAngularSeparation(geo.lo, geo.bo, HSunTrueLoN(jde, n), HSunTrueBoN(jde, n))
}
func venusElongationDerivative(jde, val float64) float64 { func venusElongationDerivative(jde, val float64) float64 {
sub := VenusSunElongation(jde+val) - VenusSunElongation(jde-val) sub := VenusSunElongation(jde+val) - VenusSunElongation(jde-val)
if sub > 180 { if sub > 180 {
@ -96,91 +195,98 @@ func venusElongationDerivativeN(jde, val float64, n int) float64 {
} }
func venusConjunction(jde float64, next uint8) float64 { func venusConjunction(jde float64, next uint8) float64 {
//0=last 1=next queryTT := jde
nowSub := venusSunLongitudeDeltaN(jde, venusEventSearchN) direction := -1.0
pos := math.Abs(venusSunLongitudeDeltaN(jde+1/86400.0, venusEventSearchN)) - math.Abs(nowSub) if next == 1 {
if pos >= 0 && next == 1 && nowSub > 0 { direction = 1
jde += VENUS_S_PERIOD/8.0 + 2
} }
if pos >= 0 && next == 1 && nowSub < 0 { left := queryTT
jde += VENUS_S_PERIOD/6.0 + 2 leftVal := venusSunLongitudeDeltaN(left, venusEventSearchN)
} if math.Abs(leftVal) <= 30.0/86400.0 {
if pos <= 0 && next == 0 && nowSub < 0 { exact := eventZeroRefine(left, 1.0, 0.000005, venusSunLongitudeDelta)
jde -= VENUS_S_PERIOD/8.0 + 2 if math.Abs(exact-queryTT) <= 1.0 {
} return TD2UT(exact, false)
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 {
jde += 8
} else {
jde -= 8
}
continue
}
break
}
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 +319,317 @@ func venusRetrograde(jde float64) float64 {
} }
func NextVenusRetrograde(jde float64) float64 { func NextVenusRetrograde(jde float64) float64 {
date := venusRetrograde(jde) p2r := NextVenusProgradeToRetrograde(jde)
if date < jde { r2p := NextVenusRetrogradeToPrograde(jde)
nextHe := NextVenusConjunction(jde) if sameEventJD(p2r, r2p) {
return venusRetrograde(nextHe + 2) return p2r
} }
return date if p2r < r2p {
return p2r
}
return r2p
} }
func LastVenusRetrograde(jde float64) float64 { func LastVenusRetrograde(jde float64) float64 {
lastHe := LastVenusConjunction(jde) p2r := LastVenusProgradeToRetrograde(jde)
date := venusRetrograde(lastHe + 2) r2p := LastVenusRetrogradeToPrograde(jde)
if date > jde { if sameEventJD(p2r, r2p) {
lastLastHe := LastVenusConjunction(lastHe - 2) return p2r
return venusRetrograde(lastLastHe + 2)
} }
return date if p2r > r2p {
return p2r
}
return r2p
}
func venusStationInWindow(start, end float64, progradeToRetrograde bool) float64 {
var best float64
if progradeToRetrograde {
guess := scanWindowForMax(start, end, 2.0, func(jd float64) float64 {
return venusRAContinuousForMaxN(jd, venusEventSearchN)
})
best = venusRAExtremumRefine(guess, start, end, 1.0, func(jd float64) float64 {
return venusRAContinuousForMax(jd)
})
} else {
guess := scanWindowForMax(start, end, 2.0, func(jd float64) float64 {
return -venusRAContinuousForMinN(jd, venusEventSearchN)
})
best = venusRAExtremumRefine(guess, start, end, 1.0, func(jd float64) float64 {
return -venusRAContinuousForMin(jd)
})
}
return TD2UT(best, false)
}
func venusProgradeToRetrogradeAroundInferior(inferior float64) float64 {
return venusStationInWindow(inferior-30.0, inferior-14.0, true)
}
func venusRetrogradeToProgradeAroundInferior(inferior float64) float64 {
return venusStationInWindow(inferior+14.0, inferior+24.0, false)
} }
func NextVenusProgradeToRetrograde(jde float64) float64 { func NextVenusProgradeToRetrograde(jde float64) float64 {
date := NextVenusRetrograde(jde) inferior := NextVenusInferiorConjunction(jde)
sub := Limit360(VenusApparentRa(date) - SunApparentRa(date)) for {
if sub > 180 { date := venusProgradeToRetrogradeAroundInferior(inferior)
return NextVenusRetrograde(date + VENUS_S_PERIOD/2) if eventUTQueryAfterOrEqual(date, jde) {
return date
}
inferior = NextVenusInferiorConjunction(eventUTNextQueryTT(inferior))
} }
return date
} }
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
}
inferior = NextVenusInferiorConjunction(eventUTNextQueryTT(inferior))
} }
return date
} }
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
}
inferior = LastVenusInferiorConjunction(eventUTLastQueryTT(inferior))
} }
return date
} }
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
}
inferior = LastVenusInferiorConjunction(eventUTLastQueryTT(inferior))
} }
return date
} }
func VenusSunElongation(jde float64) float64 { func VenusSunElongation(jde float64) float64 {
lo1, bo1 := VenusApparentLoBo(jde) lo1, bo1 := VenusApparentLoBo(jde)
lo2 := SunApparentLo(jde) lo2 := HSunApparentLo(jde)
bo2 := HSunTrueBo(jde) bo2 := HSunTrueBo(jde)
return StarAngularSeparation(lo1, bo1, lo2, bo2) return StarAngularSeparation(lo1, bo1, lo2, bo2)
} }
func venusGreatestElongation(jde float64) float64 { func venusGreatestElongationInWindow(start, end float64) float64 {
lastHe := LastVenusConjunction(jde) best := maximizeInWindow(start, end, 5.0, func(jd float64) float64 {
nextHe := NextVenusConjunction(jde) return venusTrueElongationN(jd, venusEventSearchN)
nowSub := venusSunRADelta(jde) }, func(jd float64) float64 {
if nowSub > 0 { return venusTrueElongationN(jd, -1)
jde = lastHe + ((nextHe - lastHe) / 5.0 * 2.5)
} else {
jde = lastHe + ((nextHe - lastHe) / 5.0)
}
for {
nowSub := venusElongationDerivativeN(jde, 1.0/86400.0, venusEventSearchN)
if math.Abs(nowSub) > 0.15 {
jde += 5
continue
}
break
}
JD1 := jde
for {
JD0 := JD1
stDegree := venusElongationDerivative(JD0, 2.0/86400.0)
stDegreep := (venusElongationDerivative(JD0+15.0/86400.0, 2.0/86400.0) - venusElongationDerivative(JD0-15.0/86400.0, 2.0/86400.0)) / (30.0 / 86400.0)
JD1 = JD0 - stDegree/stDegreep
if math.Abs(JD1-JD0) <= 30.0/86400.0 {
break
}
}
min := eventZeroRefine(JD1, 15.0/86400.0, 0.5/86400.0, func(jd float64) float64 {
return venusElongationDerivative(jd, 0.5/86400.0)
}) })
//fmt.Println((min - lastHe) / (nextHe - lastHe)) return TD2UT(best, false)
return TD2UT(min, false) }
func venusEastElongationWindowEndingAt(inferior float64) (float64, float64) {
lastSuperior := LastVenusSuperiorConjunction(eventUTLastQueryTT(inferior))
return lastSuperior + innerEventEpsilon, inferior - innerEventEpsilon
}
func venusWestElongationWindowEndingAt(superior float64) (float64, float64) {
lastInferior := LastVenusInferiorConjunction(eventUTLastQueryTT(superior))
return lastInferior + innerEventEpsilon, superior - innerEventEpsilon
}
func venusEastElongationWindowContaining(jde float64) (float64, float64) {
nextInferior := NextVenusInferiorConjunction(jde)
start, end := venusEastElongationWindowEndingAt(nextInferior)
if eventUTQueryBeforeOrEqual(start, jde) && eventUTQueryAfterOrEqual(end, jde) {
return start, end
}
currentInferior := LastVenusInferiorConjunction(jde)
return venusEastElongationWindowEndingAt(currentInferior)
}
func venusWestElongationWindowContaining(jde float64) (float64, float64) {
nextSuperior := NextVenusSuperiorConjunction(jde)
start, end := venusWestElongationWindowEndingAt(nextSuperior)
if eventUTQueryBeforeOrEqual(start, jde) && eventUTQueryAfterOrEqual(end, jde) {
return start, end
}
currentSuperior := LastVenusSuperiorConjunction(jde)
return venusWestElongationWindowEndingAt(currentSuperior)
}
func nextVenusGreatestElongationTyped(jde float64, east bool) float64 {
if east {
start, windowEnd := venusEastElongationWindowContaining(jde)
for {
date := venusGreatestElongationInWindow(start, windowEnd)
if eventUTQueryAfterOrEqual(date, jde) {
return date
}
nextInferior := NextVenusInferiorConjunction(eventUTNextQueryTT(windowEnd))
start, windowEnd = venusEastElongationWindowEndingAt(nextInferior)
}
}
start, windowEnd := venusWestElongationWindowContaining(jde)
for {
date := venusGreatestElongationInWindow(start, windowEnd)
if eventUTQueryAfterOrEqual(date, jde) {
return date
}
nextSuperior := NextVenusSuperiorConjunction(eventUTNextQueryTT(windowEnd))
start, windowEnd = venusWestElongationWindowEndingAt(nextSuperior)
}
}
func lastVenusGreatestElongationTyped(jde float64, east bool) float64 {
if east {
start, windowEnd := venusEastElongationWindowContaining(jde)
for {
date := venusGreatestElongationInWindow(start, windowEnd)
if eventUTQueryBeforeOrEqual(date, jde) {
return date
}
prevInferior := LastVenusInferiorConjunction(eventUTLastQueryTT(start))
start, windowEnd = venusEastElongationWindowEndingAt(prevInferior)
}
}
start, windowEnd := venusWestElongationWindowContaining(jde)
for {
date := venusGreatestElongationInWindow(start, windowEnd)
if eventUTQueryBeforeOrEqual(date, jde) {
return date
}
prevSuperior := LastVenusSuperiorConjunction(eventUTLastQueryTT(start))
start, windowEnd = venusWestElongationWindowEndingAt(prevSuperior)
}
}
func venusGreatestElongation(jde float64) float64 {
east := venusSunRADelta(jde) > 0
if east {
return nextVenusGreatestElongationTyped(jde, true)
}
return nextVenusGreatestElongationTyped(jde, false)
} }
func NextVenusGreatestElongation(jde float64) float64 { func NextVenusGreatestElongation(jde float64) float64 {
date := venusGreatestElongation(jde) east := NextVenusGreatestElongationEast(jde)
if date < jde { west := NextVenusGreatestElongationWest(jde)
nextHe := NextVenusConjunction(jde) if sameEventJD(east, west) {
return venusGreatestElongation(nextHe + 2) return east
} }
return date if east < west {
return east
}
return west
} }
func LastVenusGreatestElongation(jde float64) float64 { func LastVenusGreatestElongation(jde float64) float64 {
lastHe := LastVenusConjunction(jde) east := LastVenusGreatestElongationEast(jde)
date := venusGreatestElongation(lastHe + 2) west := LastVenusGreatestElongationWest(jde)
if date > jde { if sameEventJD(east, west) {
lastLastHe := LastVenusConjunction(lastHe - 2) return east
return venusGreatestElongation(lastLastHe + 2)
} }
return date if east > west {
return east
}
return west
}
func LastVenusInferiorConjunctionInclusive(jde float64) float64 {
date := LastVenusConjunction(jde)
if venusConjunctionTypeAt(date) {
return date
}
return LastVenusConjunction(eventUTLastQueryTT(date))
}
func NextVenusInferiorConjunctionInclusive(jde float64) float64 {
date := NextVenusConjunction(jde)
if venusConjunctionTypeAt(date) {
return date
}
return NextVenusConjunction(eventUTNextQueryTT(date))
}
func LastVenusSuperiorConjunctionInclusive(jde float64) float64 {
date := LastVenusConjunction(jde)
if !venusConjunctionTypeAt(date) {
return date
}
return LastVenusConjunction(eventUTLastQueryTT(date))
}
func NextVenusSuperiorConjunctionInclusive(jde float64) float64 {
date := NextVenusConjunction(jde)
if !venusConjunctionTypeAt(date) {
return date
}
return NextVenusConjunction(eventUTNextQueryTT(date))
}
func LastVenusRetrogradeInclusive(jde float64) float64 {
return inclusiveLastSimpleEvent(jde, LastVenusRetrograde, NextVenusRetrograde)
}
func NextVenusRetrogradeInclusive(jde float64) float64 {
return inclusiveNextSimpleEvent(jde, LastVenusRetrograde, NextVenusRetrograde)
}
func LastVenusProgradeToRetrogradeInclusive(jde float64) float64 {
return inclusiveLastSimpleEvent(jde, LastVenusProgradeToRetrograde, NextVenusProgradeToRetrograde)
}
func NextVenusProgradeToRetrogradeInclusive(jde float64) float64 {
return inclusiveNextSimpleEvent(jde, LastVenusProgradeToRetrograde, NextVenusProgradeToRetrograde)
}
func LastVenusRetrogradeToProgradeInclusive(jde float64) float64 {
return inclusiveLastSimpleEvent(jde, LastVenusRetrogradeToPrograde, NextVenusRetrogradeToPrograde)
}
func NextVenusRetrogradeToProgradeInclusive(jde float64) float64 {
return inclusiveNextSimpleEvent(jde, LastVenusRetrogradeToPrograde, NextVenusRetrogradeToPrograde)
}
func LastVenusGreatestElongationInclusive(jde float64) float64 {
return inclusiveLastSimpleEvent(jde, LastVenusGreatestElongation, NextVenusGreatestElongation)
}
func NextVenusGreatestElongationInclusive(jde float64) float64 {
return inclusiveNextSimpleEvent(jde, LastVenusGreatestElongation, NextVenusGreatestElongation)
}
func LastVenusGreatestElongationEastInclusive(jde float64) float64 {
return inclusiveLastSimpleEvent(jde, LastVenusGreatestElongationEast, NextVenusGreatestElongationEast)
}
func NextVenusGreatestElongationEastInclusive(jde float64) float64 {
return inclusiveNextSimpleEvent(jde, LastVenusGreatestElongationEast, NextVenusGreatestElongationEast)
}
func LastVenusGreatestElongationWestInclusive(jde float64) float64 {
return inclusiveLastSimpleEvent(jde, LastVenusGreatestElongationWest, NextVenusGreatestElongationWest)
}
func NextVenusGreatestElongationWestInclusive(jde float64) float64 {
return inclusiveNextSimpleEvent(jde, LastVenusGreatestElongationWest, NextVenusGreatestElongationWest)
} }
func NextVenusGreatestElongationEast(jde float64) float64 { func NextVenusGreatestElongationEast(jde float64) float64 {
date := NextVenusGreatestElongation(jde) return nextVenusGreatestElongationTyped(jde, true)
sub := Limit360(VenusApparentRa(date) - SunApparentRa(date))
if sub > 180 {
return NextVenusGreatestElongation(date + 1)
}
return date
} }
func NextVenusGreatestElongationWest(jde float64) float64 { func NextVenusGreatestElongationWest(jde float64) float64 {
date := NextVenusGreatestElongation(jde) return nextVenusGreatestElongationTyped(jde, false)
sub := Limit360(VenusApparentRa(date) - SunApparentRa(date))
if sub < 180 {
return NextVenusGreatestElongation(date + 1)
}
return date
} }
func LastVenusGreatestElongationEast(jde float64) float64 { func LastVenusGreatestElongationEast(jde float64) float64 {
date := LastVenusGreatestElongation(jde) return lastVenusGreatestElongationTyped(jde, true)
sub := Limit360(VenusApparentRa(date) - SunApparentRa(date))
if sub > 180 {
return LastVenusGreatestElongation(date - 1)
}
return date
} }
func LastVenusGreatestElongationWest(jde float64) float64 { func LastVenusGreatestElongationWest(jde float64) float64 {
date := LastVenusGreatestElongation(jde) return lastVenusGreatestElongationTyped(jde, false)
sub := Limit360(VenusApparentRa(date) - SunApparentRa(date))
if sub < 180 {
return LastVenusGreatestElongation(date - 1)
}
return date
} }

View File

@ -0,0 +1,100 @@
package astro_test
import (
"math"
"testing"
"time"
"b612.me/astro/basic"
"b612.me/astro/jupiter"
"b612.me/astro/mars"
"b612.me/astro/mercury"
"b612.me/astro/neptune"
"b612.me/astro/saturn"
"b612.me/astro/uranus"
"b612.me/astro/venus"
)
func TestPublicPlanetEventBoundaryIncludesCurrent(t *testing.T) {
type eventFuncs struct {
last func(time.Time) time.Time
next func(time.Time) time.Time
}
cases := []struct {
name string
eventUT float64
funcs eventFuncs
}{
{name: "MercuryConjunction", eventUT: basic.NextMercuryConjunction(eventBoundaryTT(2026)), funcs: eventFuncs{last: mercury.LastConjunction, next: mercury.NextConjunction}},
{name: "MercuryInferior", eventUT: basic.NextMercuryInferiorConjunctionInclusive(eventBoundaryTT(2026)), funcs: eventFuncs{last: mercury.LastInferiorConjunction, next: mercury.NextInferiorConjunction}},
{name: "MercurySuperior", eventUT: basic.NextMercurySuperiorConjunctionInclusive(eventBoundaryTT(2026)), funcs: eventFuncs{last: mercury.LastSuperiorConjunction, next: mercury.NextSuperiorConjunction}},
{name: "MercuryRetrograde", eventUT: basic.NextMercuryRetrogradeInclusive(eventBoundaryTT(2026)), funcs: eventFuncs{last: mercury.LastRetrograde, next: mercury.NextRetrograde}},
{name: "MercuryP2R", eventUT: basic.NextMercuryProgradeToRetrogradeInclusive(eventBoundaryTT(2026)), funcs: eventFuncs{last: mercury.LastProgradeToRetrograde, next: mercury.NextProgradeToRetrograde}},
{name: "MercuryR2P", eventUT: basic.NextMercuryRetrogradeToProgradeInclusive(eventBoundaryTT(2026)), funcs: eventFuncs{last: mercury.LastRetrogradeToPrograde, next: mercury.NextRetrogradeToPrograde}},
{name: "MercuryGreatestElongation", eventUT: basic.NextMercuryGreatestElongationInclusive(eventBoundaryTT(2026)), funcs: eventFuncs{last: mercury.LastGreatestElongation, next: mercury.NextGreatestElongation}},
{name: "MercuryGreatestElongationEast", eventUT: basic.NextMercuryGreatestElongationEastInclusive(eventBoundaryTT(2026)), funcs: eventFuncs{last: mercury.LastGreatestElongationEast, next: mercury.NextGreatestElongationEast}},
{name: "MercuryGreatestElongationWest", eventUT: basic.NextMercuryGreatestElongationWestInclusive(eventBoundaryTT(2026)), funcs: eventFuncs{last: mercury.LastGreatestElongationWest, next: mercury.NextGreatestElongationWest}},
{name: "VenusConjunction", eventUT: basic.NextVenusConjunction(eventBoundaryTT(2026)), funcs: eventFuncs{last: venus.LastConjunction, next: venus.NextConjunction}},
{name: "VenusInferior", eventUT: basic.NextVenusInferiorConjunctionInclusive(eventBoundaryTT(2026)), funcs: eventFuncs{last: venus.LastInferiorConjunction, next: venus.NextInferiorConjunction}},
{name: "VenusSuperior", eventUT: basic.NextVenusSuperiorConjunctionInclusive(eventBoundaryTT(2026)), funcs: eventFuncs{last: venus.LastSuperiorConjunction, next: venus.NextSuperiorConjunction}},
{name: "VenusRetrograde", eventUT: basic.NextVenusRetrogradeInclusive(eventBoundaryTT(2026)), funcs: eventFuncs{last: venus.LastRetrograde, next: venus.NextRetrograde}},
{name: "VenusP2R", eventUT: basic.NextVenusProgradeToRetrogradeInclusive(eventBoundaryTT(2026)), funcs: eventFuncs{last: venus.LastProgradeToRetrograde, next: venus.NextProgradeToRetrograde}},
{name: "VenusR2P", eventUT: basic.NextVenusRetrogradeToProgradeInclusive(eventBoundaryTT(2026)), funcs: eventFuncs{last: venus.LastRetrogradeToPrograde, next: venus.NextRetrogradeToPrograde}},
{name: "VenusGreatestElongation", eventUT: basic.NextVenusGreatestElongationInclusive(eventBoundaryTT(2026)), funcs: eventFuncs{last: venus.LastGreatestElongation, next: venus.NextGreatestElongation}},
{name: "VenusGreatestElongationEast", eventUT: basic.NextVenusGreatestElongationEastInclusive(eventBoundaryTT(2026)), funcs: eventFuncs{last: venus.LastGreatestElongationEast, next: venus.NextGreatestElongationEast}},
{name: "VenusGreatestElongationWest", eventUT: basic.NextVenusGreatestElongationWestInclusive(eventBoundaryTT(2026)), funcs: eventFuncs{last: venus.LastGreatestElongationWest, next: venus.NextGreatestElongationWest}},
{name: "MarsConjunction", eventUT: basic.NextMarsConjunction(eventBoundaryTT(2026)), funcs: eventFuncs{last: mars.LastConjunction, next: mars.NextConjunction}},
{name: "MarsOpposition", eventUT: basic.NextMarsOpposition(eventBoundaryTT(2026)), funcs: eventFuncs{last: mars.LastOpposition, next: mars.NextOpposition}},
{name: "MarsP2R", eventUT: basic.NextMarsProgradeToRetrograde(eventBoundaryTT(2026)), funcs: eventFuncs{last: mars.LastProgradeToRetrograde, next: mars.NextProgradeToRetrograde}},
{name: "MarsR2P", eventUT: basic.NextMarsRetrogradeToPrograde(eventBoundaryTT(2025)), funcs: eventFuncs{last: mars.LastRetrogradeToPrograde, next: mars.NextRetrogradeToPrograde}},
{name: "MarsEasternQuadrature", eventUT: basic.NextMarsEasternQuadrature(eventBoundaryTT(2026)), funcs: eventFuncs{last: mars.LastEasternQuadrature, next: mars.NextEasternQuadrature}},
{name: "MarsWesternQuadrature", eventUT: basic.NextMarsWesternQuadrature(eventBoundaryTT(2026)), funcs: eventFuncs{last: mars.LastWesternQuadrature, next: mars.NextWesternQuadrature}},
{name: "JupiterConjunction", eventUT: basic.NextJupiterConjunction(eventBoundaryTT(2026)), funcs: eventFuncs{last: jupiter.LastConjunction, next: jupiter.NextConjunction}},
{name: "JupiterOpposition", eventUT: basic.NextJupiterOpposition(eventBoundaryTT(2026)), funcs: eventFuncs{last: jupiter.LastOpposition, next: jupiter.NextOpposition}},
{name: "JupiterP2R", eventUT: basic.NextJupiterProgradeToRetrograde(eventBoundaryTT(2026)), funcs: eventFuncs{last: jupiter.LastProgradeToRetrograde, next: jupiter.NextProgradeToRetrograde}},
{name: "JupiterR2P", eventUT: basic.NextJupiterRetrogradeToPrograde(eventBoundaryTT(2026)), funcs: eventFuncs{last: jupiter.LastRetrogradeToPrograde, next: jupiter.NextRetrogradeToPrograde}},
{name: "JupiterEasternQuadrature", eventUT: basic.NextJupiterEasternQuadrature(eventBoundaryTT(2026)), funcs: eventFuncs{last: jupiter.LastEasternQuadrature, next: jupiter.NextEasternQuadrature}},
{name: "JupiterWesternQuadrature", eventUT: basic.NextJupiterWesternQuadrature(eventBoundaryTT(2026)), funcs: eventFuncs{last: jupiter.LastWesternQuadrature, next: jupiter.NextWesternQuadrature}},
{name: "SaturnOpposition", eventUT: basic.NextSaturnOpposition(eventBoundaryTT(2025)), funcs: eventFuncs{last: saturn.LastOpposition, next: saturn.NextOpposition}},
{name: "SaturnP2R", eventUT: basic.NextSaturnProgradeToRetrograde(eventBoundaryTT(2025)), funcs: eventFuncs{last: saturn.LastProgradeToRetrograde, next: saturn.NextProgradeToRetrograde}},
{name: "SaturnR2P", eventUT: basic.NextSaturnRetrogradeToPrograde(eventBoundaryTT(2025)), funcs: eventFuncs{last: saturn.LastRetrogradeToPrograde, next: saturn.NextRetrogradeToPrograde}},
{name: "SaturnEasternQuadrature", eventUT: basic.NextSaturnEasternQuadrature(eventBoundaryTT(2025)), funcs: eventFuncs{last: saturn.LastEasternQuadrature, next: saturn.NextEasternQuadrature}},
{name: "SaturnWesternQuadrature", eventUT: basic.NextSaturnWesternQuadrature(eventBoundaryTT(2025)), funcs: eventFuncs{last: saturn.LastWesternQuadrature, next: saturn.NextWesternQuadrature}},
{name: "UranusOpposition", eventUT: basic.NextUranusOpposition(eventBoundaryTT(2025)), funcs: eventFuncs{last: uranus.LastOpposition, next: uranus.NextOpposition}},
{name: "UranusP2R", eventUT: basic.NextUranusProgradeToRetrograde(eventBoundaryTT(2025)), funcs: eventFuncs{last: uranus.LastProgradeToRetrograde, next: uranus.NextProgradeToRetrograde}},
{name: "UranusR2P", eventUT: basic.NextUranusRetrogradeToPrograde(eventBoundaryTT(2025)), funcs: eventFuncs{last: uranus.LastRetrogradeToPrograde, next: uranus.NextRetrogradeToPrograde}},
{name: "UranusEasternQuadrature", eventUT: basic.NextUranusEasternQuadrature(eventBoundaryTT(2025)), funcs: eventFuncs{last: uranus.LastEasternQuadrature, next: uranus.NextEasternQuadrature}},
{name: "UranusWesternQuadrature", eventUT: basic.NextUranusWesternQuadrature(eventBoundaryTT(2025)), funcs: eventFuncs{last: uranus.LastWesternQuadrature, next: uranus.NextWesternQuadrature}},
{name: "NeptuneOpposition", eventUT: basic.NextNeptuneOpposition(eventBoundaryTT(2026)), funcs: eventFuncs{last: neptune.LastOpposition, next: neptune.NextOpposition}},
{name: "NeptuneP2R", eventUT: basic.NextNeptuneProgradeToRetrograde(eventBoundaryTT(2026)), funcs: eventFuncs{last: neptune.LastProgradeToRetrograde, next: neptune.NextProgradeToRetrograde}},
{name: "NeptuneR2P", eventUT: basic.NextNeptuneRetrogradeToPrograde(eventBoundaryTT(2026)), funcs: eventFuncs{last: neptune.LastRetrogradeToPrograde, next: neptune.NextRetrogradeToPrograde}},
{name: "NeptuneEasternQuadrature", eventUT: basic.NextNeptuneEasternQuadrature(eventBoundaryTT(2026)), funcs: eventFuncs{last: neptune.LastEasternQuadrature, next: neptune.NextEasternQuadrature}},
{name: "NeptuneWesternQuadrature", eventUT: basic.NextNeptuneWesternQuadrature(eventBoundaryTT(2026)), funcs: eventFuncs{last: neptune.LastWesternQuadrature, next: neptune.NextWesternQuadrature}},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
eventTime := basic.JDE2DateByZone(tc.eventUT, time.UTC, false)
assertSameEventTime(t, "last", tc.funcs.last(eventTime), eventTime)
assertSameEventTime(t, "next", tc.funcs.next(eventTime), eventTime)
})
}
}
func eventBoundaryTT(year int) float64 {
return basic.TD2UT(basic.Date2JDE(time.Date(year, 1, 1, 0, 0, 0, 0, time.UTC)), true)
}
func assertSameEventTime(t *testing.T, name string, got, want time.Time) {
t.Helper()
if math.Abs(got.Sub(want).Seconds()) > 1.0 {
t.Fatalf("%s boundary mismatch: got %s want %s delta %.3fs", name, got, want, got.Sub(want).Seconds())
}
}

View File

@ -207,8 +207,7 @@ func SetTime(date time.Time, lon, lat, height float64, aero bool) (time.Time, er
// LastConjunction 上一次合日 / previous conjunction with the Sun. // LastConjunction 上一次合日 / previous conjunction with the Sun.
// //
// 返回 date 之前最近一次与太阳的合日时刻,结果保持 date 的时区。 // 返回 date 当前或之前最近一次与太阳的合日时刻,结果保持 date 的时区。
// Returns the most recent conjunction with the Sun relative to date, keeping date's time zone.
func LastConjunction(date time.Time) time.Time { func LastConjunction(date time.Time) time.Time {
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true) jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
return basic.JDE2DateByZone(basic.LastJupiterConjunction(jde), date.Location(), false) return basic.JDE2DateByZone(basic.LastJupiterConjunction(jde), date.Location(), false)
@ -216,8 +215,7 @@ func LastConjunction(date time.Time) time.Time {
// NextConjunction 下一次合日 / next conjunction with the Sun. // NextConjunction 下一次合日 / next conjunction with the Sun.
// //
// 返回 date 之后最近一次与太阳的合日时刻,结果保持 date 的时区。 // 返回 date 当前或之后最近一次与太阳的合日时刻,结果保持 date 的时区。
// Returns the next conjunction with the Sun relative to date, keeping date's time zone.
func NextConjunction(date time.Time) time.Time { func NextConjunction(date time.Time) time.Time {
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true) jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
return basic.JDE2DateByZone(basic.NextJupiterConjunction(jde), date.Location(), false) return basic.JDE2DateByZone(basic.NextJupiterConjunction(jde), date.Location(), false)
@ -225,8 +223,7 @@ func NextConjunction(date time.Time) time.Time {
// LastOpposition 上一次冲日 / previous opposition. // LastOpposition 上一次冲日 / previous opposition.
// //
// 返回 date 之前最近一次冲日时刻,结果保持 date 的时区。 // 返回 date 当前或之前最近一次冲日时刻,结果保持 date 的时区。
// Returns the most recent opposition relative to date, keeping date's time zone.
func LastOpposition(date time.Time) time.Time { func LastOpposition(date time.Time) time.Time {
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true) jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
return basic.JDE2DateByZone(basic.LastJupiterOpposition(jde), date.Location(), false) return basic.JDE2DateByZone(basic.LastJupiterOpposition(jde), date.Location(), false)
@ -234,8 +231,7 @@ func LastOpposition(date time.Time) time.Time {
// NextOpposition 下一次冲日 / next opposition. // NextOpposition 下一次冲日 / next opposition.
// //
// 返回 date 之后最近一次冲日时刻,结果保持 date 的时区。 // 返回 date 当前或之后最近一次冲日时刻,结果保持 date 的时区。
// Returns the next opposition relative to date, keeping date's time zone.
func NextOpposition(date time.Time) time.Time { func NextOpposition(date time.Time) time.Time {
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true) jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
return basic.JDE2DateByZone(basic.NextJupiterOpposition(jde), date.Location(), false) return basic.JDE2DateByZone(basic.NextJupiterOpposition(jde), date.Location(), false)
@ -243,8 +239,7 @@ func NextOpposition(date time.Time) time.Time {
// LastProgradeToRetrograde 上一次顺行转逆行留 / previous station from prograde to retrograde. // LastProgradeToRetrograde 上一次顺行转逆行留 / previous station from prograde to retrograde.
// //
// 返回 date 之前最近一次由顺行转为逆行的留时刻,结果保持 date 的时区。 // 返回 date 当前或之前最近一次由顺行转为逆行的留时刻,结果保持 date 的时区。
// Returns the most recent stationary point where motion changes from prograde to retrograde relative to date, keeping date's time zone.
func LastProgradeToRetrograde(date time.Time) time.Time { func LastProgradeToRetrograde(date time.Time) time.Time {
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true) jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
return basic.JDE2DateByZone(basic.LastJupiterProgradeToRetrograde(jde), date.Location(), false) return basic.JDE2DateByZone(basic.LastJupiterProgradeToRetrograde(jde), date.Location(), false)
@ -252,8 +247,7 @@ func LastProgradeToRetrograde(date time.Time) time.Time {
// NextProgradeToRetrograde 下一次顺行转逆行留 / next station from prograde to retrograde. // NextProgradeToRetrograde 下一次顺行转逆行留 / next station from prograde to retrograde.
// //
// 返回 date 之后最近一次由顺行转为逆行的留时刻,结果保持 date 的时区。 // 返回 date 当前或之后最近一次由顺行转为逆行的留时刻,结果保持 date 的时区。
// Returns the next stationary point where motion changes from prograde to retrograde relative to date, keeping date's time zone.
func NextProgradeToRetrograde(date time.Time) time.Time { func NextProgradeToRetrograde(date time.Time) time.Time {
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true) jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
return basic.JDE2DateByZone(basic.NextJupiterProgradeToRetrograde(jde), date.Location(), false) return basic.JDE2DateByZone(basic.NextJupiterProgradeToRetrograde(jde), date.Location(), false)
@ -261,8 +255,7 @@ func NextProgradeToRetrograde(date time.Time) time.Time {
// LastRetrogradeToPrograde 上一次逆行转顺行留 / previous station from retrograde to prograde. // LastRetrogradeToPrograde 上一次逆行转顺行留 / previous station from retrograde to prograde.
// //
// 返回 date 之前最近一次由逆行转为顺行的留时刻,结果保持 date 的时区。 // 返回 date 当前或之前最近一次由逆行转为顺行的留时刻,结果保持 date 的时区。
// Returns the most recent stationary point where motion changes from retrograde to prograde relative to date, keeping date's time zone.
func LastRetrogradeToPrograde(date time.Time) time.Time { func LastRetrogradeToPrograde(date time.Time) time.Time {
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true) jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
return basic.JDE2DateByZone(basic.LastJupiterRetrogradeToPrograde(jde), date.Location(), false) return basic.JDE2DateByZone(basic.LastJupiterRetrogradeToPrograde(jde), date.Location(), false)
@ -270,8 +263,7 @@ func LastRetrogradeToPrograde(date time.Time) time.Time {
// NextRetrogradeToPrograde 下一次逆行转顺行留 / next station from retrograde to prograde. // NextRetrogradeToPrograde 下一次逆行转顺行留 / next station from retrograde to prograde.
// //
// 返回 date 之后最近一次由逆行转为顺行的留时刻,结果保持 date 的时区。 // 返回 date 当前或之后最近一次由逆行转为顺行的留时刻,结果保持 date 的时区。
// Returns the next stationary point where motion changes from retrograde to prograde relative to date, keeping date's time zone.
func NextRetrogradeToPrograde(date time.Time) time.Time { func NextRetrogradeToPrograde(date time.Time) time.Time {
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true) jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
return basic.JDE2DateByZone(basic.NextJupiterRetrogradeToPrograde(jde), date.Location(), false) return basic.JDE2DateByZone(basic.NextJupiterRetrogradeToPrograde(jde), date.Location(), false)
@ -279,8 +271,7 @@ func NextRetrogradeToPrograde(date time.Time) time.Time {
// LastEasternQuadrature 上一次东方照 / previous eastern quadrature. // LastEasternQuadrature 上一次东方照 / previous eastern quadrature.
// //
// 返回 date 之前最近一次东方照时刻,结果保持 date 的时区。 // 返回 date 当前或之前最近一次东方照时刻,结果保持 date 的时区。
// Returns the most recent eastern quadrature relative to date, keeping date's time zone.
func LastEasternQuadrature(date time.Time) time.Time { func LastEasternQuadrature(date time.Time) time.Time {
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true) jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
return basic.JDE2DateByZone(basic.LastJupiterEasternQuadrature(jde), date.Location(), false) return basic.JDE2DateByZone(basic.LastJupiterEasternQuadrature(jde), date.Location(), false)
@ -288,8 +279,7 @@ func LastEasternQuadrature(date time.Time) time.Time {
// NextEasternQuadrature 下一次东方照 / next eastern quadrature. // NextEasternQuadrature 下一次东方照 / next eastern quadrature.
// //
// 返回 date 之后最近一次东方照时刻,结果保持 date 的时区。 // 返回 date 当前或之后最近一次东方照时刻,结果保持 date 的时区。
// Returns the next eastern quadrature relative to date, keeping date's time zone.
func NextEasternQuadrature(date time.Time) time.Time { func NextEasternQuadrature(date time.Time) time.Time {
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true) jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
return basic.JDE2DateByZone(basic.NextJupiterEasternQuadrature(jde), date.Location(), false) return basic.JDE2DateByZone(basic.NextJupiterEasternQuadrature(jde), date.Location(), false)
@ -297,8 +287,7 @@ func NextEasternQuadrature(date time.Time) time.Time {
// LastWesternQuadrature 上一次西方照 / previous western quadrature. // LastWesternQuadrature 上一次西方照 / previous western quadrature.
// //
// 返回 date 之前最近一次西方照时刻,结果保持 date 的时区。 // 返回 date 当前或之前最近一次西方照时刻,结果保持 date 的时区。
// Returns the most recent western quadrature relative to date, keeping date's time zone.
func LastWesternQuadrature(date time.Time) time.Time { func LastWesternQuadrature(date time.Time) time.Time {
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true) jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
return basic.JDE2DateByZone(basic.LastJupiterWesternQuadrature(jde), date.Location(), false) return basic.JDE2DateByZone(basic.LastJupiterWesternQuadrature(jde), date.Location(), false)
@ -306,8 +295,7 @@ func LastWesternQuadrature(date time.Time) time.Time {
// NextWesternQuadrature 下一次西方照 / next western quadrature. // NextWesternQuadrature 下一次西方照 / next western quadrature.
// //
// 返回 date 之后最近一次西方照时刻,结果保持 date 的时区。 // 返回 date 当前或之后最近一次西方照时刻,结果保持 date 的时区。
// Returns the next western quadrature relative to date, keeping date's time zone.
func NextWesternQuadrature(date time.Time) time.Time { func NextWesternQuadrature(date time.Time) time.Time {
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true) jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
return basic.JDE2DateByZone(basic.NextJupiterWesternQuadrature(jde), date.Location(), false) return basic.JDE2DateByZone(basic.NextJupiterWesternQuadrature(jde), date.Location(), false)

View File

@ -207,8 +207,7 @@ func SetTime(date time.Time, lon, lat, height float64, aero bool) (time.Time, er
// LastConjunction 上一次合日 / previous conjunction with the Sun. // LastConjunction 上一次合日 / previous conjunction with the Sun.
// //
// 返回 date 之前最近一次与太阳的合日时刻,结果保持 date 的时区。 // 返回 date 当前或之前最近一次与太阳的合日时刻,结果保持 date 的时区。
// Returns the most recent conjunction with the Sun relative to date, keeping date's time zone.
func LastConjunction(date time.Time) time.Time { func LastConjunction(date time.Time) time.Time {
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true) jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
return basic.JDE2DateByZone(basic.LastMarsConjunction(jde), date.Location(), false) return basic.JDE2DateByZone(basic.LastMarsConjunction(jde), date.Location(), false)
@ -216,8 +215,7 @@ func LastConjunction(date time.Time) time.Time {
// NextConjunction 下一次合日 / next conjunction with the Sun. // NextConjunction 下一次合日 / next conjunction with the Sun.
// //
// 返回 date 之后最近一次与太阳的合日时刻,结果保持 date 的时区。 // 返回 date 当前或之后最近一次与太阳的合日时刻,结果保持 date 的时区。
// Returns the next conjunction with the Sun relative to date, keeping date's time zone.
func NextConjunction(date time.Time) time.Time { func NextConjunction(date time.Time) time.Time {
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true) jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
return basic.JDE2DateByZone(basic.NextMarsConjunction(jde), date.Location(), false) return basic.JDE2DateByZone(basic.NextMarsConjunction(jde), date.Location(), false)
@ -225,8 +223,7 @@ func NextConjunction(date time.Time) time.Time {
// LastOpposition 上一次冲日 / previous opposition. // LastOpposition 上一次冲日 / previous opposition.
// //
// 返回 date 之前最近一次冲日时刻,结果保持 date 的时区。 // 返回 date 当前或之前最近一次冲日时刻,结果保持 date 的时区。
// Returns the most recent opposition relative to date, keeping date's time zone.
func LastOpposition(date time.Time) time.Time { func LastOpposition(date time.Time) time.Time {
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true) jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
return basic.JDE2DateByZone(basic.LastMarsOpposition(jde), date.Location(), false) return basic.JDE2DateByZone(basic.LastMarsOpposition(jde), date.Location(), false)
@ -234,8 +231,7 @@ func LastOpposition(date time.Time) time.Time {
// NextOpposition 下一次冲日 / next opposition. // NextOpposition 下一次冲日 / next opposition.
// //
// 返回 date 之后最近一次冲日时刻,结果保持 date 的时区。 // 返回 date 当前或之后最近一次冲日时刻,结果保持 date 的时区。
// Returns the next opposition relative to date, keeping date's time zone.
func NextOpposition(date time.Time) time.Time { func NextOpposition(date time.Time) time.Time {
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true) jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
return basic.JDE2DateByZone(basic.NextMarsOpposition(jde), date.Location(), false) return basic.JDE2DateByZone(basic.NextMarsOpposition(jde), date.Location(), false)
@ -243,8 +239,7 @@ func NextOpposition(date time.Time) time.Time {
// LastProgradeToRetrograde 上一次顺行转逆行留 / previous station from prograde to retrograde. // LastProgradeToRetrograde 上一次顺行转逆行留 / previous station from prograde to retrograde.
// //
// 返回 date 之前最近一次由顺行转为逆行的留时刻,结果保持 date 的时区。 // 返回 date 当前或之前最近一次由顺行转为逆行的留时刻,结果保持 date 的时区。
// Returns the most recent stationary point where motion changes from prograde to retrograde relative to date, keeping date's time zone.
func LastProgradeToRetrograde(date time.Time) time.Time { func LastProgradeToRetrograde(date time.Time) time.Time {
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true) jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
return basic.JDE2DateByZone(basic.LastMarsProgradeToRetrograde(jde), date.Location(), false) return basic.JDE2DateByZone(basic.LastMarsProgradeToRetrograde(jde), date.Location(), false)
@ -252,8 +247,7 @@ func LastProgradeToRetrograde(date time.Time) time.Time {
// NextProgradeToRetrograde 下一次顺行转逆行留 / next station from prograde to retrograde. // NextProgradeToRetrograde 下一次顺行转逆行留 / next station from prograde to retrograde.
// //
// 返回 date 之后最近一次由顺行转为逆行的留时刻,结果保持 date 的时区。 // 返回 date 当前或之后最近一次由顺行转为逆行的留时刻,结果保持 date 的时区。
// Returns the next stationary point where motion changes from prograde to retrograde relative to date, keeping date's time zone.
func NextProgradeToRetrograde(date time.Time) time.Time { func NextProgradeToRetrograde(date time.Time) time.Time {
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true) jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
return basic.JDE2DateByZone(basic.NextMarsProgradeToRetrograde(jde), date.Location(), false) return basic.JDE2DateByZone(basic.NextMarsProgradeToRetrograde(jde), date.Location(), false)
@ -261,8 +255,7 @@ func NextProgradeToRetrograde(date time.Time) time.Time {
// LastRetrogradeToPrograde 上一次逆行转顺行留 / previous station from retrograde to prograde. // LastRetrogradeToPrograde 上一次逆行转顺行留 / previous station from retrograde to prograde.
// //
// 返回 date 之前最近一次由逆行转为顺行的留时刻,结果保持 date 的时区。 // 返回 date 当前或之前最近一次由逆行转为顺行的留时刻,结果保持 date 的时区。
// Returns the most recent stationary point where motion changes from retrograde to prograde relative to date, keeping date's time zone.
func LastRetrogradeToPrograde(date time.Time) time.Time { func LastRetrogradeToPrograde(date time.Time) time.Time {
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true) jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
return basic.JDE2DateByZone(basic.LastMarsRetrogradeToPrograde(jde), date.Location(), false) return basic.JDE2DateByZone(basic.LastMarsRetrogradeToPrograde(jde), date.Location(), false)
@ -270,8 +263,7 @@ func LastRetrogradeToPrograde(date time.Time) time.Time {
// NextRetrogradeToPrograde 下一次逆行转顺行留 / next station from retrograde to prograde. // NextRetrogradeToPrograde 下一次逆行转顺行留 / next station from retrograde to prograde.
// //
// 返回 date 之后最近一次由逆行转为顺行的留时刻,结果保持 date 的时区。 // 返回 date 当前或之后最近一次由逆行转为顺行的留时刻,结果保持 date 的时区。
// Returns the next stationary point where motion changes from retrograde to prograde relative to date, keeping date's time zone.
func NextRetrogradeToPrograde(date time.Time) time.Time { func NextRetrogradeToPrograde(date time.Time) time.Time {
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true) jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
return basic.JDE2DateByZone(basic.NextMarsRetrogradeToPrograde(jde), date.Location(), false) return basic.JDE2DateByZone(basic.NextMarsRetrogradeToPrograde(jde), date.Location(), false)
@ -279,8 +271,7 @@ func NextRetrogradeToPrograde(date time.Time) time.Time {
// LastEasternQuadrature 上一次东方照 / previous eastern quadrature. // LastEasternQuadrature 上一次东方照 / previous eastern quadrature.
// //
// 返回 date 之前最近一次东方照时刻,结果保持 date 的时区。 // 返回 date 当前或之前最近一次东方照时刻,结果保持 date 的时区。
// Returns the most recent eastern quadrature relative to date, keeping date's time zone.
func LastEasternQuadrature(date time.Time) time.Time { func LastEasternQuadrature(date time.Time) time.Time {
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true) jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
return basic.JDE2DateByZone(basic.LastMarsEasternQuadrature(jde), date.Location(), false) return basic.JDE2DateByZone(basic.LastMarsEasternQuadrature(jde), date.Location(), false)
@ -288,8 +279,7 @@ func LastEasternQuadrature(date time.Time) time.Time {
// NextEasternQuadrature 下一次东方照 / next eastern quadrature. // NextEasternQuadrature 下一次东方照 / next eastern quadrature.
// //
// 返回 date 之后最近一次东方照时刻,结果保持 date 的时区。 // 返回 date 当前或之后最近一次东方照时刻,结果保持 date 的时区。
// Returns the next eastern quadrature relative to date, keeping date's time zone.
func NextEasternQuadrature(date time.Time) time.Time { func NextEasternQuadrature(date time.Time) time.Time {
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true) jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
return basic.JDE2DateByZone(basic.NextMarsEasternQuadrature(jde), date.Location(), false) return basic.JDE2DateByZone(basic.NextMarsEasternQuadrature(jde), date.Location(), false)
@ -297,8 +287,7 @@ func NextEasternQuadrature(date time.Time) time.Time {
// LastWesternQuadrature 上一次西方照 / previous western quadrature. // LastWesternQuadrature 上一次西方照 / previous western quadrature.
// //
// 返回 date 之前最近一次西方照时刻,结果保持 date 的时区。 // 返回 date 当前或之前最近一次西方照时刻,结果保持 date 的时区。
// Returns the most recent western quadrature relative to date, keeping date's time zone.
func LastWesternQuadrature(date time.Time) time.Time { func LastWesternQuadrature(date time.Time) time.Time {
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true) jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
return basic.JDE2DateByZone(basic.LastMarsWesternQuadrature(jde), date.Location(), false) return basic.JDE2DateByZone(basic.LastMarsWesternQuadrature(jde), date.Location(), false)
@ -306,8 +295,7 @@ func LastWesternQuadrature(date time.Time) time.Time {
// NextWesternQuadrature 下一次西方照 / next western quadrature. // NextWesternQuadrature 下一次西方照 / next western quadrature.
// //
// 返回 date 之后最近一次西方照时刻,结果保持 date 的时区。 // 返回 date 当前或之后最近一次西方照时刻,结果保持 date 的时区。
// Returns the next western quadrature relative to date, keeping date's time zone.
func NextWesternQuadrature(date time.Time) time.Time { func NextWesternQuadrature(date time.Time) time.Time {
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true) jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
return basic.JDE2DateByZone(basic.NextMarsWesternQuadrature(jde), date.Location(), false) return basic.JDE2DateByZone(basic.NextMarsWesternQuadrature(jde), date.Location(), false)

View File

@ -207,8 +207,7 @@ func SetTime(date time.Time, lon, lat, height float64, aero bool) (time.Time, er
// LastConjunction 上一次合日 / previous conjunction with the Sun. // LastConjunction 上一次合日 / previous conjunction with the Sun.
// //
// 返回 date 之前最近一次与太阳的合日时刻,结果保持 date 的时区。 // 返回 date 当前或之前最近一次与太阳的合日时刻,结果保持 date 的时区。
// Returns the most recent conjunction with the Sun relative to date, keeping date's time zone.
func LastConjunction(date time.Time) time.Time { func LastConjunction(date time.Time) time.Time {
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true) jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
return basic.JDE2DateByZone(basic.LastMercuryConjunction(jde), date.Location(), false) return basic.JDE2DateByZone(basic.LastMercuryConjunction(jde), date.Location(), false)
@ -216,8 +215,7 @@ func LastConjunction(date time.Time) time.Time {
// NextConjunction 下一次合日 / next conjunction with the Sun. // NextConjunction 下一次合日 / next conjunction with the Sun.
// //
// 返回 date 之后最近一次与太阳的合日时刻,结果保持 date 的时区。 // 返回 date 当前或之后最近一次与太阳的合日时刻,结果保持 date 的时区。
// Returns the next conjunction with the Sun relative to date, keeping date's time zone.
func NextConjunction(date time.Time) time.Time { func NextConjunction(date time.Time) time.Time {
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true) jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
return basic.JDE2DateByZone(basic.NextMercuryConjunction(jde), date.Location(), false) return basic.JDE2DateByZone(basic.NextMercuryConjunction(jde), date.Location(), false)
@ -225,144 +223,128 @@ func NextConjunction(date time.Time) time.Time {
// LastInferiorConjunction 上一次下合 / previous inferior conjunction. // LastInferiorConjunction 上一次下合 / previous inferior conjunction.
// //
// 返回 date 之前最近一次下合时刻,结果保持 date 的时区。 // 返回 date 当前或之前最近一次下合时刻,结果保持 date 的时区。
// Returns the most recent inferior conjunction relative to date, keeping date's time zone.
func LastInferiorConjunction(date time.Time) time.Time { func LastInferiorConjunction(date time.Time) time.Time {
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true) jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
return basic.JDE2DateByZone(basic.LastMercuryInferiorConjunction(jde), date.Location(), false) return basic.JDE2DateByZone(basic.LastMercuryInferiorConjunctionInclusive(jde), date.Location(), false)
} }
// NextInferiorConjunction 下一次下合 / next inferior conjunction. // NextInferiorConjunction 下一次下合 / next inferior conjunction.
// //
// 返回 date 之后最近一次下合时刻,结果保持 date 的时区。 // 返回 date 当前或之后最近一次下合时刻,结果保持 date 的时区。
// Returns the next inferior conjunction relative to date, keeping date's time zone.
func NextInferiorConjunction(date time.Time) time.Time { func NextInferiorConjunction(date time.Time) time.Time {
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true) jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
return basic.JDE2DateByZone(basic.NextMercuryInferiorConjunction(jde), date.Location(), false) return basic.JDE2DateByZone(basic.NextMercuryInferiorConjunctionInclusive(jde), date.Location(), false)
} }
// LastSuperiorConjunction 上一次上合 / previous superior conjunction. // LastSuperiorConjunction 上一次上合 / previous superior conjunction.
// //
// 返回 date 之前最近一次上合时刻,结果保持 date 的时区。 // 返回 date 当前或之前最近一次上合时刻,结果保持 date 的时区。
// Returns the most recent superior conjunction relative to date, keeping date's time zone.
func LastSuperiorConjunction(date time.Time) time.Time { func LastSuperiorConjunction(date time.Time) time.Time {
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true) jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
return basic.JDE2DateByZone(basic.LastMercurySuperiorConjunction(jde), date.Location(), false) return basic.JDE2DateByZone(basic.LastMercurySuperiorConjunctionInclusive(jde), date.Location(), false)
} }
// NextSuperiorConjunction 下一次上合 / next superior conjunction. // NextSuperiorConjunction 下一次上合 / next superior conjunction.
// //
// 返回 date 之后最近一次上合时刻,结果保持 date 的时区。 // 返回 date 当前或之后最近一次上合时刻,结果保持 date 的时区。
// Returns the next superior conjunction relative to date, keeping date's time zone.
func NextSuperiorConjunction(date time.Time) time.Time { func NextSuperiorConjunction(date time.Time) time.Time {
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true) jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
return basic.JDE2DateByZone(basic.NextMercurySuperiorConjunction(jde), date.Location(), false) return basic.JDE2DateByZone(basic.NextMercurySuperiorConjunctionInclusive(jde), date.Location(), false)
} }
// LastRetrograde 上一次留 / previous stationary point. // LastRetrograde 上一次留 / previous stationary point.
// //
// 返回 date 之前最近一次留时刻,不区分顺转逆还是逆转顺,结果保持 date 的时区。 // 返回 date 当前或之前最近一次留时刻,不区分顺转逆还是逆转顺,结果保持 date 的时区。
// Returns the most recent stationary point relative to date without distinguishing direction, keeping date's time zone.
func LastRetrograde(date time.Time) time.Time { func LastRetrograde(date time.Time) time.Time {
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true) jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
return basic.JDE2DateByZone(basic.LastMercuryRetrograde(jde), date.Location(), false) return basic.JDE2DateByZone(basic.LastMercuryRetrogradeInclusive(jde), date.Location(), false)
} }
// NextRetrograde 下一次留 / next stationary point. // NextRetrograde 下一次留 / next stationary point.
// //
// 返回 date 之后最近一次留时刻,不区分顺转逆还是逆转顺,结果保持 date 的时区。 // 返回 date 当前或之后最近一次留时刻,不区分顺转逆还是逆转顺,结果保持 date 的时区。
// Returns the next stationary point relative to date without distinguishing direction, keeping date's time zone.
func NextRetrograde(date time.Time) time.Time { func NextRetrograde(date time.Time) time.Time {
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true) jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
return basic.JDE2DateByZone(basic.NextMercuryRetrograde(jde), date.Location(), false) return basic.JDE2DateByZone(basic.NextMercuryRetrogradeInclusive(jde), date.Location(), false)
} }
// LastProgradeToRetrograde 上一次顺行转逆行留 / previous station from prograde to retrograde. // LastProgradeToRetrograde 上一次顺行转逆行留 / previous station from prograde to retrograde.
// //
// 返回 date 之前最近一次由顺行转为逆行的留时刻,结果保持 date 的时区。 // 返回 date 当前或之前最近一次由顺行转为逆行的留时刻,结果保持 date 的时区。
// Returns the most recent stationary point where motion changes from prograde to retrograde relative to date, keeping date's time zone.
func LastProgradeToRetrograde(date time.Time) time.Time { func LastProgradeToRetrograde(date time.Time) time.Time {
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true) jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
return basic.JDE2DateByZone(basic.LastMercuryProgradeToRetrograde(jde), date.Location(), false) return basic.JDE2DateByZone(basic.LastMercuryProgradeToRetrogradeInclusive(jde), date.Location(), false)
} }
// NextProgradeToRetrograde 下一次顺行转逆行留 / next station from prograde to retrograde. // NextProgradeToRetrograde 下一次顺行转逆行留 / next station from prograde to retrograde.
// //
// 返回 date 之后最近一次由顺行转为逆行的留时刻,结果保持 date 的时区。 // 返回 date 当前或之后最近一次由顺行转为逆行的留时刻,结果保持 date 的时区。
// Returns the next stationary point where motion changes from prograde to retrograde relative to date, keeping date's time zone.
func NextProgradeToRetrograde(date time.Time) time.Time { func NextProgradeToRetrograde(date time.Time) time.Time {
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true) jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
return basic.JDE2DateByZone(basic.NextMercuryProgradeToRetrograde(jde), date.Location(), false) return basic.JDE2DateByZone(basic.NextMercuryProgradeToRetrogradeInclusive(jde), date.Location(), false)
} }
// LastRetrogradeToPrograde 上一次逆行转顺行留 / previous station from retrograde to prograde. // LastRetrogradeToPrograde 上一次逆行转顺行留 / previous station from retrograde to prograde.
// //
// 返回 date 之前最近一次由逆行转为顺行的留时刻,结果保持 date 的时区。 // 返回 date 当前或之前最近一次由逆行转为顺行的留时刻,结果保持 date 的时区。
// Returns the most recent stationary point where motion changes from retrograde to prograde relative to date, keeping date's time zone.
func LastRetrogradeToPrograde(date time.Time) time.Time { func LastRetrogradeToPrograde(date time.Time) time.Time {
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true) jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
return basic.JDE2DateByZone(basic.LastMercuryRetrogradeToPrograde(jde), date.Location(), false) return basic.JDE2DateByZone(basic.LastMercuryRetrogradeToProgradeInclusive(jde), date.Location(), false)
} }
// NextRetrogradeToPrograde 下一次逆行转顺行留 / next station from retrograde to prograde. // NextRetrogradeToPrograde 下一次逆行转顺行留 / next station from retrograde to prograde.
// //
// 返回 date 之后最近一次由逆行转为顺行的留时刻,结果保持 date 的时区。 // 返回 date 当前或之后最近一次由逆行转为顺行的留时刻,结果保持 date 的时区。
// Returns the next stationary point where motion changes from retrograde to prograde relative to date, keeping date's time zone.
func NextRetrogradeToPrograde(date time.Time) time.Time { func NextRetrogradeToPrograde(date time.Time) time.Time {
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true) jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
return basic.JDE2DateByZone(basic.NextMercuryRetrogradeToPrograde(jde), date.Location(), false) return basic.JDE2DateByZone(basic.NextMercuryRetrogradeToProgradeInclusive(jde), date.Location(), false)
} }
// LastGreatestElongation 上一次大距 / previous greatest elongation. // LastGreatestElongation 上一次大距 / previous greatest elongation.
// //
// 返回 date 之前最近一次大距时刻,不区分东西大距,结果保持 date 的时区。 // 返回 date 当前或之前最近一次大距时刻,不区分东西大距,结果保持 date 的时区。
// Returns the most recent greatest elongation relative to date without distinguishing east or west, keeping date's time zone.
func LastGreatestElongation(date time.Time) time.Time { func LastGreatestElongation(date time.Time) time.Time {
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true) jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
return basic.JDE2DateByZone(basic.LastMercuryGreatestElongation(jde), date.Location(), false) return basic.JDE2DateByZone(basic.LastMercuryGreatestElongationInclusive(jde), date.Location(), false)
} }
// NextGreatestElongation 下一次大距 / next greatest elongation. // NextGreatestElongation 下一次大距 / next greatest elongation.
// //
// 返回 date 之后最近一次大距时刻,不区分东西大距,结果保持 date 的时区。 // 返回 date 当前或之后最近一次大距时刻,不区分东西大距,结果保持 date 的时区。
// Returns the next greatest elongation relative to date without distinguishing east or west, keeping date's time zone.
func NextGreatestElongation(date time.Time) time.Time { func NextGreatestElongation(date time.Time) time.Time {
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true) jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
return basic.JDE2DateByZone(basic.NextMercuryGreatestElongation(jde), date.Location(), false) return basic.JDE2DateByZone(basic.NextMercuryGreatestElongationInclusive(jde), date.Location(), false)
} }
// LastGreatestElongationEast 上一次东大距 / previous greatest eastern elongation. // LastGreatestElongationEast 上一次东大距 / previous greatest eastern elongation.
// //
// 返回 date 之前最近一次东大距时刻,结果保持 date 的时区。 // 返回 date 当前或之前最近一次东大距时刻,结果保持 date 的时区。
// Returns the most recent greatest eastern elongation relative to date, keeping date's time zone.
func LastGreatestElongationEast(date time.Time) time.Time { func LastGreatestElongationEast(date time.Time) time.Time {
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true) jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
return basic.JDE2DateByZone(basic.LastMercuryGreatestElongationEast(jde), date.Location(), false) return basic.JDE2DateByZone(basic.LastMercuryGreatestElongationEastInclusive(jde), date.Location(), false)
} }
// NextGreatestElongationEast 下一次东大距 / next greatest eastern elongation. // NextGreatestElongationEast 下一次东大距 / next greatest eastern elongation.
// //
// 返回 date 之后最近一次东大距时刻,结果保持 date 的时区。 // 返回 date 当前或之后最近一次东大距时刻,结果保持 date 的时区。
// Returns the next greatest eastern elongation relative to date, keeping date's time zone.
func NextGreatestElongationEast(date time.Time) time.Time { func NextGreatestElongationEast(date time.Time) time.Time {
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true) jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
return basic.JDE2DateByZone(basic.NextMercuryGreatestElongationEast(jde), date.Location(), false) return basic.JDE2DateByZone(basic.NextMercuryGreatestElongationEastInclusive(jde), date.Location(), false)
} }
// LastGreatestElongationWest 上一次西大距 / previous greatest western elongation. // LastGreatestElongationWest 上一次西大距 / previous greatest western elongation.
// //
// 返回 date 之前最近一次西大距时刻,结果保持 date 的时区。 // 返回 date 当前或之前最近一次西大距时刻,结果保持 date 的时区。
// Returns the most recent greatest western elongation relative to date, keeping date's time zone.
func LastGreatestElongationWest(date time.Time) time.Time { func LastGreatestElongationWest(date time.Time) time.Time {
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true) jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
return basic.JDE2DateByZone(basic.LastMercuryGreatestElongationWest(jde), date.Location(), false) return basic.JDE2DateByZone(basic.LastMercuryGreatestElongationWestInclusive(jde), date.Location(), false)
} }
// NextGreatestElongationWest 下一次西大距 / next greatest western elongation. // NextGreatestElongationWest 下一次西大距 / next greatest western elongation.
// //
// 返回 date 之后最近一次西大距时刻,结果保持 date 的时区。 // 返回 date 当前或之后最近一次西大距时刻,结果保持 date 的时区。
// Returns the next greatest western elongation relative to date, keeping date's time zone.
func NextGreatestElongationWest(date time.Time) time.Time { func NextGreatestElongationWest(date time.Time) time.Time {
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true) jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
return basic.JDE2DateByZone(basic.NextMercuryGreatestElongationWest(jde), date.Location(), false) return basic.JDE2DateByZone(basic.NextMercuryGreatestElongationWestInclusive(jde), date.Location(), false)
} }

View File

@ -207,8 +207,7 @@ func SetTime(date time.Time, lon, lat, height float64, aero bool) (time.Time, er
// LastConjunction 上一次合日 / previous conjunction with the Sun. // LastConjunction 上一次合日 / previous conjunction with the Sun.
// //
// 返回 date 之前最近一次与太阳的合日时刻,结果保持 date 的时区。 // 返回 date 当前或之前最近一次与太阳的合日时刻,结果保持 date 的时区。
// Returns the most recent conjunction with the Sun relative to date, keeping date's time zone.
func LastConjunction(date time.Time) time.Time { func LastConjunction(date time.Time) time.Time {
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true) jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
return basic.JDE2DateByZone(basic.LastNeptuneConjunction(jde), date.Location(), false) return basic.JDE2DateByZone(basic.LastNeptuneConjunction(jde), date.Location(), false)
@ -216,8 +215,7 @@ func LastConjunction(date time.Time) time.Time {
// NextConjunction 下一次合日 / next conjunction with the Sun. // NextConjunction 下一次合日 / next conjunction with the Sun.
// //
// 返回 date 之后最近一次与太阳的合日时刻,结果保持 date 的时区。 // 返回 date 当前或之后最近一次与太阳的合日时刻,结果保持 date 的时区。
// Returns the next conjunction with the Sun relative to date, keeping date's time zone.
func NextConjunction(date time.Time) time.Time { func NextConjunction(date time.Time) time.Time {
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true) jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
return basic.JDE2DateByZone(basic.NextNeptuneConjunction(jde), date.Location(), false) return basic.JDE2DateByZone(basic.NextNeptuneConjunction(jde), date.Location(), false)
@ -225,8 +223,7 @@ func NextConjunction(date time.Time) time.Time {
// LastOpposition 上一次冲日 / previous opposition. // LastOpposition 上一次冲日 / previous opposition.
// //
// 返回 date 之前最近一次冲日时刻,结果保持 date 的时区。 // 返回 date 当前或之前最近一次冲日时刻,结果保持 date 的时区。
// Returns the most recent opposition relative to date, keeping date's time zone.
func LastOpposition(date time.Time) time.Time { func LastOpposition(date time.Time) time.Time {
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true) jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
return basic.JDE2DateByZone(basic.LastNeptuneOpposition(jde), date.Location(), false) return basic.JDE2DateByZone(basic.LastNeptuneOpposition(jde), date.Location(), false)
@ -234,8 +231,7 @@ func LastOpposition(date time.Time) time.Time {
// NextOpposition 下一次冲日 / next opposition. // NextOpposition 下一次冲日 / next opposition.
// //
// 返回 date 之后最近一次冲日时刻,结果保持 date 的时区。 // 返回 date 当前或之后最近一次冲日时刻,结果保持 date 的时区。
// Returns the next opposition relative to date, keeping date's time zone.
func NextOpposition(date time.Time) time.Time { func NextOpposition(date time.Time) time.Time {
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true) jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
return basic.JDE2DateByZone(basic.NextNeptuneOpposition(jde), date.Location(), false) return basic.JDE2DateByZone(basic.NextNeptuneOpposition(jde), date.Location(), false)
@ -243,8 +239,7 @@ func NextOpposition(date time.Time) time.Time {
// LastProgradeToRetrograde 上一次顺行转逆行留 / previous station from prograde to retrograde. // LastProgradeToRetrograde 上一次顺行转逆行留 / previous station from prograde to retrograde.
// //
// 返回 date 之前最近一次由顺行转为逆行的留时刻,结果保持 date 的时区。 // 返回 date 当前或之前最近一次由顺行转为逆行的留时刻,结果保持 date 的时区。
// Returns the most recent stationary point where motion changes from prograde to retrograde relative to date, keeping date's time zone.
func LastProgradeToRetrograde(date time.Time) time.Time { func LastProgradeToRetrograde(date time.Time) time.Time {
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true) jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
return basic.JDE2DateByZone(basic.LastNeptuneProgradeToRetrograde(jde), date.Location(), false) return basic.JDE2DateByZone(basic.LastNeptuneProgradeToRetrograde(jde), date.Location(), false)
@ -252,8 +247,7 @@ func LastProgradeToRetrograde(date time.Time) time.Time {
// NextProgradeToRetrograde 下一次顺行转逆行留 / next station from prograde to retrograde. // NextProgradeToRetrograde 下一次顺行转逆行留 / next station from prograde to retrograde.
// //
// 返回 date 之后最近一次由顺行转为逆行的留时刻,结果保持 date 的时区。 // 返回 date 当前或之后最近一次由顺行转为逆行的留时刻,结果保持 date 的时区。
// Returns the next stationary point where motion changes from prograde to retrograde relative to date, keeping date's time zone.
func NextProgradeToRetrograde(date time.Time) time.Time { func NextProgradeToRetrograde(date time.Time) time.Time {
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true) jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
return basic.JDE2DateByZone(basic.NextNeptuneProgradeToRetrograde(jde), date.Location(), false) return basic.JDE2DateByZone(basic.NextNeptuneProgradeToRetrograde(jde), date.Location(), false)
@ -261,8 +255,7 @@ func NextProgradeToRetrograde(date time.Time) time.Time {
// LastRetrogradeToPrograde 上一次逆行转顺行留 / previous station from retrograde to prograde. // LastRetrogradeToPrograde 上一次逆行转顺行留 / previous station from retrograde to prograde.
// //
// 返回 date 之前最近一次由逆行转为顺行的留时刻,结果保持 date 的时区。 // 返回 date 当前或之前最近一次由逆行转为顺行的留时刻,结果保持 date 的时区。
// Returns the most recent stationary point where motion changes from retrograde to prograde relative to date, keeping date's time zone.
func LastRetrogradeToPrograde(date time.Time) time.Time { func LastRetrogradeToPrograde(date time.Time) time.Time {
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true) jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
return basic.JDE2DateByZone(basic.LastNeptuneRetrogradeToPrograde(jde), date.Location(), false) return basic.JDE2DateByZone(basic.LastNeptuneRetrogradeToPrograde(jde), date.Location(), false)
@ -270,8 +263,7 @@ func LastRetrogradeToPrograde(date time.Time) time.Time {
// NextRetrogradeToPrograde 下一次逆行转顺行留 / next station from retrograde to prograde. // NextRetrogradeToPrograde 下一次逆行转顺行留 / next station from retrograde to prograde.
// //
// 返回 date 之后最近一次由逆行转为顺行的留时刻,结果保持 date 的时区。 // 返回 date 当前或之后最近一次由逆行转为顺行的留时刻,结果保持 date 的时区。
// Returns the next stationary point where motion changes from retrograde to prograde relative to date, keeping date's time zone.
func NextRetrogradeToPrograde(date time.Time) time.Time { func NextRetrogradeToPrograde(date time.Time) time.Time {
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true) jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
return basic.JDE2DateByZone(basic.NextNeptuneRetrogradeToPrograde(jde), date.Location(), false) return basic.JDE2DateByZone(basic.NextNeptuneRetrogradeToPrograde(jde), date.Location(), false)
@ -279,8 +271,7 @@ func NextRetrogradeToPrograde(date time.Time) time.Time {
// LastEasternQuadrature 上一次东方照 / previous eastern quadrature. // LastEasternQuadrature 上一次东方照 / previous eastern quadrature.
// //
// 返回 date 之前最近一次东方照时刻,结果保持 date 的时区。 // 返回 date 当前或之前最近一次东方照时刻,结果保持 date 的时区。
// Returns the most recent eastern quadrature relative to date, keeping date's time zone.
func LastEasternQuadrature(date time.Time) time.Time { func LastEasternQuadrature(date time.Time) time.Time {
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true) jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
return basic.JDE2DateByZone(basic.LastNeptuneEasternQuadrature(jde), date.Location(), false) return basic.JDE2DateByZone(basic.LastNeptuneEasternQuadrature(jde), date.Location(), false)
@ -288,8 +279,7 @@ func LastEasternQuadrature(date time.Time) time.Time {
// NextEasternQuadrature 下一次东方照 / next eastern quadrature. // NextEasternQuadrature 下一次东方照 / next eastern quadrature.
// //
// 返回 date 之后最近一次东方照时刻,结果保持 date 的时区。 // 返回 date 当前或之后最近一次东方照时刻,结果保持 date 的时区。
// Returns the next eastern quadrature relative to date, keeping date's time zone.
func NextEasternQuadrature(date time.Time) time.Time { func NextEasternQuadrature(date time.Time) time.Time {
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true) jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
return basic.JDE2DateByZone(basic.NextNeptuneEasternQuadrature(jde), date.Location(), false) return basic.JDE2DateByZone(basic.NextNeptuneEasternQuadrature(jde), date.Location(), false)
@ -297,8 +287,7 @@ func NextEasternQuadrature(date time.Time) time.Time {
// LastWesternQuadrature 上一次西方照 / previous western quadrature. // LastWesternQuadrature 上一次西方照 / previous western quadrature.
// //
// 返回 date 之前最近一次西方照时刻,结果保持 date 的时区。 // 返回 date 当前或之前最近一次西方照时刻,结果保持 date 的时区。
// Returns the most recent western quadrature relative to date, keeping date's time zone.
func LastWesternQuadrature(date time.Time) time.Time { func LastWesternQuadrature(date time.Time) time.Time {
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true) jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
return basic.JDE2DateByZone(basic.LastNeptuneWesternQuadrature(jde), date.Location(), false) return basic.JDE2DateByZone(basic.LastNeptuneWesternQuadrature(jde), date.Location(), false)
@ -306,8 +295,7 @@ func LastWesternQuadrature(date time.Time) time.Time {
// NextWesternQuadrature 下一次西方照 / next western quadrature. // NextWesternQuadrature 下一次西方照 / next western quadrature.
// //
// 返回 date 之后最近一次西方照时刻,结果保持 date 的时区。 // 返回 date 当前或之后最近一次西方照时刻,结果保持 date 的时区。
// Returns the next western quadrature relative to date, keeping date's time zone.
func NextWesternQuadrature(date time.Time) time.Time { func NextWesternQuadrature(date time.Time) time.Time {
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true) jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
return basic.JDE2DateByZone(basic.NextNeptuneWesternQuadrature(jde), date.Location(), false) return basic.JDE2DateByZone(basic.NextNeptuneWesternQuadrature(jde), date.Location(), false)

View File

@ -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 {

View File

@ -207,8 +207,7 @@ func SetTime(date time.Time, lon, lat, height float64, aero bool) (time.Time, er
// LastConjunction 上一次合日 / previous conjunction with the Sun. // LastConjunction 上一次合日 / previous conjunction with the Sun.
// //
// 返回 date 之前最近一次与太阳的合日时刻,结果保持 date 的时区。 // 返回 date 当前或之前最近一次与太阳的合日时刻,结果保持 date 的时区。
// Returns the most recent conjunction with the Sun relative to date, keeping date's time zone.
func LastConjunction(date time.Time) time.Time { func LastConjunction(date time.Time) time.Time {
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true) jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
return basic.JDE2DateByZone(basic.LastSaturnConjunction(jde), date.Location(), false) return basic.JDE2DateByZone(basic.LastSaturnConjunction(jde), date.Location(), false)
@ -216,8 +215,7 @@ func LastConjunction(date time.Time) time.Time {
// NextConjunction 下一次合日 / next conjunction with the Sun. // NextConjunction 下一次合日 / next conjunction with the Sun.
// //
// 返回 date 之后最近一次与太阳的合日时刻,结果保持 date 的时区。 // 返回 date 当前或之后最近一次与太阳的合日时刻,结果保持 date 的时区。
// Returns the next conjunction with the Sun relative to date, keeping date's time zone.
func NextConjunction(date time.Time) time.Time { func NextConjunction(date time.Time) time.Time {
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true) jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
return basic.JDE2DateByZone(basic.NextSaturnConjunction(jde), date.Location(), false) return basic.JDE2DateByZone(basic.NextSaturnConjunction(jde), date.Location(), false)
@ -225,8 +223,7 @@ func NextConjunction(date time.Time) time.Time {
// LastOpposition 上一次冲日 / previous opposition. // LastOpposition 上一次冲日 / previous opposition.
// //
// 返回 date 之前最近一次冲日时刻,结果保持 date 的时区。 // 返回 date 当前或之前最近一次冲日时刻,结果保持 date 的时区。
// Returns the most recent opposition relative to date, keeping date's time zone.
func LastOpposition(date time.Time) time.Time { func LastOpposition(date time.Time) time.Time {
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true) jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
return basic.JDE2DateByZone(basic.LastSaturnOpposition(jde), date.Location(), false) return basic.JDE2DateByZone(basic.LastSaturnOpposition(jde), date.Location(), false)
@ -234,8 +231,7 @@ func LastOpposition(date time.Time) time.Time {
// NextOpposition 下一次冲日 / next opposition. // NextOpposition 下一次冲日 / next opposition.
// //
// 返回 date 之后最近一次冲日时刻,结果保持 date 的时区。 // 返回 date 当前或之后最近一次冲日时刻,结果保持 date 的时区。
// Returns the next opposition relative to date, keeping date's time zone.
func NextOpposition(date time.Time) time.Time { func NextOpposition(date time.Time) time.Time {
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true) jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
return basic.JDE2DateByZone(basic.NextSaturnOpposition(jde), date.Location(), false) return basic.JDE2DateByZone(basic.NextSaturnOpposition(jde), date.Location(), false)
@ -243,8 +239,7 @@ func NextOpposition(date time.Time) time.Time {
// LastProgradeToRetrograde 上一次顺行转逆行留 / previous station from prograde to retrograde. // LastProgradeToRetrograde 上一次顺行转逆行留 / previous station from prograde to retrograde.
// //
// 返回 date 之前最近一次由顺行转为逆行的留时刻,结果保持 date 的时区。 // 返回 date 当前或之前最近一次由顺行转为逆行的留时刻,结果保持 date 的时区。
// Returns the most recent stationary point where motion changes from prograde to retrograde relative to date, keeping date's time zone.
func LastProgradeToRetrograde(date time.Time) time.Time { func LastProgradeToRetrograde(date time.Time) time.Time {
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true) jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
return basic.JDE2DateByZone(basic.LastSaturnProgradeToRetrograde(jde), date.Location(), false) return basic.JDE2DateByZone(basic.LastSaturnProgradeToRetrograde(jde), date.Location(), false)
@ -252,8 +247,7 @@ func LastProgradeToRetrograde(date time.Time) time.Time {
// NextProgradeToRetrograde 下一次顺行转逆行留 / next station from prograde to retrograde. // NextProgradeToRetrograde 下一次顺行转逆行留 / next station from prograde to retrograde.
// //
// 返回 date 之后最近一次由顺行转为逆行的留时刻,结果保持 date 的时区。 // 返回 date 当前或之后最近一次由顺行转为逆行的留时刻,结果保持 date 的时区。
// Returns the next stationary point where motion changes from prograde to retrograde relative to date, keeping date's time zone.
func NextProgradeToRetrograde(date time.Time) time.Time { func NextProgradeToRetrograde(date time.Time) time.Time {
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true) jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
return basic.JDE2DateByZone(basic.NextSaturnProgradeToRetrograde(jde), date.Location(), false) return basic.JDE2DateByZone(basic.NextSaturnProgradeToRetrograde(jde), date.Location(), false)
@ -261,8 +255,7 @@ func NextProgradeToRetrograde(date time.Time) time.Time {
// LastRetrogradeToPrograde 上一次逆行转顺行留 / previous station from retrograde to prograde. // LastRetrogradeToPrograde 上一次逆行转顺行留 / previous station from retrograde to prograde.
// //
// 返回 date 之前最近一次由逆行转为顺行的留时刻,结果保持 date 的时区。 // 返回 date 当前或之前最近一次由逆行转为顺行的留时刻,结果保持 date 的时区。
// Returns the most recent stationary point where motion changes from retrograde to prograde relative to date, keeping date's time zone.
func LastRetrogradeToPrograde(date time.Time) time.Time { func LastRetrogradeToPrograde(date time.Time) time.Time {
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true) jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
return basic.JDE2DateByZone(basic.LastSaturnRetrogradeToPrograde(jde), date.Location(), false) return basic.JDE2DateByZone(basic.LastSaturnRetrogradeToPrograde(jde), date.Location(), false)
@ -270,8 +263,7 @@ func LastRetrogradeToPrograde(date time.Time) time.Time {
// NextRetrogradeToPrograde 下一次逆行转顺行留 / next station from retrograde to prograde. // NextRetrogradeToPrograde 下一次逆行转顺行留 / next station from retrograde to prograde.
// //
// 返回 date 之后最近一次由逆行转为顺行的留时刻,结果保持 date 的时区。 // 返回 date 当前或之后最近一次由逆行转为顺行的留时刻,结果保持 date 的时区。
// Returns the next stationary point where motion changes from retrograde to prograde relative to date, keeping date's time zone.
func NextRetrogradeToPrograde(date time.Time) time.Time { func NextRetrogradeToPrograde(date time.Time) time.Time {
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true) jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
return basic.JDE2DateByZone(basic.NextSaturnRetrogradeToPrograde(jde), date.Location(), false) return basic.JDE2DateByZone(basic.NextSaturnRetrogradeToPrograde(jde), date.Location(), false)
@ -279,8 +271,7 @@ func NextRetrogradeToPrograde(date time.Time) time.Time {
// LastEasternQuadrature 上一次东方照 / previous eastern quadrature. // LastEasternQuadrature 上一次东方照 / previous eastern quadrature.
// //
// 返回 date 之前最近一次东方照时刻,结果保持 date 的时区。 // 返回 date 当前或之前最近一次东方照时刻,结果保持 date 的时区。
// Returns the most recent eastern quadrature relative to date, keeping date's time zone.
func LastEasternQuadrature(date time.Time) time.Time { func LastEasternQuadrature(date time.Time) time.Time {
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true) jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
return basic.JDE2DateByZone(basic.LastSaturnEasternQuadrature(jde), date.Location(), false) return basic.JDE2DateByZone(basic.LastSaturnEasternQuadrature(jde), date.Location(), false)
@ -288,8 +279,7 @@ func LastEasternQuadrature(date time.Time) time.Time {
// NextEasternQuadrature 下一次东方照 / next eastern quadrature. // NextEasternQuadrature 下一次东方照 / next eastern quadrature.
// //
// 返回 date 之后最近一次东方照时刻,结果保持 date 的时区。 // 返回 date 当前或之后最近一次东方照时刻,结果保持 date 的时区。
// Returns the next eastern quadrature relative to date, keeping date's time zone.
func NextEasternQuadrature(date time.Time) time.Time { func NextEasternQuadrature(date time.Time) time.Time {
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true) jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
return basic.JDE2DateByZone(basic.NextSaturnEasternQuadrature(jde), date.Location(), false) return basic.JDE2DateByZone(basic.NextSaturnEasternQuadrature(jde), date.Location(), false)
@ -297,8 +287,7 @@ func NextEasternQuadrature(date time.Time) time.Time {
// LastWesternQuadrature 上一次西方照 / previous western quadrature. // LastWesternQuadrature 上一次西方照 / previous western quadrature.
// //
// 返回 date 之前最近一次西方照时刻,结果保持 date 的时区。 // 返回 date 当前或之前最近一次西方照时刻,结果保持 date 的时区。
// Returns the most recent western quadrature relative to date, keeping date's time zone.
func LastWesternQuadrature(date time.Time) time.Time { func LastWesternQuadrature(date time.Time) time.Time {
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true) jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
return basic.JDE2DateByZone(basic.LastSaturnWesternQuadrature(jde), date.Location(), false) return basic.JDE2DateByZone(basic.LastSaturnWesternQuadrature(jde), date.Location(), false)
@ -306,8 +295,7 @@ func LastWesternQuadrature(date time.Time) time.Time {
// NextWesternQuadrature 下一次西方照 / next western quadrature. // NextWesternQuadrature 下一次西方照 / next western quadrature.
// //
// 返回 date 之后最近一次西方照时刻,结果保持 date 的时区。 // 返回 date 当前或之后最近一次西方照时刻,结果保持 date 的时区。
// Returns the next western quadrature relative to date, keeping date's time zone.
func NextWesternQuadrature(date time.Time) time.Time { func NextWesternQuadrature(date time.Time) time.Time {
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true) jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
return basic.JDE2DateByZone(basic.NextSaturnWesternQuadrature(jde), date.Location(), false) return basic.JDE2DateByZone(basic.NextSaturnWesternQuadrature(jde), date.Location(), false)

View File

@ -207,8 +207,7 @@ func SetTime(date time.Time, lon, lat, height float64, aero bool) (time.Time, er
// LastConjunction 上一次合日 / previous conjunction with the Sun. // LastConjunction 上一次合日 / previous conjunction with the Sun.
// //
// 返回 date 之前最近一次与太阳的合日时刻,结果保持 date 的时区。 // 返回 date 当前或之前最近一次与太阳的合日时刻,结果保持 date 的时区。
// Returns the most recent conjunction with the Sun relative to date, keeping date's time zone.
func LastConjunction(date time.Time) time.Time { func LastConjunction(date time.Time) time.Time {
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true) jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
return basic.JDE2DateByZone(basic.LastUranusConjunction(jde), date.Location(), false) return basic.JDE2DateByZone(basic.LastUranusConjunction(jde), date.Location(), false)
@ -216,8 +215,7 @@ func LastConjunction(date time.Time) time.Time {
// NextConjunction 下一次合日 / next conjunction with the Sun. // NextConjunction 下一次合日 / next conjunction with the Sun.
// //
// 返回 date 之后最近一次与太阳的合日时刻,结果保持 date 的时区。 // 返回 date 当前或之后最近一次与太阳的合日时刻,结果保持 date 的时区。
// Returns the next conjunction with the Sun relative to date, keeping date's time zone.
func NextConjunction(date time.Time) time.Time { func NextConjunction(date time.Time) time.Time {
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true) jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
return basic.JDE2DateByZone(basic.NextUranusConjunction(jde), date.Location(), false) return basic.JDE2DateByZone(basic.NextUranusConjunction(jde), date.Location(), false)
@ -225,8 +223,7 @@ func NextConjunction(date time.Time) time.Time {
// LastOpposition 上一次冲日 / previous opposition. // LastOpposition 上一次冲日 / previous opposition.
// //
// 返回 date 之前最近一次冲日时刻,结果保持 date 的时区。 // 返回 date 当前或之前最近一次冲日时刻,结果保持 date 的时区。
// Returns the most recent opposition relative to date, keeping date's time zone.
func LastOpposition(date time.Time) time.Time { func LastOpposition(date time.Time) time.Time {
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true) jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
return basic.JDE2DateByZone(basic.LastUranusOpposition(jde), date.Location(), false) return basic.JDE2DateByZone(basic.LastUranusOpposition(jde), date.Location(), false)
@ -234,8 +231,7 @@ func LastOpposition(date time.Time) time.Time {
// NextOpposition 下一次冲日 / next opposition. // NextOpposition 下一次冲日 / next opposition.
// //
// 返回 date 之后最近一次冲日时刻,结果保持 date 的时区。 // 返回 date 当前或之后最近一次冲日时刻,结果保持 date 的时区。
// Returns the next opposition relative to date, keeping date's time zone.
func NextOpposition(date time.Time) time.Time { func NextOpposition(date time.Time) time.Time {
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true) jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
return basic.JDE2DateByZone(basic.NextUranusOpposition(jde), date.Location(), false) return basic.JDE2DateByZone(basic.NextUranusOpposition(jde), date.Location(), false)
@ -243,8 +239,7 @@ func NextOpposition(date time.Time) time.Time {
// LastProgradeToRetrograde 上一次顺行转逆行留 / previous station from prograde to retrograde. // LastProgradeToRetrograde 上一次顺行转逆行留 / previous station from prograde to retrograde.
// //
// 返回 date 之前最近一次由顺行转为逆行的留时刻,结果保持 date 的时区。 // 返回 date 当前或之前最近一次由顺行转为逆行的留时刻,结果保持 date 的时区。
// Returns the most recent stationary point where motion changes from prograde to retrograde relative to date, keeping date's time zone.
func LastProgradeToRetrograde(date time.Time) time.Time { func LastProgradeToRetrograde(date time.Time) time.Time {
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true) jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
return basic.JDE2DateByZone(basic.LastUranusProgradeToRetrograde(jde), date.Location(), false) return basic.JDE2DateByZone(basic.LastUranusProgradeToRetrograde(jde), date.Location(), false)
@ -252,8 +247,7 @@ func LastProgradeToRetrograde(date time.Time) time.Time {
// NextProgradeToRetrograde 下一次顺行转逆行留 / next station from prograde to retrograde. // NextProgradeToRetrograde 下一次顺行转逆行留 / next station from prograde to retrograde.
// //
// 返回 date 之后最近一次由顺行转为逆行的留时刻,结果保持 date 的时区。 // 返回 date 当前或之后最近一次由顺行转为逆行的留时刻,结果保持 date 的时区。
// Returns the next stationary point where motion changes from prograde to retrograde relative to date, keeping date's time zone.
func NextProgradeToRetrograde(date time.Time) time.Time { func NextProgradeToRetrograde(date time.Time) time.Time {
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true) jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
return basic.JDE2DateByZone(basic.NextUranusProgradeToRetrograde(jde), date.Location(), false) return basic.JDE2DateByZone(basic.NextUranusProgradeToRetrograde(jde), date.Location(), false)
@ -261,8 +255,7 @@ func NextProgradeToRetrograde(date time.Time) time.Time {
// LastRetrogradeToPrograde 上一次逆行转顺行留 / previous station from retrograde to prograde. // LastRetrogradeToPrograde 上一次逆行转顺行留 / previous station from retrograde to prograde.
// //
// 返回 date 之前最近一次由逆行转为顺行的留时刻,结果保持 date 的时区。 // 返回 date 当前或之前最近一次由逆行转为顺行的留时刻,结果保持 date 的时区。
// Returns the most recent stationary point where motion changes from retrograde to prograde relative to date, keeping date's time zone.
func LastRetrogradeToPrograde(date time.Time) time.Time { func LastRetrogradeToPrograde(date time.Time) time.Time {
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true) jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
return basic.JDE2DateByZone(basic.LastUranusRetrogradeToPrograde(jde), date.Location(), false) return basic.JDE2DateByZone(basic.LastUranusRetrogradeToPrograde(jde), date.Location(), false)
@ -270,8 +263,7 @@ func LastRetrogradeToPrograde(date time.Time) time.Time {
// NextRetrogradeToPrograde 下一次逆行转顺行留 / next station from retrograde to prograde. // NextRetrogradeToPrograde 下一次逆行转顺行留 / next station from retrograde to prograde.
// //
// 返回 date 之后最近一次由逆行转为顺行的留时刻,结果保持 date 的时区。 // 返回 date 当前或之后最近一次由逆行转为顺行的留时刻,结果保持 date 的时区。
// Returns the next stationary point where motion changes from retrograde to prograde relative to date, keeping date's time zone.
func NextRetrogradeToPrograde(date time.Time) time.Time { func NextRetrogradeToPrograde(date time.Time) time.Time {
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true) jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
return basic.JDE2DateByZone(basic.NextUranusRetrogradeToPrograde(jde), date.Location(), false) return basic.JDE2DateByZone(basic.NextUranusRetrogradeToPrograde(jde), date.Location(), false)
@ -279,8 +271,7 @@ func NextRetrogradeToPrograde(date time.Time) time.Time {
// LastEasternQuadrature 上一次东方照 / previous eastern quadrature. // LastEasternQuadrature 上一次东方照 / previous eastern quadrature.
// //
// 返回 date 之前最近一次东方照时刻,结果保持 date 的时区。 // 返回 date 当前或之前最近一次东方照时刻,结果保持 date 的时区。
// Returns the most recent eastern quadrature relative to date, keeping date's time zone.
func LastEasternQuadrature(date time.Time) time.Time { func LastEasternQuadrature(date time.Time) time.Time {
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true) jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
return basic.JDE2DateByZone(basic.LastUranusEasternQuadrature(jde), date.Location(), false) return basic.JDE2DateByZone(basic.LastUranusEasternQuadrature(jde), date.Location(), false)
@ -288,8 +279,7 @@ func LastEasternQuadrature(date time.Time) time.Time {
// NextEasternQuadrature 下一次东方照 / next eastern quadrature. // NextEasternQuadrature 下一次东方照 / next eastern quadrature.
// //
// 返回 date 之后最近一次东方照时刻,结果保持 date 的时区。 // 返回 date 当前或之后最近一次东方照时刻,结果保持 date 的时区。
// Returns the next eastern quadrature relative to date, keeping date's time zone.
func NextEasternQuadrature(date time.Time) time.Time { func NextEasternQuadrature(date time.Time) time.Time {
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true) jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
return basic.JDE2DateByZone(basic.NextUranusEasternQuadrature(jde), date.Location(), false) return basic.JDE2DateByZone(basic.NextUranusEasternQuadrature(jde), date.Location(), false)
@ -297,8 +287,7 @@ func NextEasternQuadrature(date time.Time) time.Time {
// LastWesternQuadrature 上一次西方照 / previous western quadrature. // LastWesternQuadrature 上一次西方照 / previous western quadrature.
// //
// 返回 date 之前最近一次西方照时刻,结果保持 date 的时区。 // 返回 date 当前或之前最近一次西方照时刻,结果保持 date 的时区。
// Returns the most recent western quadrature relative to date, keeping date's time zone.
func LastWesternQuadrature(date time.Time) time.Time { func LastWesternQuadrature(date time.Time) time.Time {
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true) jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
return basic.JDE2DateByZone(basic.LastUranusWesternQuadrature(jde), date.Location(), false) return basic.JDE2DateByZone(basic.LastUranusWesternQuadrature(jde), date.Location(), false)
@ -306,8 +295,7 @@ func LastWesternQuadrature(date time.Time) time.Time {
// NextWesternQuadrature 下一次西方照 / next western quadrature. // NextWesternQuadrature 下一次西方照 / next western quadrature.
// //
// 返回 date 之后最近一次西方照时刻,结果保持 date 的时区。 // 返回 date 当前或之后最近一次西方照时刻,结果保持 date 的时区。
// Returns the next western quadrature relative to date, keeping date's time zone.
func NextWesternQuadrature(date time.Time) time.Time { func NextWesternQuadrature(date time.Time) time.Time {
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true) jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
return basic.JDE2DateByZone(basic.NextUranusWesternQuadrature(jde), date.Location(), false) return basic.JDE2DateByZone(basic.NextUranusWesternQuadrature(jde), date.Location(), false)

View File

@ -207,8 +207,7 @@ func SetTime(date time.Time, lon, lat, height float64, aero bool) (time.Time, er
// LastConjunction 上一次合日 / previous conjunction with the Sun. // LastConjunction 上一次合日 / previous conjunction with the Sun.
// //
// 返回 date 之前最近一次与太阳的合日时刻,结果保持 date 的时区。 // 返回 date 当前或之前最近一次与太阳的合日时刻,结果保持 date 的时区。
// Returns the most recent conjunction with the Sun relative to date, keeping date's time zone.
func LastConjunction(date time.Time) time.Time { func LastConjunction(date time.Time) time.Time {
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true) jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
return basic.JDE2DateByZone(basic.LastVenusConjunction(jde), date.Location(), false) return basic.JDE2DateByZone(basic.LastVenusConjunction(jde), date.Location(), false)
@ -216,8 +215,7 @@ func LastConjunction(date time.Time) time.Time {
// NextConjunction 下一次合日 / next conjunction with the Sun. // NextConjunction 下一次合日 / next conjunction with the Sun.
// //
// 返回 date 之后最近一次与太阳的合日时刻,结果保持 date 的时区。 // 返回 date 当前或之后最近一次与太阳的合日时刻,结果保持 date 的时区。
// Returns the next conjunction with the Sun relative to date, keeping date's time zone.
func NextConjunction(date time.Time) time.Time { func NextConjunction(date time.Time) time.Time {
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true) jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
return basic.JDE2DateByZone(basic.NextVenusConjunction(jde), date.Location(), false) return basic.JDE2DateByZone(basic.NextVenusConjunction(jde), date.Location(), false)
@ -225,144 +223,128 @@ func NextConjunction(date time.Time) time.Time {
// LastInferiorConjunction 上一次下合 / previous inferior conjunction. // LastInferiorConjunction 上一次下合 / previous inferior conjunction.
// //
// 返回 date 之前最近一次下合时刻,结果保持 date 的时区。 // 返回 date 当前或之前最近一次下合时刻,结果保持 date 的时区。
// Returns the most recent inferior conjunction relative to date, keeping date's time zone.
func LastInferiorConjunction(date time.Time) time.Time { func LastInferiorConjunction(date time.Time) time.Time {
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true) jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
return basic.JDE2DateByZone(basic.LastVenusInferiorConjunction(jde), date.Location(), false) return basic.JDE2DateByZone(basic.LastVenusInferiorConjunctionInclusive(jde), date.Location(), false)
} }
// NextInferiorConjunction 下一次下合 / next inferior conjunction. // NextInferiorConjunction 下一次下合 / next inferior conjunction.
// //
// 返回 date 之后最近一次下合时刻,结果保持 date 的时区。 // 返回 date 当前或之后最近一次下合时刻,结果保持 date 的时区。
// Returns the next inferior conjunction relative to date, keeping date's time zone.
func NextInferiorConjunction(date time.Time) time.Time { func NextInferiorConjunction(date time.Time) time.Time {
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true) jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
return basic.JDE2DateByZone(basic.NextVenusInferiorConjunction(jde), date.Location(), false) return basic.JDE2DateByZone(basic.NextVenusInferiorConjunctionInclusive(jde), date.Location(), false)
} }
// LastSuperiorConjunction 上一次上合 / previous superior conjunction. // LastSuperiorConjunction 上一次上合 / previous superior conjunction.
// //
// 返回 date 之前最近一次上合时刻,结果保持 date 的时区。 // 返回 date 当前或之前最近一次上合时刻,结果保持 date 的时区。
// Returns the most recent superior conjunction relative to date, keeping date's time zone.
func LastSuperiorConjunction(date time.Time) time.Time { func LastSuperiorConjunction(date time.Time) time.Time {
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true) jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
return basic.JDE2DateByZone(basic.LastVenusSuperiorConjunction(jde), date.Location(), false) return basic.JDE2DateByZone(basic.LastVenusSuperiorConjunctionInclusive(jde), date.Location(), false)
} }
// NextSuperiorConjunction 下一次上合 / next superior conjunction. // NextSuperiorConjunction 下一次上合 / next superior conjunction.
// //
// 返回 date 之后最近一次上合时刻,结果保持 date 的时区。 // 返回 date 当前或之后最近一次上合时刻,结果保持 date 的时区。
// Returns the next superior conjunction relative to date, keeping date's time zone.
func NextSuperiorConjunction(date time.Time) time.Time { func NextSuperiorConjunction(date time.Time) time.Time {
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true) jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
return basic.JDE2DateByZone(basic.NextVenusSuperiorConjunction(jde), date.Location(), false) return basic.JDE2DateByZone(basic.NextVenusSuperiorConjunctionInclusive(jde), date.Location(), false)
} }
// LastRetrograde 上一次留 / previous stationary point. // LastRetrograde 上一次留 / previous stationary point.
// //
// 返回 date 之前最近一次留时刻,不区分顺转逆还是逆转顺,结果保持 date 的时区。 // 返回 date 当前或之前最近一次留时刻,不区分顺转逆还是逆转顺,结果保持 date 的时区。
// Returns the most recent stationary point relative to date without distinguishing direction, keeping date's time zone.
func LastRetrograde(date time.Time) time.Time { func LastRetrograde(date time.Time) time.Time {
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true) jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
return basic.JDE2DateByZone(basic.LastVenusRetrograde(jde), date.Location(), false) return basic.JDE2DateByZone(basic.LastVenusRetrogradeInclusive(jde), date.Location(), false)
} }
// NextRetrograde 下一次留 / next stationary point. // NextRetrograde 下一次留 / next stationary point.
// //
// 返回 date 之后最近一次留时刻,不区分顺转逆还是逆转顺,结果保持 date 的时区。 // 返回 date 当前或之后最近一次留时刻,不区分顺转逆还是逆转顺,结果保持 date 的时区。
// Returns the next stationary point relative to date without distinguishing direction, keeping date's time zone.
func NextRetrograde(date time.Time) time.Time { func NextRetrograde(date time.Time) time.Time {
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true) jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
return basic.JDE2DateByZone(basic.NextVenusRetrograde(jde), date.Location(), false) return basic.JDE2DateByZone(basic.NextVenusRetrogradeInclusive(jde), date.Location(), false)
} }
// LastProgradeToRetrograde 上一次顺行转逆行留 / previous station from prograde to retrograde. // LastProgradeToRetrograde 上一次顺行转逆行留 / previous station from prograde to retrograde.
// //
// 返回 date 之前最近一次由顺行转为逆行的留时刻,结果保持 date 的时区。 // 返回 date 当前或之前最近一次由顺行转为逆行的留时刻,结果保持 date 的时区。
// Returns the most recent stationary point where motion changes from prograde to retrograde relative to date, keeping date's time zone.
func LastProgradeToRetrograde(date time.Time) time.Time { func LastProgradeToRetrograde(date time.Time) time.Time {
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true) jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
return basic.JDE2DateByZone(basic.LastVenusProgradeToRetrograde(jde), date.Location(), false) return basic.JDE2DateByZone(basic.LastVenusProgradeToRetrogradeInclusive(jde), date.Location(), false)
} }
// NextProgradeToRetrograde 下一次顺行转逆行留 / next station from prograde to retrograde. // NextProgradeToRetrograde 下一次顺行转逆行留 / next station from prograde to retrograde.
// //
// 返回 date 之后最近一次由顺行转为逆行的留时刻,结果保持 date 的时区。 // 返回 date 当前或之后最近一次由顺行转为逆行的留时刻,结果保持 date 的时区。
// Returns the next stationary point where motion changes from prograde to retrograde relative to date, keeping date's time zone.
func NextProgradeToRetrograde(date time.Time) time.Time { func NextProgradeToRetrograde(date time.Time) time.Time {
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true) jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
return basic.JDE2DateByZone(basic.NextVenusProgradeToRetrograde(jde), date.Location(), false) return basic.JDE2DateByZone(basic.NextVenusProgradeToRetrogradeInclusive(jde), date.Location(), false)
} }
// LastRetrogradeToPrograde 上一次逆行转顺行留 / previous station from retrograde to prograde. // LastRetrogradeToPrograde 上一次逆行转顺行留 / previous station from retrograde to prograde.
// //
// 返回 date 之前最近一次由逆行转为顺行的留时刻,结果保持 date 的时区。 // 返回 date 当前或之前最近一次由逆行转为顺行的留时刻,结果保持 date 的时区。
// Returns the most recent stationary point where motion changes from retrograde to prograde relative to date, keeping date's time zone.
func LastRetrogradeToPrograde(date time.Time) time.Time { func LastRetrogradeToPrograde(date time.Time) time.Time {
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true) jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
return basic.JDE2DateByZone(basic.LastVenusRetrogradeToPrograde(jde), date.Location(), false) return basic.JDE2DateByZone(basic.LastVenusRetrogradeToProgradeInclusive(jde), date.Location(), false)
} }
// NextRetrogradeToPrograde 下一次逆行转顺行留 / next station from retrograde to prograde. // NextRetrogradeToPrograde 下一次逆行转顺行留 / next station from retrograde to prograde.
// //
// 返回 date 之后最近一次由逆行转为顺行的留时刻,结果保持 date 的时区。 // 返回 date 当前或之后最近一次由逆行转为顺行的留时刻,结果保持 date 的时区。
// Returns the next stationary point where motion changes from retrograde to prograde relative to date, keeping date's time zone.
func NextRetrogradeToPrograde(date time.Time) time.Time { func NextRetrogradeToPrograde(date time.Time) time.Time {
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true) jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
return basic.JDE2DateByZone(basic.NextVenusRetrogradeToPrograde(jde), date.Location(), false) return basic.JDE2DateByZone(basic.NextVenusRetrogradeToProgradeInclusive(jde), date.Location(), false)
} }
// LastGreatestElongation 上一次大距 / previous greatest elongation. // LastGreatestElongation 上一次大距 / previous greatest elongation.
// //
// 返回 date 之前最近一次大距时刻,不区分东西大距,结果保持 date 的时区。 // 返回 date 当前或之前最近一次大距时刻,不区分东西大距,结果保持 date 的时区。
// Returns the most recent greatest elongation relative to date without distinguishing east or west, keeping date's time zone.
func LastGreatestElongation(date time.Time) time.Time { func LastGreatestElongation(date time.Time) time.Time {
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true) jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
return basic.JDE2DateByZone(basic.LastVenusGreatestElongation(jde), date.Location(), false) return basic.JDE2DateByZone(basic.LastVenusGreatestElongationInclusive(jde), date.Location(), false)
} }
// NextGreatestElongation 下一次大距 / next greatest elongation. // NextGreatestElongation 下一次大距 / next greatest elongation.
// //
// 返回 date 之后最近一次大距时刻,不区分东西大距,结果保持 date 的时区。 // 返回 date 当前或之后最近一次大距时刻,不区分东西大距,结果保持 date 的时区。
// Returns the next greatest elongation relative to date without distinguishing east or west, keeping date's time zone.
func NextGreatestElongation(date time.Time) time.Time { func NextGreatestElongation(date time.Time) time.Time {
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true) jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
return basic.JDE2DateByZone(basic.NextVenusGreatestElongation(jde), date.Location(), false) return basic.JDE2DateByZone(basic.NextVenusGreatestElongationInclusive(jde), date.Location(), false)
} }
// LastGreatestElongationEast 上一次东大距 / previous greatest eastern elongation. // LastGreatestElongationEast 上一次东大距 / previous greatest eastern elongation.
// //
// 返回 date 之前最近一次东大距时刻,结果保持 date 的时区。 // 返回 date 当前或之前最近一次东大距时刻,结果保持 date 的时区。
// Returns the most recent greatest eastern elongation relative to date, keeping date's time zone.
func LastGreatestElongationEast(date time.Time) time.Time { func LastGreatestElongationEast(date time.Time) time.Time {
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true) jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
return basic.JDE2DateByZone(basic.LastVenusGreatestElongationEast(jde), date.Location(), false) return basic.JDE2DateByZone(basic.LastVenusGreatestElongationEastInclusive(jde), date.Location(), false)
} }
// NextGreatestElongationEast 下一次东大距 / next greatest eastern elongation. // NextGreatestElongationEast 下一次东大距 / next greatest eastern elongation.
// //
// 返回 date 之后最近一次东大距时刻,结果保持 date 的时区。 // 返回 date 当前或之后最近一次东大距时刻,结果保持 date 的时区。
// Returns the next greatest eastern elongation relative to date, keeping date's time zone.
func NextGreatestElongationEast(date time.Time) time.Time { func NextGreatestElongationEast(date time.Time) time.Time {
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true) jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
return basic.JDE2DateByZone(basic.NextVenusGreatestElongationEast(jde), date.Location(), false) return basic.JDE2DateByZone(basic.NextVenusGreatestElongationEastInclusive(jde), date.Location(), false)
} }
// LastGreatestElongationWest 上一次西大距 / previous greatest western elongation. // LastGreatestElongationWest 上一次西大距 / previous greatest western elongation.
// //
// 返回 date 之前最近一次西大距时刻,结果保持 date 的时区。 // 返回 date 当前或之前最近一次西大距时刻,结果保持 date 的时区。
// Returns the most recent greatest western elongation relative to date, keeping date's time zone.
func LastGreatestElongationWest(date time.Time) time.Time { func LastGreatestElongationWest(date time.Time) time.Time {
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true) jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
return basic.JDE2DateByZone(basic.LastVenusGreatestElongationWest(jde), date.Location(), false) return basic.JDE2DateByZone(basic.LastVenusGreatestElongationWestInclusive(jde), date.Location(), false)
} }
// NextGreatestElongationWest 下一次西大距 / next greatest western elongation. // NextGreatestElongationWest 下一次西大距 / next greatest western elongation.
// //
// 返回 date 之后最近一次西大距时刻,结果保持 date 的时区。 // 返回 date 当前或之后最近一次西大距时刻,结果保持 date 的时区。
// Returns the next greatest western elongation relative to date, keeping date's time zone.
func NextGreatestElongationWest(date time.Time) time.Time { func NextGreatestElongationWest(date time.Time) time.Time {
jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true) jde := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
return basic.JDE2DateByZone(basic.NextVenusGreatestElongationWest(jde), date.Location(), false) return basic.JDE2DateByZone(basic.NextVenusGreatestElongationWestInclusive(jde), date.Location(), false)
} }