Compare commits
34 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
25dc7ac0bc
|
|||
|
a8e7513683
|
|||
|
c8dd777a7b
|
|||
|
46b555cd49
|
|||
|
be3af3884c
|
|||
|
34ff6a36ae
|
|||
|
d40c4dfcd9
|
|||
|
bec7b8a0d8
|
|||
|
3ffdbe0034
|
|||
|
98ff574495
|
|||
|
dadbba7171
|
|||
|
add07bbd85
|
|||
|
27b7e4ab77
|
|||
|
0ab91bcd2d
|
|||
|
543abcafa5
|
|||
|
9f688024e8
|
|||
|
b865e64fa9
|
|||
|
3389c33cb5
|
|||
|
d6b6452304
|
|||
|
438f1700c7
|
|||
|
126cf68cab
|
|||
|
4302981518
|
|||
| 94aeb84da5 | |||
| 1952df0c30 | |||
| b0920d327c | |||
| 616cd54222 | |||
| d479d39352 | |||
| 460e042aa9 | |||
| c2c79c3615 | |||
| 2db30bfd92 | |||
| dbe0fe1229 | |||
| 7a317ff1af | |||
| 6ff76468b4 | |||
| 6b97736829 |
@@ -0,0 +1 @@
|
|||||||
|
.idea
|
||||||
Generated
-8
@@ -1,8 +0,0 @@
|
|||||||
# 默认忽略的文件
|
|
||||||
/shelf/
|
|
||||||
/workspace.xml
|
|
||||||
# 数据源本地存储已忽略文件
|
|
||||||
/../../../../../../:\gocode\src\b612.me\astro\.idea/dataSources/
|
|
||||||
/dataSources.local.xml
|
|
||||||
# 基于编辑器的 HTTP 客户端请求
|
|
||||||
/httpRequests/
|
|
||||||
Generated
-9
@@ -1,9 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<module type="WEB_MODULE" version="4">
|
|
||||||
<component name="Go" enabled="true" />
|
|
||||||
<component name="NewModuleRootManager">
|
|
||||||
<content url="file://$MODULE_DIR$" />
|
|
||||||
<orderEntry type="inheritedJdk" />
|
|
||||||
<orderEntry type="sourceFolder" forTests="false" />
|
|
||||||
</component>
|
|
||||||
</module>
|
|
||||||
Generated
-8
@@ -1,8 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<project version="4">
|
|
||||||
<component name="ProjectModuleManager">
|
|
||||||
<modules>
|
|
||||||
<module fileurl="file://$PROJECT_DIR$/.idea/astro.iml" filepath="$PROJECT_DIR$/.idea/astro.iml" />
|
|
||||||
</modules>
|
|
||||||
</component>
|
|
||||||
</project>
|
|
||||||
Generated
-6
@@ -1,6 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<project version="4">
|
|
||||||
<component name="VcsDirectoryMappings">
|
|
||||||
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
|
||||||
</component>
|
|
||||||
</project>
|
|
||||||
@@ -0,0 +1,201 @@
|
|||||||
|
Apache License
|
||||||
|
Version 2.0, January 2004
|
||||||
|
http://www.apache.org/licenses/
|
||||||
|
|
||||||
|
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||||
|
|
||||||
|
1. Definitions.
|
||||||
|
|
||||||
|
"License" shall mean the terms and conditions for use, reproduction,
|
||||||
|
and distribution as defined by Sections 1 through 9 of this document.
|
||||||
|
|
||||||
|
"Licensor" shall mean the copyright owner or entity authorized by
|
||||||
|
the copyright owner that is granting the License.
|
||||||
|
|
||||||
|
"Legal Entity" shall mean the union of the acting entity and all
|
||||||
|
other entities that control, are controlled by, or are under common
|
||||||
|
control with that entity. For the purposes of this definition,
|
||||||
|
"control" means (i) the power, direct or indirect, to cause the
|
||||||
|
direction or management of such entity, whether by contract or
|
||||||
|
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||||
|
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||||
|
|
||||||
|
"You" (or "Your") shall mean an individual or Legal Entity
|
||||||
|
exercising permissions granted by this License.
|
||||||
|
|
||||||
|
"Source" form shall mean the preferred form for making modifications,
|
||||||
|
including but not limited to software source code, documentation
|
||||||
|
source, and configuration files.
|
||||||
|
|
||||||
|
"Object" form shall mean any form resulting from mechanical
|
||||||
|
transformation or translation of a Source form, including but
|
||||||
|
not limited to compiled object code, generated documentation,
|
||||||
|
and conversions to other media types.
|
||||||
|
|
||||||
|
"Work" shall mean the work of authorship, whether in Source or
|
||||||
|
Object form, made available under the License, as indicated by a
|
||||||
|
copyright notice that is included in or attached to the work
|
||||||
|
(an example is provided in the Appendix below).
|
||||||
|
|
||||||
|
"Derivative Works" shall mean any work, whether in Source or Object
|
||||||
|
form, that is based on (or derived from) the Work and for which the
|
||||||
|
editorial revisions, annotations, elaborations, or other modifications
|
||||||
|
represent, as a whole, an original work of authorship. For the purposes
|
||||||
|
of this License, Derivative Works shall not include works that remain
|
||||||
|
separable from, or merely link (or bind by name) to the interfaces of,
|
||||||
|
the Work and Derivative Works thereof.
|
||||||
|
|
||||||
|
"Contribution" shall mean any work of authorship, including
|
||||||
|
the original version of the Work and any modifications or additions
|
||||||
|
to that Work or Derivative Works thereof, that is intentionally
|
||||||
|
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||||
|
or by an individual or Legal Entity authorized to submit on behalf of
|
||||||
|
the copyright owner. For the purposes of this definition, "submitted"
|
||||||
|
means any form of electronic, verbal, or written communication sent
|
||||||
|
to the Licensor or its representatives, including but not limited to
|
||||||
|
communication on electronic mailing lists, source code control systems,
|
||||||
|
and issue tracking systems that are managed by, or on behalf of, the
|
||||||
|
Licensor for the purpose of discussing and improving the Work, but
|
||||||
|
excluding communication that is conspicuously marked or otherwise
|
||||||
|
designated in writing by the copyright owner as "Not a Contribution."
|
||||||
|
|
||||||
|
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||||
|
on behalf of whom a Contribution has been received by Licensor and
|
||||||
|
subsequently incorporated within the Work.
|
||||||
|
|
||||||
|
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
copyright license to reproduce, prepare Derivative Works of,
|
||||||
|
publicly display, publicly perform, sublicense, and distribute the
|
||||||
|
Work and such Derivative Works in Source or Object form.
|
||||||
|
|
||||||
|
3. Grant of Patent License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
(except as stated in this section) patent license to make, have made,
|
||||||
|
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||||
|
where such license applies only to those patent claims licensable
|
||||||
|
by such Contributor that are necessarily infringed by their
|
||||||
|
Contribution(s) alone or by combination of their Contribution(s)
|
||||||
|
with the Work to which such Contribution(s) was submitted. If You
|
||||||
|
institute patent litigation against any entity (including a
|
||||||
|
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||||
|
or a Contribution incorporated within the Work constitutes direct
|
||||||
|
or contributory patent infringement, then any patent licenses
|
||||||
|
granted to You under this License for that Work shall terminate
|
||||||
|
as of the date such litigation is filed.
|
||||||
|
|
||||||
|
4. Redistribution. You may reproduce and distribute copies of the
|
||||||
|
Work or Derivative Works thereof in any medium, with or without
|
||||||
|
modifications, and in Source or Object form, provided that You
|
||||||
|
meet the following conditions:
|
||||||
|
|
||||||
|
(a) You must give any other recipients of the Work or
|
||||||
|
Derivative Works a copy of this License; and
|
||||||
|
|
||||||
|
(b) You must cause any modified files to carry prominent notices
|
||||||
|
stating that You changed the files; and
|
||||||
|
|
||||||
|
(c) You must retain, in the Source form of any Derivative Works
|
||||||
|
that You distribute, all copyright, patent, trademark, and
|
||||||
|
attribution notices from the Source form of the Work,
|
||||||
|
excluding those notices that do not pertain to any part of
|
||||||
|
the Derivative Works; and
|
||||||
|
|
||||||
|
(d) If the Work includes a "NOTICE" text file as part of its
|
||||||
|
distribution, then any Derivative Works that You distribute must
|
||||||
|
include a readable copy of the attribution notices contained
|
||||||
|
within such NOTICE file, excluding those notices that do not
|
||||||
|
pertain to any part of the Derivative Works, in at least one
|
||||||
|
of the following places: within a NOTICE text file distributed
|
||||||
|
as part of the Derivative Works; within the Source form or
|
||||||
|
documentation, if provided along with the Derivative Works; or,
|
||||||
|
within a display generated by the Derivative Works, if and
|
||||||
|
wherever such third-party notices normally appear. The contents
|
||||||
|
of the NOTICE file are for informational purposes only and
|
||||||
|
do not modify the License. You may add Your own attribution
|
||||||
|
notices within Derivative Works that You distribute, alongside
|
||||||
|
or as an addendum to the NOTICE text from the Work, provided
|
||||||
|
that such additional attribution notices cannot be construed
|
||||||
|
as modifying the License.
|
||||||
|
|
||||||
|
You may add Your own copyright statement to Your modifications and
|
||||||
|
may provide additional or different license terms and conditions
|
||||||
|
for use, reproduction, or distribution of Your modifications, or
|
||||||
|
for any such Derivative Works as a whole, provided Your use,
|
||||||
|
reproduction, and distribution of the Work otherwise complies with
|
||||||
|
the conditions stated in this License.
|
||||||
|
|
||||||
|
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||||
|
any Contribution intentionally submitted for inclusion in the Work
|
||||||
|
by You to the Licensor shall be under the terms and conditions of
|
||||||
|
this License, without any additional terms or conditions.
|
||||||
|
Notwithstanding the above, nothing herein shall supersede or modify
|
||||||
|
the terms of any separate license agreement you may have executed
|
||||||
|
with Licensor regarding such Contributions.
|
||||||
|
|
||||||
|
6. Trademarks. This License does not grant permission to use the trade
|
||||||
|
names, trademarks, service marks, or product names of the Licensor,
|
||||||
|
except as required for reasonable and customary use in describing the
|
||||||
|
origin of the Work and reproducing the content of the NOTICE file.
|
||||||
|
|
||||||
|
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||||
|
agreed to in writing, Licensor provides the Work (and each
|
||||||
|
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||||
|
implied, including, without limitation, any warranties or conditions
|
||||||
|
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||||
|
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||||
|
appropriateness of using or redistributing the Work and assume any
|
||||||
|
risks associated with Your exercise of permissions under this License.
|
||||||
|
|
||||||
|
8. Limitation of Liability. In no event and under no legal theory,
|
||||||
|
whether in tort (including negligence), contract, or otherwise,
|
||||||
|
unless required by applicable law (such as deliberate and grossly
|
||||||
|
negligent acts) or agreed to in writing, shall any Contributor be
|
||||||
|
liable to You for damages, including any direct, indirect, special,
|
||||||
|
incidental, or consequential damages of any character arising as a
|
||||||
|
result of this License or out of the use or inability to use the
|
||||||
|
Work (including but not limited to damages for loss of goodwill,
|
||||||
|
work stoppage, computer failure or malfunction, or any and all
|
||||||
|
other commercial damages or losses), even if such Contributor
|
||||||
|
has been advised of the possibility of such damages.
|
||||||
|
|
||||||
|
9. Accepting Warranty or Additional Liability. While redistributing
|
||||||
|
the Work or Derivative Works thereof, You may choose to offer,
|
||||||
|
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||||
|
or other liability obligations and/or rights consistent with this
|
||||||
|
License. However, in accepting such obligations, You may act only
|
||||||
|
on Your own behalf and on Your sole responsibility, not on behalf
|
||||||
|
of any other Contributor, and only if You agree to indemnify,
|
||||||
|
defend, and hold each Contributor harmless for any liability
|
||||||
|
incurred by, or claims asserted against, such Contributor by reason
|
||||||
|
of your accepting any such warranty or additional liability.
|
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
APPENDIX: How to apply the Apache License to your work.
|
||||||
|
|
||||||
|
To apply the Apache License to your work, attach the following
|
||||||
|
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||||
|
replaced with your own identifying information. (Don't include
|
||||||
|
the brackets!) The text should be enclosed in the appropriate
|
||||||
|
comment syntax for the file format. We also recommend that a
|
||||||
|
file or class name and description of purpose be included on the
|
||||||
|
same "printed page" as the copyright notice for easier
|
||||||
|
identification within third-party archives.
|
||||||
|
|
||||||
|
Copyright [yyyy] [name of copyright owner]
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
+1863
File diff suppressed because it is too large
Load Diff
@@ -1,2 +1,16 @@
|
|||||||
// Package astro
|
// Package astro
|
||||||
package astro
|
package astro
|
||||||
|
|
||||||
|
import "b612.me/astro/basic"
|
||||||
|
|
||||||
|
func DeltaT() func(float64, bool) float64 {
|
||||||
|
return basic.GetDeltaTFn()
|
||||||
|
}
|
||||||
|
|
||||||
|
func SetDeltaT(deltaT func(float64, bool) float64) {
|
||||||
|
basic.SetDeltaTFn(deltaT)
|
||||||
|
}
|
||||||
|
|
||||||
|
func DefaultDeltaT() func(float64, bool) float64 {
|
||||||
|
return basic.DefaultDeltaTv2
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,123 @@
|
|||||||
|
package astro_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"math"
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"b612.me/astro/basic"
|
||||||
|
"b612.me/astro/moon"
|
||||||
|
"b612.me/astro/planet"
|
||||||
|
"b612.me/astro/sun"
|
||||||
|
)
|
||||||
|
|
||||||
|
type baselinePlanetSnapshot struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
XT int `json:"xt"`
|
||||||
|
LonBits uint64 `json:"lon_bits"`
|
||||||
|
LatBits uint64 `json:"lat_bits"`
|
||||||
|
RadBits uint64 `json:"rad_bits"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type baselineMoonSnapshot struct {
|
||||||
|
LonBits uint64 `json:"lon_bits"`
|
||||||
|
LatBits uint64 `json:"lat_bits"`
|
||||||
|
DisBits uint64 `json:"dis_bits"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type baselineSample struct {
|
||||||
|
UTC string `json:"utc"`
|
||||||
|
TTJD float64 `json:"tt_jd"`
|
||||||
|
Planets []baselinePlanetSnapshot `json:"planets"`
|
||||||
|
Moon baselineMoonSnapshot `json:"moon"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadBaselineSamples(t *testing.T) []baselineSample {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
data, err := os.ReadFile("testdata/planet_moon_baseline.json")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var samples []baselineSample
|
||||||
|
if err := json.Unmarshal(data, &samples); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if len(samples) == 0 {
|
||||||
|
t.Fatal("empty baseline samples")
|
||||||
|
}
|
||||||
|
return samples
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPlanetMoonBaselineRegression(t *testing.T) {
|
||||||
|
samples := loadBaselineSamples(t)
|
||||||
|
for _, sample := range samples {
|
||||||
|
for _, body := range sample.Planets {
|
||||||
|
gotLon := planet.WherePlanet(body.XT, 0, sample.TTJD)
|
||||||
|
if math.Float64bits(gotLon) != body.LonBits {
|
||||||
|
t.Fatalf("%s lon regression at %s", body.Name, sample.UTC)
|
||||||
|
}
|
||||||
|
gotLonN := planet.WherePlanetN(body.XT, 0, sample.TTJD, -1)
|
||||||
|
if math.Float64bits(gotLonN) != body.LonBits {
|
||||||
|
t.Fatalf("%s lon full-n regression at %s", body.Name, sample.UTC)
|
||||||
|
}
|
||||||
|
|
||||||
|
gotLat := planet.WherePlanet(body.XT, 1, sample.TTJD)
|
||||||
|
if math.Float64bits(gotLat) != body.LatBits {
|
||||||
|
t.Fatalf("%s lat regression at %s", body.Name, sample.UTC)
|
||||||
|
}
|
||||||
|
gotLatN := planet.WherePlanetN(body.XT, 1, sample.TTJD, -1)
|
||||||
|
if math.Float64bits(gotLatN) != body.LatBits {
|
||||||
|
t.Fatalf("%s lat full-n regression at %s", body.Name, sample.UTC)
|
||||||
|
}
|
||||||
|
|
||||||
|
gotRad := planet.WherePlanet(body.XT, 2, sample.TTJD)
|
||||||
|
if math.Float64bits(gotRad) != body.RadBits {
|
||||||
|
t.Fatalf("%s rad regression at %s", body.Name, sample.UTC)
|
||||||
|
}
|
||||||
|
gotRadN := planet.WherePlanetN(body.XT, 2, sample.TTJD, -1)
|
||||||
|
if math.Float64bits(gotRadN) != body.RadBits {
|
||||||
|
t.Fatalf("%s rad full-n regression at %s", body.Name, sample.UTC)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if math.Float64bits(basic.HMoonTrueLo(sample.TTJD)) != sample.Moon.LonBits {
|
||||||
|
t.Fatalf("moon lon regression at %s", sample.UTC)
|
||||||
|
}
|
||||||
|
if math.Float64bits(basic.HMoonTrueLoN(sample.TTJD, -1)) != sample.Moon.LonBits {
|
||||||
|
t.Fatalf("moon lon full-n regression at %s", sample.UTC)
|
||||||
|
}
|
||||||
|
if math.Float64bits(basic.HMoonTrueBo(sample.TTJD)) != sample.Moon.LatBits {
|
||||||
|
t.Fatalf("moon lat regression at %s", sample.UTC)
|
||||||
|
}
|
||||||
|
if math.Float64bits(basic.HMoonTrueBoN(sample.TTJD, -1)) != sample.Moon.LatBits {
|
||||||
|
t.Fatalf("moon lat full-n regression at %s", sample.UTC)
|
||||||
|
}
|
||||||
|
if math.Float64bits(basic.HMoonAway(sample.TTJD)) != sample.Moon.DisBits {
|
||||||
|
t.Fatalf("moon distance regression at %s", sample.UTC)
|
||||||
|
}
|
||||||
|
if math.Float64bits(basic.HMoonAwayN(sample.TTJD, -1)) != sample.Moon.DisBits {
|
||||||
|
t.Fatalf("moon distance full-n regression at %s", sample.UTC)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPublicTruncationFullMatchesDefault(t *testing.T) {
|
||||||
|
date := time.Date(2026, 1, 2, 3, 4, 5, 123456789, time.UTC)
|
||||||
|
|
||||||
|
if math.Float64bits(sun.TrueLo(date)) != math.Float64bits(sun.TrueLoN(date, -1)) {
|
||||||
|
t.Fatal("sun.TrueLoN(-1) should match default")
|
||||||
|
}
|
||||||
|
if math.Float64bits(sun.TrueBo(date)) != math.Float64bits(sun.TrueBoN(date, -1)) {
|
||||||
|
t.Fatal("sun.TrueBoN(-1) should match default")
|
||||||
|
}
|
||||||
|
if math.Float64bits(moon.TrueLo(date)) != math.Float64bits(moon.TrueLoN(date, -1)) {
|
||||||
|
t.Fatal("moon.TrueLoN(-1) should match default")
|
||||||
|
}
|
||||||
|
if math.Float64bits(moon.TrueBo(date)) != math.Float64bits(moon.TrueBoN(date, -1)) {
|
||||||
|
t.Fatal("moon.TrueBoN(-1) should match default")
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,39 +0,0 @@
|
|||||||
package basic
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
func Test_All(t *testing.T) {
|
|
||||||
show()
|
|
||||||
}
|
|
||||||
|
|
||||||
func Benchmark_All(b *testing.B) {
|
|
||||||
for i := 0; i < b.N; i++ {
|
|
||||||
show()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func show() {
|
|
||||||
jde := GetNowJDE() - 1
|
|
||||||
ra := HSunApparentRa(jde - 8.0/24.0)
|
|
||||||
dec := HSunApparentDec(jde - 8.0/24.0)
|
|
||||||
fmt.Printf("当前JDE:%.14f\n", jde)
|
|
||||||
fmt.Println("当前太阳黄经:", HSunApparentLo(jde-8.0/24.0))
|
|
||||||
fmt.Println("当前太阳赤经:", ra)
|
|
||||||
fmt.Println("当前太阳赤纬:", dec)
|
|
||||||
fmt.Println("当前太阳星座:", WhichCst(ra, dec, jde))
|
|
||||||
fmt.Println("当前黄赤交角:", EclipticObliquity(jde-8.0/24.0, true))
|
|
||||||
fmt.Println("当前日出:", JDE2Date(GetSunRiseTime(jde, 115, 32, 8, 1, 10)))
|
|
||||||
fmt.Println("当前日落:", JDE2Date(GetSunDownTime(jde, 115, 32, 8, 1, 10)))
|
|
||||||
fmt.Println("当前晨影 -6:", JDE2Date(GetAsaTime(jde, 115, 32, 8, -6)))
|
|
||||||
fmt.Println("当前晨影 -12:", JDE2Date(GetAsaTime(jde, 115, 32, 8, -12)))
|
|
||||||
fmt.Println("当前昏影 -6:", JDE2Date(GetBanTime(jde, 115, 32, 8, -6)))
|
|
||||||
fmt.Println("当前昏影 -12:", JDE2Date(GetBanTime(jde, 115, 32, 8, -12)))
|
|
||||||
fmt.Print("农历:")
|
|
||||||
fmt.Println(GetLunar(2019, 10, 23, 8.0/24.0))
|
|
||||||
fmt.Println("当前月出:", JDE2Date(GetMoonRiseTime(jde, 115, 32, 8, 1, 10)))
|
|
||||||
fmt.Println("当前月落:", JDE2Date(GetMoonDownTime(jde, 115, 32, 8, 1, 10)))
|
|
||||||
fmt.Println("月相:", MoonLight(jde-8.0/24.0))
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
package basic
|
||||||
|
|
||||||
|
import "math"
|
||||||
|
|
||||||
|
const rad = math.Pi / 180.0
|
||||||
|
const deg = 180.0 / math.Pi
|
||||||
+253
@@ -0,0 +1,253 @@
|
|||||||
|
package basic
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math"
|
||||||
|
"sort"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
earthApsisBaseTTJDE = 2451547.507
|
||||||
|
earthApsisMeanYearDays = 365.2596358
|
||||||
|
earthApsisQuadraticTerm = 0.0000000156
|
||||||
|
earthApsisBaseYear = 2000.01
|
||||||
|
earthApsisSeedScale = 0.99997
|
||||||
|
earthApsisBracketHalfWidth = 5.0
|
||||||
|
earthApsisSampleStep = 0.25
|
||||||
|
earthApsisDerivativeStep = 1e-3
|
||||||
|
earthApsisToleranceDays = 1e-8
|
||||||
|
earthApsisMaxIterations = 24
|
||||||
|
moonApsisBaseTTJDE = 2451534.6698
|
||||||
|
moonApsisMeanMonthDays = 27.55454989
|
||||||
|
moonApsisBaseCycle = 1325.55
|
||||||
|
moonApsisQuadraticTerm = -0.0006691
|
||||||
|
moonApsisCubicTerm = -0.000001098
|
||||||
|
moonApsisQuarticTerm = 0.0000000052
|
||||||
|
moonApsisBracketHalfWidth = 2.0
|
||||||
|
moonApsisSampleStep = 0.125
|
||||||
|
moonApsisDerivativeStep = 1e-4
|
||||||
|
moonApsisToleranceDays = 1e-8
|
||||||
|
moonApsisMaxIterations = 24
|
||||||
|
)
|
||||||
|
|
||||||
|
// ApsisEvent 轨道极值事件 / orbital distance extremum event.
|
||||||
|
type ApsisEvent struct {
|
||||||
|
// JDE 是事件发生时刻对应的世界时儒略日 / event time as UTC-based Julian day.
|
||||||
|
JDE float64
|
||||||
|
// Distance 是极值距离;地球相关事件单位 AU,月球相关事件单位 km / extremum distance.
|
||||||
|
Distance float64
|
||||||
|
}
|
||||||
|
|
||||||
|
type apsisSearchConfig struct {
|
||||||
|
bracketHalfWidth float64
|
||||||
|
sampleStep float64
|
||||||
|
derivativeStep float64
|
||||||
|
toleranceDays float64
|
||||||
|
maxIterations int
|
||||||
|
maximize bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// EarthPerihelion 地球指定年份的近日点 / Earth perihelion in the given year.
|
||||||
|
func EarthPerihelion(year int) ApsisEvent {
|
||||||
|
return earthApsis(year, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
// EarthAphelion 地球指定年份的远日点 / Earth aphelion in the given year.
|
||||||
|
func EarthAphelion(year int) ApsisEvent {
|
||||||
|
return earthApsis(year, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MoonPerigees 指定年月内的所有月球近地点 / all lunar perigees in the given Gregorian month.
|
||||||
|
func MoonPerigees(year int, month time.Month) []ApsisEvent {
|
||||||
|
return moonApsisInMonth(year, month, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MoonApogees 指定年月内的所有月球远地点 / all lunar apogees in the given Gregorian month.
|
||||||
|
func MoonApogees(year int, month time.Month) []ApsisEvent {
|
||||||
|
return moonApsisInMonth(year, month, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
func earthApsis(year int, aphelion bool) ApsisEvent {
|
||||||
|
seedTT := earthApsisSeedTT(year, aphelion)
|
||||||
|
cfg := apsisSearchConfig{
|
||||||
|
bracketHalfWidth: earthApsisBracketHalfWidth,
|
||||||
|
sampleStep: earthApsisSampleStep,
|
||||||
|
derivativeStep: earthApsisDerivativeStep,
|
||||||
|
toleranceDays: earthApsisToleranceDays,
|
||||||
|
maxIterations: earthApsisMaxIterations,
|
||||||
|
maximize: aphelion,
|
||||||
|
}
|
||||||
|
eventTT, distanceAU := refineDistanceExtremum(seedTT, cfg, EarthAway)
|
||||||
|
return ApsisEvent{
|
||||||
|
JDE: TD2UT(eventTT, false),
|
||||||
|
Distance: distanceAU,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func moonApsisInMonth(year int, month time.Month, apogee bool) []ApsisEvent {
|
||||||
|
startUTC := time.Date(year, month, 1, 0, 0, 0, 0, time.UTC)
|
||||||
|
endUTC := startUTC.AddDate(0, 1, 0)
|
||||||
|
startTT := TD2UT(Date2JDE(startUTC), true)
|
||||||
|
endTT := TD2UT(Date2JDE(endUTC), true)
|
||||||
|
|
||||||
|
kStart := int(math.Floor((startTT-moonApsisBaseTTJDE)/moonApsisMeanMonthDays)) - 1
|
||||||
|
kEnd := int(math.Ceil((endTT-moonApsisBaseTTJDE)/moonApsisMeanMonthDays)) + 1
|
||||||
|
phase := 0.0
|
||||||
|
if apogee {
|
||||||
|
phase = 0.5
|
||||||
|
}
|
||||||
|
|
||||||
|
cfg := apsisSearchConfig{
|
||||||
|
bracketHalfWidth: moonApsisBracketHalfWidth,
|
||||||
|
sampleStep: moonApsisSampleStep,
|
||||||
|
derivativeStep: moonApsisDerivativeStep,
|
||||||
|
toleranceDays: moonApsisToleranceDays,
|
||||||
|
maxIterations: moonApsisMaxIterations,
|
||||||
|
maximize: apogee,
|
||||||
|
}
|
||||||
|
|
||||||
|
events := make([]ApsisEvent, 0, 2)
|
||||||
|
for k := kStart; k <= kEnd; k++ {
|
||||||
|
seedTT := moonApsisSeedTT(float64(k) + phase)
|
||||||
|
eventTT, distanceKM := refineDistanceExtremum(seedTT, cfg, HMoonAway)
|
||||||
|
eventUT := TD2UT(eventTT, false)
|
||||||
|
eventTimeUTC := JDE2DateByZone(eventUT, time.UTC, false)
|
||||||
|
if eventTimeUTC.Before(startUTC) || !eventTimeUTC.Before(endUTC) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
events = append(events, ApsisEvent{
|
||||||
|
JDE: eventUT,
|
||||||
|
Distance: distanceKM,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
sort.Slice(events, func(i, j int) bool {
|
||||||
|
return events[i].JDE < events[j].JDE
|
||||||
|
})
|
||||||
|
return events
|
||||||
|
}
|
||||||
|
|
||||||
|
func earthApsisSeedTT(year int, aphelion bool) float64 {
|
||||||
|
k := math.Round(earthApsisSeedScale * (float64(year) - earthApsisBaseYear))
|
||||||
|
if aphelion {
|
||||||
|
k += 0.5
|
||||||
|
}
|
||||||
|
return earthApsisBaseTTJDE + earthApsisMeanYearDays*k + earthApsisQuadraticTerm*k*k
|
||||||
|
}
|
||||||
|
|
||||||
|
func moonApsisSeedTT(k float64) float64 {
|
||||||
|
t := k / moonApsisBaseCycle
|
||||||
|
return moonApsisBaseTTJDE +
|
||||||
|
moonApsisMeanMonthDays*k +
|
||||||
|
moonApsisQuadraticTerm*t*t +
|
||||||
|
moonApsisCubicTerm*t*t*t +
|
||||||
|
moonApsisQuarticTerm*t*t*t*t
|
||||||
|
}
|
||||||
|
|
||||||
|
func refineDistanceExtremum(seed float64, cfg apsisSearchConfig, distanceFn func(float64) float64) (float64, float64) {
|
||||||
|
best := seed
|
||||||
|
bestDistance := distanceFn(seed)
|
||||||
|
for sample := seed - cfg.bracketHalfWidth; sample <= seed+cfg.bracketHalfWidth+1e-12; sample += cfg.sampleStep {
|
||||||
|
dist := distanceFn(sample)
|
||||||
|
if distanceBetter(dist, bestDistance, cfg.maximize) {
|
||||||
|
best = sample
|
||||||
|
bestDistance = dist
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
left, right, ok := apsisDerivativeBracket(best, seed, cfg, distanceFn)
|
||||||
|
if !ok {
|
||||||
|
return best, bestDistance
|
||||||
|
}
|
||||||
|
|
||||||
|
leftDeriv := apsisDistanceDerivative(distanceFn, left, cfg.derivativeStep)
|
||||||
|
rightDeriv := apsisDistanceDerivative(distanceFn, right, cfg.derivativeStep)
|
||||||
|
current := best
|
||||||
|
for i := 0; i < cfg.maxIterations; i++ {
|
||||||
|
first, second := apsisDistanceDerivatives(distanceFn, current, cfg.derivativeStep)
|
||||||
|
next := current
|
||||||
|
if math.Abs(second) > 0 {
|
||||||
|
next = current - first/second
|
||||||
|
}
|
||||||
|
if !(next > left && next < right) || math.IsNaN(next) || math.IsInf(next, 0) {
|
||||||
|
next = (left + right) / 2
|
||||||
|
}
|
||||||
|
|
||||||
|
nextDeriv := apsisDistanceDerivative(distanceFn, next, cfg.derivativeStep)
|
||||||
|
if leftDeriv == 0 {
|
||||||
|
right = next
|
||||||
|
rightDeriv = nextDeriv
|
||||||
|
} else if leftDeriv*nextDeriv <= 0 {
|
||||||
|
right = next
|
||||||
|
rightDeriv = nextDeriv
|
||||||
|
} else {
|
||||||
|
left = next
|
||||||
|
leftDeriv = nextDeriv
|
||||||
|
}
|
||||||
|
|
||||||
|
if math.Abs(next-current) <= cfg.toleranceDays || math.Abs(right-left) <= cfg.toleranceDays {
|
||||||
|
current = next
|
||||||
|
break
|
||||||
|
}
|
||||||
|
current = next
|
||||||
|
_ = rightDeriv
|
||||||
|
}
|
||||||
|
|
||||||
|
return current, distanceFn(current)
|
||||||
|
}
|
||||||
|
|
||||||
|
func apsisDerivativeBracket(best, seed float64, cfg apsisSearchConfig, distanceFn func(float64) float64) (float64, float64, bool) {
|
||||||
|
leftBound := seed - cfg.bracketHalfWidth
|
||||||
|
rightBound := seed + cfg.bracketHalfWidth
|
||||||
|
left := best - cfg.sampleStep
|
||||||
|
right := best + cfg.sampleStep
|
||||||
|
if left < leftBound {
|
||||||
|
left = leftBound
|
||||||
|
}
|
||||||
|
if right > rightBound {
|
||||||
|
right = rightBound
|
||||||
|
}
|
||||||
|
|
||||||
|
leftDeriv := apsisDistanceDerivative(distanceFn, left, cfg.derivativeStep)
|
||||||
|
rightDeriv := apsisDistanceDerivative(distanceFn, right, cfg.derivativeStep)
|
||||||
|
for i := 0; i < cfg.maxIterations; i++ {
|
||||||
|
if leftDeriv == 0 || rightDeriv == 0 || leftDeriv*rightDeriv < 0 {
|
||||||
|
return left, right, true
|
||||||
|
}
|
||||||
|
if left > leftBound {
|
||||||
|
left -= cfg.sampleStep
|
||||||
|
if left < leftBound {
|
||||||
|
left = leftBound
|
||||||
|
}
|
||||||
|
leftDeriv = apsisDistanceDerivative(distanceFn, left, cfg.derivativeStep)
|
||||||
|
}
|
||||||
|
if right < rightBound {
|
||||||
|
right += cfg.sampleStep
|
||||||
|
if right > rightBound {
|
||||||
|
right = rightBound
|
||||||
|
}
|
||||||
|
rightDeriv = apsisDistanceDerivative(distanceFn, right, cfg.derivativeStep)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0, 0, false
|
||||||
|
}
|
||||||
|
|
||||||
|
func apsisDistanceDerivative(distanceFn func(float64) float64, jd, h float64) float64 {
|
||||||
|
return (distanceFn(jd+h) - distanceFn(jd-h)) / (2 * h)
|
||||||
|
}
|
||||||
|
|
||||||
|
func apsisDistanceDerivatives(distanceFn func(float64) float64, jd, h float64) (float64, float64) {
|
||||||
|
prev := distanceFn(jd - h)
|
||||||
|
curr := distanceFn(jd)
|
||||||
|
next := distanceFn(jd + h)
|
||||||
|
first := (next - prev) / (2 * h)
|
||||||
|
second := (next - 2*curr + prev) / (h * h)
|
||||||
|
return first, second
|
||||||
|
}
|
||||||
|
|
||||||
|
func distanceBetter(candidate, current float64, maximize bool) bool {
|
||||||
|
if maximize {
|
||||||
|
return candidate > current
|
||||||
|
}
|
||||||
|
return candidate < current
|
||||||
|
}
|
||||||
@@ -0,0 +1,175 @@
|
|||||||
|
package basic
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"math"
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type earthApsisSample struct {
|
||||||
|
Kind string `json:"kind"`
|
||||||
|
Year int `json:"year"`
|
||||||
|
TimeUTC string `json:"time_utc"`
|
||||||
|
DistanceAU float64 `json:"distance_au"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type moonApsisSample struct {
|
||||||
|
Kind string `json:"kind"`
|
||||||
|
Year int `json:"year"`
|
||||||
|
Month int `json:"month"`
|
||||||
|
TimeUTC string `json:"time_utc"`
|
||||||
|
DistanceKM float64 `json:"distance_km"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type moonApsisMonthState struct {
|
||||||
|
perigees []ApsisEvent
|
||||||
|
apogees []ApsisEvent
|
||||||
|
perigeeI int
|
||||||
|
apogeeI int
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEarthApsisMatchesHorizonsBaseline(t *testing.T) {
|
||||||
|
data, err := os.ReadFile("testdata/earth_apsis_baseline.json")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("read baseline: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var samples []earthApsisSample
|
||||||
|
if err := json.Unmarshal(data, &samples); err != nil {
|
||||||
|
t.Fatalf("decode baseline: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
const timeTolerance = 2 * time.Minute
|
||||||
|
const distanceToleranceAU = 5e-8
|
||||||
|
|
||||||
|
var maxTimeDiff time.Duration
|
||||||
|
var maxDistanceDiff float64
|
||||||
|
for _, sample := range samples {
|
||||||
|
wantTime, err := time.Parse(time.RFC3339Nano, sample.TimeUTC)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("parse sample time %q: %v", sample.TimeUTC, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var got ApsisEvent
|
||||||
|
switch sample.Kind {
|
||||||
|
case "perihelion":
|
||||||
|
got = EarthPerihelion(sample.Year)
|
||||||
|
case "aphelion":
|
||||||
|
got = EarthAphelion(sample.Year)
|
||||||
|
default:
|
||||||
|
t.Fatalf("unknown earth apsis kind %q", sample.Kind)
|
||||||
|
}
|
||||||
|
|
||||||
|
gotTime := JDE2DateByZone(got.JDE, time.UTC, false)
|
||||||
|
timeDiff := gotTime.Sub(wantTime)
|
||||||
|
if timeDiff < 0 {
|
||||||
|
timeDiff = -timeDiff
|
||||||
|
}
|
||||||
|
if timeDiff > maxTimeDiff {
|
||||||
|
maxTimeDiff = timeDiff
|
||||||
|
}
|
||||||
|
if timeDiff > timeTolerance {
|
||||||
|
t.Fatalf("%s %d time mismatch: got %s want %s tolerance %v", sample.Kind, sample.Year, gotTime.Format(time.RFC3339Nano), sample.TimeUTC, timeTolerance)
|
||||||
|
}
|
||||||
|
|
||||||
|
distanceDiff := math.Abs(got.Distance - sample.DistanceAU)
|
||||||
|
if distanceDiff > maxDistanceDiff {
|
||||||
|
maxDistanceDiff = distanceDiff
|
||||||
|
}
|
||||||
|
if distanceDiff > distanceToleranceAU {
|
||||||
|
t.Fatalf("%s %d distance mismatch: got %.12f want %.12f tolerance %.12f", sample.Kind, sample.Year, got.Distance, sample.DistanceAU, distanceToleranceAU)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Logf("earth apsis max diff: time=%v distance=%.12f AU", maxTimeDiff, maxDistanceDiff)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMoonApsisMatchesHorizonsBaseline(t *testing.T) {
|
||||||
|
// Baseline is generated from JPL Horizons by scripts/generate_moon_apsis_baseline.sh.
|
||||||
|
data, err := os.ReadFile("testdata/moon_apsis_baseline.json")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("read baseline: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var samples []moonApsisSample
|
||||||
|
if err := json.Unmarshal(data, &samples); err != nil {
|
||||||
|
t.Fatalf("decode baseline: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
const timeTolerance = 20 * time.Minute
|
||||||
|
const distanceToleranceKM = 50.0
|
||||||
|
|
||||||
|
states := make(map[int]*moonApsisMonthState)
|
||||||
|
var maxTimeDiff time.Duration
|
||||||
|
var maxDistanceDiff float64
|
||||||
|
|
||||||
|
for _, sample := range samples {
|
||||||
|
wantTime, err := time.Parse(time.RFC3339Nano, sample.TimeUTC)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("parse sample time %q: %v", sample.TimeUTC, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
key := sample.Year*100 + sample.Month
|
||||||
|
state := states[key]
|
||||||
|
if state == nil {
|
||||||
|
state = &moonApsisMonthState{
|
||||||
|
perigees: MoonPerigees(sample.Year, time.Month(sample.Month)),
|
||||||
|
apogees: MoonApogees(sample.Year, time.Month(sample.Month)),
|
||||||
|
}
|
||||||
|
states[key] = state
|
||||||
|
}
|
||||||
|
|
||||||
|
var got ApsisEvent
|
||||||
|
switch sample.Kind {
|
||||||
|
case "perigee":
|
||||||
|
if state.perigeeI >= len(state.perigees) {
|
||||||
|
t.Fatalf("%04d-%02d missing perigee #%d", sample.Year, sample.Month, state.perigeeI+1)
|
||||||
|
}
|
||||||
|
got = state.perigees[state.perigeeI]
|
||||||
|
state.perigeeI++
|
||||||
|
case "apogee":
|
||||||
|
if state.apogeeI >= len(state.apogees) {
|
||||||
|
t.Fatalf("%04d-%02d missing apogee #%d", sample.Year, sample.Month, state.apogeeI+1)
|
||||||
|
}
|
||||||
|
got = state.apogees[state.apogeeI]
|
||||||
|
state.apogeeI++
|
||||||
|
default:
|
||||||
|
t.Fatalf("unknown moon apsis kind %q", sample.Kind)
|
||||||
|
}
|
||||||
|
|
||||||
|
gotTime := JDE2DateByZone(got.JDE, time.UTC, false)
|
||||||
|
timeDiff := gotTime.Sub(wantTime)
|
||||||
|
if timeDiff < 0 {
|
||||||
|
timeDiff = -timeDiff
|
||||||
|
}
|
||||||
|
if timeDiff > maxTimeDiff {
|
||||||
|
maxTimeDiff = timeDiff
|
||||||
|
}
|
||||||
|
if timeDiff > timeTolerance {
|
||||||
|
t.Fatalf("%s %04d-%02d time mismatch: got %s want %s tolerance %v", sample.Kind, sample.Year, sample.Month, gotTime.Format(time.RFC3339Nano), sample.TimeUTC, timeTolerance)
|
||||||
|
}
|
||||||
|
|
||||||
|
distanceDiff := math.Abs(got.Distance - sample.DistanceKM)
|
||||||
|
if distanceDiff > maxDistanceDiff {
|
||||||
|
maxDistanceDiff = distanceDiff
|
||||||
|
}
|
||||||
|
if distanceDiff > distanceToleranceKM {
|
||||||
|
t.Fatalf("%s %04d-%02d distance mismatch: got %.6f want %.6f tolerance %.6f", sample.Kind, sample.Year, sample.Month, got.Distance, sample.DistanceKM, distanceToleranceKM)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for key, state := range states {
|
||||||
|
year := key / 100
|
||||||
|
month := key % 100
|
||||||
|
if state.perigeeI != len(state.perigees) {
|
||||||
|
t.Fatalf("%04d-%02d unconsumed perigees: got %d of %d", year, month, state.perigeeI, len(state.perigees))
|
||||||
|
}
|
||||||
|
if state.apogeeI != len(state.apogees) {
|
||||||
|
t.Fatalf("%04d-%02d unconsumed apogees: got %d of %d", year, month, state.apogeeI, len(state.apogees))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Logf("moon apsis max diff: time=%v distance=%.6f km", maxTimeDiff, maxDistanceDiff)
|
||||||
|
}
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
package basic
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
func Test_JDECALC(t *testing.T) {
|
|
||||||
i := 10
|
|
||||||
for i > 0 {
|
|
||||||
fmt.Printf("%.18f\n", GetNowJDE())
|
|
||||||
time.Sleep(time.Second)
|
|
||||||
i--
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func Test_TDUT(t *testing.T) {
|
|
||||||
fmt.Printf("%.18f\n", DeltaT(2119.5, false))
|
|
||||||
}
|
|
||||||
|
|
||||||
func Test_JDE2(t *testing.T) {
|
|
||||||
fmt.Println(JDE2Date(2458868.500))
|
|
||||||
}
|
|
||||||
@@ -1,402 +0,0 @@
|
|||||||
package basic
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"math"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
/*
|
|
||||||
@name: 儒略日计算
|
|
||||||
@dec: 计算给定时间的儒略日,1582年改力后为格里高利历,之前为儒略历
|
|
||||||
@ 请注意,传入的时间在天文计算中一般为力学时,应当注意和世界时的转化
|
|
||||||
*/
|
|
||||||
func JDECalc(Year, Month int, Day float64) float64 {
|
|
||||||
if Month == 1 || Month == 2 {
|
|
||||||
Year--
|
|
||||||
Month += 12
|
|
||||||
}
|
|
||||||
var tmpvarB int
|
|
||||||
tmpvar := fmt.Sprintf("%04d-%02d-%2d", Year, Month, int(math.Floor(Day)))
|
|
||||||
if strings.Compare(tmpvar, `1582-10-04`) != 1 {
|
|
||||||
tmpvarB = 0
|
|
||||||
} else {
|
|
||||||
tmpvarA := int(Year / 100)
|
|
||||||
tmpvarB = 2 - tmpvarA + int(tmpvarA/4)
|
|
||||||
}
|
|
||||||
return (math.Floor(365.25*(float64(Year)+4716.0)) + math.Floor(30.6001*float64(Month+1)) + Day + float64(tmpvarB) - 1524.5)
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
@name: 获得当前儒略日时间:当地世界时,非格林尼治时间
|
|
||||||
*/
|
|
||||||
func GetNowJDE() (NowJDE float64) {
|
|
||||||
Time := float64(time.Now().Second())/3600.0/24.0 + float64(time.Now().Minute())/60.0/24.0 + float64(time.Now().Hour())/24.0
|
|
||||||
NowJDE = JDECalc(time.Now().Year(), int(time.Now().Month()), float64(time.Now().Day())+Time)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func dt_ext(y, jsd float64) float64 { // 二次曲线外推,用于数值外插
|
|
||||||
dy := (y - 1820) / 100.00
|
|
||||||
return -20 + jsd*dy*dy
|
|
||||||
}
|
|
||||||
func dt_cal(y float64) float64 { //传入年, 返回世界时UT与原子时(力学时 TD)之差, ΔT = TD - UT
|
|
||||||
dt_at := []float64{
|
|
||||||
-4000, 108371.7, -13036.80, 392.000, 0.0000,
|
|
||||||
-500, 17201.0, -627.82, 16.170, -0.3413,
|
|
||||||
-150, 12200.6, -346.41, 5.403, -0.1593,
|
|
||||||
150, 9113.8, -328.13, -1.647, 0.0377,
|
|
||||||
500, 5707.5, -391.41, 0.915, 0.3145,
|
|
||||||
900, 2203.4, -283.45, 13.034, -0.1778,
|
|
||||||
1300, 490.1, -57.35, 2.085, -0.0072,
|
|
||||||
1600, 120.0, -9.81, -1.532, 0.1403,
|
|
||||||
1700, 10.2, -0.91, 0.510, -0.0370,
|
|
||||||
1800, 13.4, -0.72, 0.202, -0.0193,
|
|
||||||
1830, 7.8, -1.81, 0.416, -0.0247,
|
|
||||||
1860, 8.3, -0.13, -0.406, 0.0292,
|
|
||||||
1880, -5.4, 0.32, -0.183, 0.0173,
|
|
||||||
1900, -2.3, 2.06, 0.169, -0.0135,
|
|
||||||
1920, 21.2, 1.69, -0.304, 0.0167,
|
|
||||||
1940, 24.2, 1.22, -0.064, 0.0031,
|
|
||||||
1960, 33.2, 0.51, 0.231, -0.0109,
|
|
||||||
1980, 51.0, 1.29, -0.026, 0.0032,
|
|
||||||
2000, 63.87, 0.1, 0, 0,
|
|
||||||
2005, 64.7, 0.4, 0, 0, //一次项记为x,则 10x=0.4秒/年*(2015-2005),解得x=0.4
|
|
||||||
2015, 69,
|
|
||||||
}
|
|
||||||
y0 := dt_at[len(dt_at)-2] //表中最后一年
|
|
||||||
t0 := dt_at[len(dt_at)-1] //表中最后一年的 deltatT
|
|
||||||
if y >= y0 {
|
|
||||||
jsd := float64(31) // sjd是y1年之后的加速度估计
|
|
||||||
// 瑞士星历表jsd=31, NASA网站jsd=32, skmap的jsd=29
|
|
||||||
if y > y0+100.00 {
|
|
||||||
return dt_ext(y, jsd)
|
|
||||||
}
|
|
||||||
v := dt_ext(y, jsd) //二次曲线外推
|
|
||||||
dv := dt_ext(y0, jsd) - t0 // ye年的二次外推与te的差
|
|
||||||
return (v - dv*(y0+100.00-y)/100.00)
|
|
||||||
}
|
|
||||||
d := dt_at
|
|
||||||
var i int
|
|
||||||
for i = 0; i < len(d); i += 5 {
|
|
||||||
if float64(y) < d[i+5] {
|
|
||||||
break
|
|
||||||
// 判断年所在的区间
|
|
||||||
}
|
|
||||||
}
|
|
||||||
t1 := (y - d[i]) / (d[i+5] - d[i]) * 10.00 //////// 三次插值, 保证精确性
|
|
||||||
t2 := t1 * t1
|
|
||||||
t3 := t2 * t1
|
|
||||||
res := d[i+1] + d[i+2]*t1 + d[i+3]*t2 + d[i+4]*t3
|
|
||||||
return (res)
|
|
||||||
}
|
|
||||||
func DeltaT(Date float64, IsJDE bool) (Result float64) { //传入年或儒略日,传出为秒
|
|
||||||
var Year float64
|
|
||||||
if IsJDE {
|
|
||||||
dates := JDE2Date(Date)
|
|
||||||
Year = float64(dates.Year()) + float64(dates.YearDay())/365.0
|
|
||||||
} else {
|
|
||||||
Year = Date
|
|
||||||
}
|
|
||||||
if Year < 2010 {
|
|
||||||
Result = dt_cal(Year)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if Year < 2100 && Year >= 2010 {
|
|
||||||
var t = (Year - 2000.0)
|
|
||||||
Result = 62.92 + 0.32217*t + 0.005589*t*t
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if Year >= 2100 && Year <= 2150 {
|
|
||||||
Result = -20 + 32*(((Year-1820.0)/100.0)*((Year-1820.0)/100.0)) - 0.5628*(2150-Year)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if Year > 2150 {
|
|
||||||
//tmp=(Year-1820)/100;
|
|
||||||
//Result= -20 + 32 * tmp*tmp;
|
|
||||||
Result = dt_cal(Year)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
func TD2UT(JDE float64, UT2TD bool) float64 { // true 世界时转力学时CC,false 力学时转世界时VV
|
|
||||||
|
|
||||||
Deltat := DeltaT(JDE, true)
|
|
||||||
if UT2TD {
|
|
||||||
return JDE + Deltat/3600/24
|
|
||||||
} else {
|
|
||||||
return JDE - Deltat/3600/24
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* @name: JDE转日期,输出为数组
|
|
||||||
*/
|
|
||||||
func JDE2Date(JD float64) time.Time {
|
|
||||||
JD = JD + 0.5
|
|
||||||
Z := float64(int(JD))
|
|
||||||
F := JD - Z
|
|
||||||
var A, B, Years, Months, Days float64
|
|
||||||
if Z < 2299161.0 {
|
|
||||||
A = Z
|
|
||||||
} else {
|
|
||||||
alpha := math.Floor((Z - 1867216.25) / 36524.25)
|
|
||||||
A = Z + 1 + alpha - math.Floor(alpha/4)
|
|
||||||
}
|
|
||||||
B = A + 1524
|
|
||||||
C := math.Floor((B - 122.1) / 365.25)
|
|
||||||
D := math.Floor(365.25 * C)
|
|
||||||
E := math.Floor((B - D) / 30.6001)
|
|
||||||
Days = B - D - math.Floor(30.6001*E) + F
|
|
||||||
if E < 14 {
|
|
||||||
Months = E - 1
|
|
||||||
}
|
|
||||||
if E == 14 || E == 15 {
|
|
||||||
Months = E - 13
|
|
||||||
}
|
|
||||||
if Months > 2 {
|
|
||||||
Years = C - 4716
|
|
||||||
}
|
|
||||||
if Months == 1 || Months == 2 {
|
|
||||||
Years = C - 4715
|
|
||||||
}
|
|
||||||
tms := (Days - math.Floor(Days)) * 24 * 3600
|
|
||||||
Days = math.Floor(Days)
|
|
||||||
tz, _ := time.LoadLocation("Local")
|
|
||||||
dates := time.Date(int(Years), time.Month(int(Months)), int(Days), 0, 0, 0, 0, tz)
|
|
||||||
dates = time.Unix(dates.Unix()+int64(tms), int64((tms-math.Floor(tms))*1000000000))
|
|
||||||
return dates
|
|
||||||
}
|
|
||||||
|
|
||||||
func JDE2DateByZone(JD float64, tz *time.Location, byZone bool) time.Time {
|
|
||||||
JD = JD + 0.5
|
|
||||||
Z := float64(int(JD))
|
|
||||||
F := JD - Z
|
|
||||||
var A, B, Years, Months, Days float64
|
|
||||||
if Z < 2299161.0 {
|
|
||||||
A = Z
|
|
||||||
} else {
|
|
||||||
alpha := math.Floor((Z - 1867216.25) / 36524.25)
|
|
||||||
A = Z + 1 + alpha - math.Floor(alpha/4)
|
|
||||||
}
|
|
||||||
B = A + 1524
|
|
||||||
C := math.Floor((B - 122.1) / 365.25)
|
|
||||||
D := math.Floor(365.25 * C)
|
|
||||||
E := math.Floor((B - D) / 30.6001)
|
|
||||||
Days = B - D - math.Floor(30.6001*E) + F
|
|
||||||
if E < 14 {
|
|
||||||
Months = E - 1
|
|
||||||
}
|
|
||||||
if E == 14 || E == 15 {
|
|
||||||
Months = E - 13
|
|
||||||
}
|
|
||||||
if Months > 2 {
|
|
||||||
Years = C - 4716
|
|
||||||
}
|
|
||||||
if Months == 1 || Months == 2 {
|
|
||||||
Years = C - 4715
|
|
||||||
}
|
|
||||||
tms := (Days - math.Floor(Days)) * 24 * 3600
|
|
||||||
Days = math.Floor(Days)
|
|
||||||
if !byZone {
|
|
||||||
dates := time.Date(int(Years), time.Month(int(Months)), int(Days), 0, 0, 0, 0, time.UTC)
|
|
||||||
return time.Unix(dates.Unix()+int64(tms), int64((tms-math.Floor(tms))*1000000000)).In(tz)
|
|
||||||
}
|
|
||||||
dates := time.Date(int(Years), time.Month(int(Months)), int(Days), 0, 0, 0, 0, tz)
|
|
||||||
return time.Unix(dates.Unix()+int64(tms), int64((tms-math.Floor(tms))*1000000000))
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetLunar(year, month, day int, tz float64) (lmonth, lday int, leap bool, result string) {
|
|
||||||
jde := JDECalc(year, month, float64(day)) //计算当前JDE时间
|
|
||||||
if month == 11 || month == 12 { //判断当前日期属于前一年周期还是后一年周期
|
|
||||||
//判断方法:当前日期与冬至日所在朔望月的关系
|
|
||||||
winterday := GetJQTime(year, 270) + tz //冬至日日期(世界时,北京时间)
|
|
||||||
Fday := TD2UT(CalcMoonS(float64(year)+11.0/12.0+5.0/30.0/12.0, 0), true) + tz //朔月(世界时,北京时间)
|
|
||||||
Yday := TD2UT(CalcMoonS(float64(year)+1.0, 0), true) + tz //下一朔月(世界时,北京时间)
|
|
||||||
if Fday-math.Floor(Fday) > 0.5 {
|
|
||||||
Fday = math.Floor(Fday) + 0.5
|
|
||||||
} else {
|
|
||||||
Fday = math.Floor(Fday) - 0.5
|
|
||||||
}
|
|
||||||
if Yday-math.Floor(Yday) > 0.5 {
|
|
||||||
Yday = math.Floor(Yday) + 0.5
|
|
||||||
} else {
|
|
||||||
Yday = math.Floor(Yday) - 0.5
|
|
||||||
}
|
|
||||||
if winterday >= Fday && winterday < Yday && jde <= Fday {
|
|
||||||
year--
|
|
||||||
}
|
|
||||||
if winterday >= Yday && jde < Yday {
|
|
||||||
year--
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
year--
|
|
||||||
}
|
|
||||||
jieqi := GetOneYearJQ(year) //一年的节气
|
|
||||||
moon := GetOneYearMoon(float64(year)) //一年朔月日
|
|
||||||
winter1 := jieqi[1] //第一年冬至日
|
|
||||||
winter2 := jieqi[25] //第二年冬至日
|
|
||||||
for k, v := range moon {
|
|
||||||
if tz != 8.0/24 {
|
|
||||||
v = v - 8.0/24 + tz
|
|
||||||
}
|
|
||||||
if v-math.Floor(v) > 0.5 {
|
|
||||||
moon[k] = math.Floor(v) + 0.5
|
|
||||||
} else {
|
|
||||||
moon[k] = math.Floor(v) - 0.5
|
|
||||||
}
|
|
||||||
} //置闰月为0点
|
|
||||||
for k, v := range jieqi {
|
|
||||||
if tz != 8.0/24 {
|
|
||||||
v = v - 8.0/24 + tz
|
|
||||||
}
|
|
||||||
if v-math.Floor(v) > 0.5 {
|
|
||||||
jieqi[k] = math.Floor(v) + 0.5
|
|
||||||
} else {
|
|
||||||
jieqi[k] = math.Floor(v) - 0.5
|
|
||||||
}
|
|
||||||
} //置节气为0点
|
|
||||||
mooncount := 0 //年内朔望月计数
|
|
||||||
var min, max int = 20, 0 //最大最小计数
|
|
||||||
for i := 1; i < 16; i++ {
|
|
||||||
if moon[i] >= winter1 && moon[i] < winter2 {
|
|
||||||
if i <= min {
|
|
||||||
min = i
|
|
||||||
}
|
|
||||||
if i >= max {
|
|
||||||
max = i
|
|
||||||
}
|
|
||||||
mooncount++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
leapmonth := 20
|
|
||||||
if mooncount == 13 { //存在闰月
|
|
||||||
j, i := 3, 1
|
|
||||||
for i = min; i <= max; i++ {
|
|
||||||
if !(moon[i] <= jieqi[j] && moon[i+1] > jieqi[j]) {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
j += 2
|
|
||||||
}
|
|
||||||
leapmonth = i - min + 1
|
|
||||||
}
|
|
||||||
i := 0
|
|
||||||
for i = min - 1; i <= max; i++ {
|
|
||||||
if moon[i] > jde {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
lmonth = i - min
|
|
||||||
var sleap bool = false
|
|
||||||
leap = false
|
|
||||||
if lmonth >= leapmonth {
|
|
||||||
sleap = true
|
|
||||||
}
|
|
||||||
if lmonth == leapmonth {
|
|
||||||
leap = true
|
|
||||||
}
|
|
||||||
if lmonth < 2 {
|
|
||||||
lmonth += 11
|
|
||||||
} else {
|
|
||||||
lmonth--
|
|
||||||
}
|
|
||||||
if sleap {
|
|
||||||
lmonth--
|
|
||||||
}
|
|
||||||
if lmonth <= 0 {
|
|
||||||
lmonth += 12
|
|
||||||
}
|
|
||||||
lday = int(jde-moon[i-1]) + 1
|
|
||||||
strmonth := []string{"十", "一", "二", "三", "四", "五", "六", "七", "八", "九", "十", "冬", "腊"}
|
|
||||||
strday := []string{"初", "十", "廿", "三"}
|
|
||||||
if leap {
|
|
||||||
result += "闰"
|
|
||||||
}
|
|
||||||
if lmonth == 1 {
|
|
||||||
result += "正月"
|
|
||||||
} else {
|
|
||||||
result += strmonth[lmonth] + "月"
|
|
||||||
}
|
|
||||||
if lday == 20 {
|
|
||||||
result += "二十"
|
|
||||||
} else if lday == 10 {
|
|
||||||
result += "初十"
|
|
||||||
} else {
|
|
||||||
result += strday[lday/10] + strmonth[lday%10]
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetSolar(year, month, day int, leap bool, tz float64) float64 {
|
|
||||||
if month < 11 {
|
|
||||||
year--
|
|
||||||
}
|
|
||||||
jieqi := GetOneYearJQ(year) //一年的节气
|
|
||||||
moon := GetOneYearMoon(float64(year)) //一年朔月日
|
|
||||||
winter1 := jieqi[1] //第一年冬至日
|
|
||||||
winter2 := jieqi[25] //第二年冬至日
|
|
||||||
for k, v := range moon {
|
|
||||||
if tz != 8.0/24 {
|
|
||||||
v = v - 8.0/24 + tz
|
|
||||||
}
|
|
||||||
if v-math.Floor(v) > 0.5 {
|
|
||||||
moon[k] = math.Floor(v) + 0.5
|
|
||||||
} else {
|
|
||||||
moon[k] = math.Floor(v) - 0.5
|
|
||||||
}
|
|
||||||
} //置闰月为0点
|
|
||||||
for k, v := range jieqi {
|
|
||||||
if tz != 8.0/24 {
|
|
||||||
v = v - 8.0/24 + tz
|
|
||||||
}
|
|
||||||
if v-math.Floor(v) > 0.5 {
|
|
||||||
jieqi[k] = math.Floor(v) + 0.5
|
|
||||||
} else {
|
|
||||||
jieqi[k] = math.Floor(v) - 0.5
|
|
||||||
}
|
|
||||||
} //置节气为0点
|
|
||||||
mooncount := 0 //年内朔望月计数
|
|
||||||
var min, max int = 20, 0 //最大最小计数
|
|
||||||
for i := 1; i < 16; i++ {
|
|
||||||
if moon[i] >= winter1 && moon[i] < winter2 {
|
|
||||||
if i <= min {
|
|
||||||
min = i
|
|
||||||
}
|
|
||||||
if i >= max {
|
|
||||||
max = i
|
|
||||||
}
|
|
||||||
mooncount++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
leapmonth := 20
|
|
||||||
if mooncount == 13 { //存在闰月
|
|
||||||
j, i := 3, 1
|
|
||||||
for i = min; i <= max; i++ {
|
|
||||||
if !(moon[i] <= jieqi[j] && moon[i+1] > jieqi[j]) {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
j += 2
|
|
||||||
}
|
|
||||||
leapmonth = i - min + 1
|
|
||||||
}
|
|
||||||
if leap {
|
|
||||||
month++
|
|
||||||
}
|
|
||||||
if month > 10 {
|
|
||||||
month -= 11
|
|
||||||
} else {
|
|
||||||
month++
|
|
||||||
}
|
|
||||||
if month >= leapmonth && !leap {
|
|
||||||
month++
|
|
||||||
}
|
|
||||||
jde := moon[min-1+month] + float64(day) - 1
|
|
||||||
return jde
|
|
||||||
}
|
|
||||||
|
|
||||||
// Date2JDE 日期转儒略日
|
|
||||||
func Date2JDE(date time.Time) float64 {
|
|
||||||
day := float64(date.Day()) + float64(date.Hour())/24.0 + float64(date.Minute())/24.0/60.0 + float64(date.Second())/24.0/3600.0 + float64(date.Nanosecond())/1000000000.0/3600.0/24.0
|
|
||||||
return JDECalc(date.Year(), int(date.Month()), day)
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
package basic
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestGetJQTime(t *testing.T) {
|
||||||
|
originalFunc := func(year, angle int) float64 {
|
||||||
|
const iterations = 1
|
||||||
|
|
||||||
|
var day int
|
||||||
|
if angle%2 == 0 {
|
||||||
|
day = 18
|
||||||
|
} else {
|
||||||
|
day = 3
|
||||||
|
}
|
||||||
|
|
||||||
|
month := 3.0
|
||||||
|
if angle%10 != 0 {
|
||||||
|
month += float64(angle+15) / 30.0
|
||||||
|
} else {
|
||||||
|
month += float64(angle) / 30.0
|
||||||
|
}
|
||||||
|
if month > 12 {
|
||||||
|
month -= 12
|
||||||
|
}
|
||||||
|
|
||||||
|
jd := JDECalc(year, int(month), float64(day))
|
||||||
|
if angle == 0 {
|
||||||
|
angle = 360
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i < iterations; i++ {
|
||||||
|
for {
|
||||||
|
jd0 := jd
|
||||||
|
slope := (JQLospec(jd0+0.000005, float64(angle)) - JQLospec(jd0-0.000005, float64(angle))) / 0.00001
|
||||||
|
jd = jd0 - (JQLospec(jd0, float64(angle))-float64(angle))/slope
|
||||||
|
if math.Abs(jd-jd0) <= 0.00001 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
jd -= 0.001
|
||||||
|
}
|
||||||
|
jd += 0.001
|
||||||
|
return TD2UT(jd, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
testCases := []struct {
|
||||||
|
year int
|
||||||
|
angle int
|
||||||
|
}{
|
||||||
|
{1900, 0}, {1900, 15}, {1900, 30}, {1900, 45}, {1900, 90}, {1900, 180}, {1900, 270}, {1900, 360},
|
||||||
|
{1950, 0}, {1950, 30}, {1950, 90}, {1950, 180}, {1950, 270},
|
||||||
|
{2000, 0}, {2000, 15}, {2000, 45}, {2000, 90}, {2000, 360},
|
||||||
|
{2023, 0}, {2023, 30}, {2023, 90}, {2023, 180}, {2023, 270},
|
||||||
|
{2100, 0}, {2100, 15}, {2100, 30}, {2100, 45}, {2100, 90}, {2100, 180}, {2100, 270}, {2100, 360},
|
||||||
|
{2200, 0}, {2200, 30}, {2200, 90}, {2200, 180}, {2200, 270},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
originalResult := originalFunc(tc.year, tc.angle)
|
||||||
|
optimizedResult := GetJQTime(tc.year, tc.angle)
|
||||||
|
diff := math.Abs(originalResult - optimizedResult)
|
||||||
|
if diff > 1e-10 {
|
||||||
|
t.Fatalf("GetJQTime mismatch: year=%d angle=%d original=%.15f optimized=%.15f diff=%.15f",
|
||||||
|
tc.year, tc.angle, originalResult, optimizedResult, diff)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,204 @@
|
|||||||
|
package basic
|
||||||
|
|
||||||
|
import (
|
||||||
|
. "b612.me/astro/tools"
|
||||||
|
)
|
||||||
|
|
||||||
|
type constellationPoint struct {
|
||||||
|
RA float64 //赤经
|
||||||
|
DEC float64 //赤纬
|
||||||
|
}
|
||||||
|
|
||||||
|
var constellationPolygons map[string][]constellationPoint
|
||||||
|
|
||||||
|
func initConstellationPolygons() {
|
||||||
|
constellationPolygons = make(map[string][]constellationPoint, 89)
|
||||||
|
constellationPolygons["AND"] = []constellationPoint{constellationPoint{344.46530375, 35.1682358}, constellationPoint{344.34285125, 53.1680298}, constellationPoint{351.45289375, 53.1870041}, constellationPoint{351.4656825, 50.6870193}, constellationPoint{355.27055708333, 50.6929131}, constellationPoint{355.27607875, 48.6929169}, constellationPoint{4.1463675, 48.6949348}, constellationPoint{4.14327875, 46.6949348}, constellationPoint{14.776077083333, 46.6757545}, constellationPoint{14.7888675, 48.6757393}, constellationPoint{18.588407916667, 48.6632690}, constellationPoint{18.60590375, 50.6632347}, constellationPoint{22.40793625, 50.6478767}, constellationPoint{26.96852375, 50.6257439}, constellationPoint{26.931439583333, 47.6258430}, constellationPoint{32.62149125, 47.5927505}, constellationPoint{32.67380125, 51.0925827}, constellationPoint{39.88547875, 51.0423737}, constellationPoint{39.67934125, 37.2931557}, constellationPoint{31.87109125, 37.3470840}, constellationPoint{31.854250416667, 35.5971375}, constellationPoint{22.910835, 35.6453362}, constellationPoint{22.89742625, 33.6453705}, constellationPoint{12.44306375, 33.6818962}, constellationPoint{12.41349125, 24.4319324}, constellationPoint{14.424064583333, 24.4266243}, constellationPoint{14.414815416667, 21.6766376}, constellationPoint{3.73992125, 21.6951923}, constellationPoint{3.7406445833333, 22.6951923}, constellationPoint{2.61001625, 22.6957588}, constellationPoint{2.6128425, 28.6957588}, constellationPoint{1.60621625, 28.6960354}, constellationPoint{1.6069770833333, 32.0293655}, constellationPoint{357.82874125, 32.0285034}, constellationPoint{357.8280825, 32.7785072}, constellationPoint{354.04915791667, 32.7746468}, constellationPoint{354.04417958333, 35.1913109}}
|
||||||
|
constellationPolygons["ANT"] = []constellationPoint{constellationPoint{141.904335, -24.5425186}, constellationPoint{141.77159875, -37.2920151}, constellationPoint{141.73406125, -40.2918739}, constellationPoint{166.45650458333, -40.4246216}, constellationPoint{166.47936291667, -35.6746559}, constellationPoint{163.95851541667, -35.6664963}, constellationPoint{163.977885, -31.8332005}, constellationPoint{160.20137875, -31.8185863}, constellationPoint{160.21289375, -29.8186131}, constellationPoint{155.18132708333, -29.7947845}, constellationPoint{155.1993375, -27.1281624}, constellationPoint{147.65928291667, -27.0835037}, constellationPoint{147.67968125, -24.5835705}}
|
||||||
|
constellationPolygons["APS"] = []constellationPoint{constellationPoint{209.11110875, -83.1200714}, constellationPoint{276.86599791667, -82.4582748}, constellationPoint{274.19506041667, -74.9745178}, constellationPoint{273.28007708333, -67.4800797}, constellationPoint{265.77572875, -67.5711060}, constellationPoint{258.2424825, -67.6610870}, constellationPoint{258.47067875, -70.1597443}, constellationPoint{224.16644125, -70.5115433}, constellationPoint{207.46087041667, -70.6244431}, constellationPoint{207.78143375, -75.6235962}}
|
||||||
|
constellationPolygons["AQR"] = []constellationPoint{constellationPoint{309.59884625, 0.4361772}, constellationPoint{309.5798775, 2.4360874}, constellationPoint{314.08109708333, 2.4773185}, constellationPoint{321.58347125, 2.5393796}, constellationPoint{323.58427041667, 2.5544112}, constellationPoint{323.57874875, 3.3043909}, constellationPoint{326.58021875, 3.3256676}, constellationPoint{326.58708625, 2.3256910}, constellationPoint{331.588755, 2.3576119}, constellationPoint{331.58726708333, 2.6076074}, constellationPoint{342.84221708333, 2.6622071}, constellationPoint{342.8497125, 0.6622211}, constellationPoint{342.86470375, -3.3377509}, constellationPoint{359.10221125, -3.3042023}, constellationPoint{359.10329875, -6.3042021}, constellationPoint{359.11056458333, -24.8042011}, constellationPoint{346.68096625, -24.8250446}, constellationPoint{329.77028875, -24.9040413}, constellationPoint{329.6561625, -8.4043999}, constellationPoint{321.668415, -8.4602947}, constellationPoint{321.71645125, -14.4601107}, constellationPoint{309.74390125, -14.5631361}, constellationPoint{309.68464791667, -8.5634165}}
|
||||||
|
constellationPolygons["AQL"] = []constellationPoint{constellationPoint{280.35020875, 0.1154895}, constellationPoint{280.3262325, 2.1153460}, constellationPoint{284.57642541667, 2.1659052}, constellationPoint{284.525985, 6.4156075}, constellationPoint{281.45856875, 6.3791943}, constellationPoint{281.388045, 12.1287737}, constellationPoint{284.45626208333, 12.1651964}, constellationPoint{284.37363625, 18.6647091}, constellationPoint{286.37549375, 18.6882229}, constellationPoint{286.4054775, 16.3550682}, constellationPoint{298.92116541667, 16.4957294}, constellationPoint{298.926, 16.0790844}, constellationPoint{303.5589975, 16.1275158}, constellationPoint{303.636675, 8.8779116}, constellationPoint{306.01395625, 8.9018240}, constellationPoint{306.07910125, 2.4021468}, constellationPoint{309.5798775, 2.4360874}, constellationPoint{309.59884625, 0.4361772}, constellationPoint{309.68464791667, -8.5634165}, constellationPoint{301.69369625, -8.6430750}, constellationPoint{301.72636958333, -11.6762342}, constellationPoint{284.74405375, -11.8664360}, constellationPoint{284.64729291667, -3.8336766}, constellationPoint{280.3981875, -3.8842230}}
|
||||||
|
constellationPolygons["ARA"] = []constellationPoint{constellationPoint{249.03468125, -60.2644577}, constellationPoint{248.57062375, -45.7670517}, constellationPoint{269.80928375, -45.5163460}, constellationPoint{272.3090175, -45.4859734}, constellationPoint{272.67225291667, -56.9837723}, constellationPoint{265.16818958333, -57.0747757}, constellationPoint{265.77572875, -67.5711060}, constellationPoint{258.2424825, -67.6610870}, constellationPoint{255.72498291667, -67.6905823}, constellationPoint{255.5423925, -65.1916428}, constellationPoint{254.28351458333, -65.2062531}, constellationPoint{254.1951375, -63.7900925}, constellationPoint{251.67632125, -63.8189964}, constellationPoint{251.53784708333, -61.2364578}, constellationPoint{249.08163, -61.2641945}}
|
||||||
|
constellationPolygons["ARI"] = []constellationPoint{constellationPoint{31.6652475, 10.5143948}, constellationPoint{26.65573375, 10.5432396}, constellationPoint{26.744674583333, 25.6263351}, constellationPoint{30.51371125, 25.6050701}, constellationPoint{30.53061625, 27.8550186}, constellationPoint{38.07014875, 27.8047638}, constellationPoint{38.10319375, 31.2213154}, constellationPoint{42.62838, 31.1865025}, constellationPoint{52.426667916667, 31.1003609}, constellationPoint{52.2906225, 19.4343338}, constellationPoint{51.037234583333, 19.4461136}, constellationPoint{50.94641125, 10.3632069}}
|
||||||
|
constellationPolygons["AUR"] = []constellationPoint{constellationPoint{69.4869375, 30.9218750}, constellationPoint{69.57384125, 36.2547150}, constellationPoint{72.45734375, 36.2218513}, constellationPoint{72.840285, 52.7196465}, constellationPoint{77.484762083333, 52.6655540}, constellationPoint{77.606764583333, 56.1648331}, constellationPoint{94.13108875, 55.9658089}, constellationPoint{94.05736625, 53.9662552}, constellationPoint{100.04603125, 53.8938293}, constellationPoint{99.919459583333, 49.8945885}, constellationPoint{104.40635875, 49.8410034}, constellationPoint{104.26530291667, 44.3418388}, constellationPoint{112.73412458333, 44.2435493}, constellationPoint{112.56071875, 35.2445297}, constellationPoint{100.09027625, 35.3905640}, constellationPoint{99.965657916667, 27.8913116}, constellationPoint{90.22107125, 28.0092907}, constellationPoint{90.228902916667, 28.5092430}, constellationPoint{73.212475416667, 28.7124405}, constellationPoint{73.235342916667, 30.2123089}, constellationPoint{69.47678875, 30.2552605}}
|
||||||
|
constellationPolygons["BOO"] = []constellationPoint{constellationPoint{227.78148625, 7.5253930}, constellationPoint{204.06384875, 7.3605771}, constellationPoint{204.02893041667, 14.3604937}, constellationPoint{203.95387208333, 27.8603134}, constellationPoint{210.7888275, 27.8976517}, constellationPoint{210.77085875, 30.1475964}, constellationPoint{211.88893375, 30.1545391}, constellationPoint{211.69873208333, 47.9039383}, constellationPoint{211.58439125, 54.9035759}, constellationPoint{217.25124875, 54.9422379}, constellationPoint{229.59105458333, 55.0448647}, constellationPoint{229.65737375, 52.5451736}, constellationPoint{237.08447375, 52.6174774}, constellationPoint{237.12458541667, 51.1176796}, constellationPoint{237.36542708333, 39.6189079}, constellationPoint{232.64365208333, 39.5721130}, constellationPoint{232.74697791667, 32.5726128}, constellationPoint{229.01591875, 32.5376778}, constellationPoint{229.09951625, 25.5380573}, constellationPoint{227.60549125, 25.5246105}}
|
||||||
|
constellationPolygons["CAE"] = []constellationPoint{constellationPoint{65.0764125, -39.7007294}, constellationPoint{64.88238625, -48.6996651}, constellationPoint{68.362247916667, -48.7384491}, constellationPoint{68.42409625, -46.2387962}, constellationPoint{73.402082916667, -46.2959023}, constellationPoint{73.482370416667, -42.7963676}, constellationPoint{75.97444375, -42.8255501}, constellationPoint{76.2549375, -27.0772038}, constellationPoint{73.75929625, -27.0479794}, constellationPoint{71.76333125, -27.0248775}, constellationPoint{71.72241875, -29.7746429}, constellationPoint{69.97665125, -29.7546597}, constellationPoint{69.862375416667, -36.7540054}, constellationPoint{65.12985, -36.7010231}}
|
||||||
|
constellationPolygons["CAM"] = []constellationPoint{constellationPoint{94.13108875, 55.9658089}, constellationPoint{77.606764583333, 56.1648331}, constellationPoint{77.484762083333, 52.6655540}, constellationPoint{72.840285, 52.7196465}, constellationPoint{52.31308625, 52.9366074}, constellationPoint{52.381900416667, 55.4362831}, constellationPoint{49.8540225, 55.4596519}, constellationPoint{49.91349625, 57.4593849}, constellationPoint{48.90093, 57.4684982}, constellationPoint{49.3954575, 68.4662857}, constellationPoint{54.237034583333, 68.4214401}, constellationPoint{55.30874875, 77.4163132}, constellationPoint{56.726209583333, 77.4025955}, constellationPoint{57.53049, 80.3986664}, constellationPoint{80.488894583333, 80.1478500}, constellationPoint{84.536117916667, 85.1239471}, constellationPoint{127.953615, 84.6103745}, constellationPoint{130.40275041667, 86.0975418}, constellationPoint{213.0229575, 85.9308090}, constellationPoint{216.78285625, 79.4449844}, constellationPoint{203.80918958333, 79.3629303}, constellationPoint{204.15701875, 76.3638153}, constellationPoint{195.8206125, 76.3289108}, constellationPoint{174.43479625, 76.3084106}, constellationPoint{174.53158375, 79.3083420}, constellationPoint{162.81859791667, 79.3401794}, constellationPoint{163.10541625, 81.3396072}, constellationPoint{142.191195, 81.4677658}, constellationPoint{140.61547375, 72.9741364}, constellationPoint{123.08622875, 73.1383743}, constellationPoint{122.12910125, 59.6433983}, constellationPoint{107.7531975, 59.8037262}, constellationPoint{107.8515525, 61.8031464}, constellationPoint{94.40745625, 61.9641266}}
|
||||||
|
constellationPolygons["CNC"] = []constellationPoint{constellationPoint{140.40425875, 6.4700689}, constellationPoint{122.92139125, 6.6302376}, constellationPoint{120.54834291667, 6.6549850}, constellationPoint{120.5806725, 9.6548138}, constellationPoint{118.83248875, 9.6734257}, constellationPoint{118.87160541667, 13.1732168}, constellationPoint{118.94754458333, 19.6728077}, constellationPoint{120.07012375, 19.6608200}, constellationPoint{120.17164625, 27.6602821}, constellationPoint{121.91596958333, 27.6419144}, constellationPoint{121.99323125, 33.1415138}, constellationPoint{140.645985, 32.9691162}}
|
||||||
|
constellationPolygons["CVN"] = []constellationPoint{constellationPoint{181.59450625, 33.3039627}, constellationPoint{181.59141625, 44.3039627}, constellationPoint{182.82643375, 44.3043365}, constellationPoint{182.8185225, 52.3043365}, constellationPoint{203.74239875, 52.3598061}, constellationPoint{203.79511375, 47.8599281}, constellationPoint{211.69873208333, 47.9039383}, constellationPoint{211.88893375, 30.1545391}, constellationPoint{210.77085875, 30.1475964}, constellationPoint{210.7888275, 27.8976517}, constellationPoint{203.95387208333, 27.8603134}, constellationPoint{200.22657458333, 27.8437748}, constellationPoint{200.20774791667, 31.3437366}, constellationPoint{186.55769375, 31.3074341}, constellationPoint{186.55426041667, 33.3074303}}
|
||||||
|
constellationPolygons["CMA"] = []constellationPoint{constellationPoint{93.215625, -11.0301533}, constellationPoint{111.97339958333, -11.2521448}, constellationPoint{111.67719875, -33.2504692}, constellationPoint{99.903859583333, -33.1128159}, constellationPoint{92.899067916667, -33.0282326}, constellationPoint{92.99256625, -27.2787991}}
|
||||||
|
constellationPolygons["CMI"] = []constellationPoint{constellationPoint{122.84900708333, -0.3693900}, constellationPoint{109.59966625, -0.2243290}, constellationPoint{109.61691875, 1.2755718}, constellationPoint{106.86739625, 1.3074419}, constellationPoint{106.91427458333, 5.3071680}, constellationPoint{106.66432208333, 5.3100886}, constellationPoint{106.71787958333, 9.8097754}, constellationPoint{106.7482275, 12.3095980}, constellationPoint{114.24100375, 12.2238722}, constellationPoint{114.2527275, 13.2238064}, constellationPoint{118.87160541667, 13.1732168}, constellationPoint{118.83248875, 9.6734257}, constellationPoint{120.5806725, 9.6548138}, constellationPoint{120.54834291667, 6.6549850}, constellationPoint{122.92139125, 6.6302376}}
|
||||||
|
constellationPolygons["CAP"] = []constellationPoint{constellationPoint{309.68464791667, -8.5634165}, constellationPoint{301.69369625, -8.6430750}, constellationPoint{301.72636958333, -11.6762342}, constellationPoint{301.91596958333, -27.6419144}, constellationPoint{306.89795541667, -27.5913391}, constellationPoint{321.83163625, -27.4596672}, constellationPoint{321.80777541667, -24.9597607}, constellationPoint{329.77028875, -24.9040413}, constellationPoint{329.6561625, -8.4043999}, constellationPoint{321.668415, -8.4602947}, constellationPoint{321.71645125, -14.4601107}, constellationPoint{309.74390125, -14.5631361}}
|
||||||
|
constellationPolygons["CAR"] = []constellationPoint{constellationPoint{170.15592125, -57.1843452}, constellationPoint{166.33725625, -57.1744423}, constellationPoint{133.32365541667, -56.9739723}, constellationPoint{133.38017375, -54.9742203}, constellationPoint{127.56711875, -54.9204712}, constellationPoint{127.60929125, -53.4206772}, constellationPoint{123.32011625, -53.3782196}, constellationPoint{123.38112875, -51.1285286}, constellationPoint{120.8616975, -51.1025848}, constellationPoint{90.748902083333, -50.7545471}, constellationPoint{90.693705, -52.5042114}, constellationPoint{93.19435375, -52.5345764}, constellationPoint{93.1074, -55.0340500}, constellationPoint{98.114275416667, -55.0945587}, constellationPoint{97.995077916667, -58.0938416}, constellationPoint{103.01111708333, -58.1537018}, constellationPoint{102.70331375, -64.1518784}, constellationPoint{136.09472708333, -64.4990387}, constellationPoint{135.24368708333, -75.4954681}, constellationPoint{169.85697291667, -75.6840134}, constellationPoint{170.08481125, -64.6842651}}
|
||||||
|
constellationPolygons["CAS"] = []constellationPoint{constellationPoint{344.34285125, 53.1680298}, constellationPoint{344.30402708333, 56.9179611}, constellationPoint{344.26912375, 59.7512321}, constellationPoint{348.85966375, 59.7646751}, constellationPoint{348.81649041667, 63.6812897}, constellationPoint{355.21757125, 63.6928787}, constellationPoint{355.19785958333, 66.6928711}, constellationPoint{6.76376375, 66.6924438}, constellationPoint{6.92291375, 77.6923447}, constellationPoint{55.30874875, 77.4163132}, constellationPoint{54.237034583333, 68.4214401}, constellationPoint{49.3954575, 68.4662857}, constellationPoint{48.90093, 57.4684982}, constellationPoint{38.762337083333, 57.5513000}, constellationPoint{38.802355416667, 59.0511551}, constellationPoint{30.795625416667, 59.1046104}, constellationPoint{30.77362375, 58.1046753}, constellationPoint{27.5952225, 58.1227188}, constellationPoint{27.53364125, 54.6228828}, constellationPoint{22.45601375, 54.6477699}, constellationPoint{22.40793625, 50.6478767}, constellationPoint{18.60590375, 50.6632347}, constellationPoint{18.588407916667, 48.6632690}, constellationPoint{14.7888675, 48.6757393}, constellationPoint{14.776077083333, 46.6757545}, constellationPoint{4.14327875, 46.6949348}, constellationPoint{4.1463675, 48.6949348}, constellationPoint{355.27607875, 48.6929169}, constellationPoint{355.27055708333, 50.6929131}, constellationPoint{351.4656825, 50.6870193}, constellationPoint{351.45289375, 53.1870041}}
|
||||||
|
constellationPolygons["CEN"] = []constellationPoint{constellationPoint{166.47936291667, -35.6746559}, constellationPoint{166.45650458333, -40.4246216}, constellationPoint{166.33725625, -57.1744423}, constellationPoint{170.15592125, -57.1843452}, constellationPoint{170.08481125, -64.6842651}, constellationPoint{179.05736375, -64.6957855}, constellationPoint{179.07076791667, -55.6957932}, constellationPoint{194.33451125, -55.6771049}, constellationPoint{194.43838041667, -64.6769638}, constellationPoint{204.68028625, -64.6379395}, constellationPoint{220.51497458333, -64.5390244}, constellationPoint{220.23446541667, -55.5400887}, constellationPoint{214.65681625, -55.5799522}, constellationPoint{214.45026458333, -42.5806465}, constellationPoint{225.79627958333, -42.4941750}, constellationPoint{225.63076958333, -29.9948788}, constellationPoint{190.41739958333, -30.1863899}, constellationPoint{190.42719875, -33.6863785}, constellationPoint{185.38743458333, -33.6938934}, constellationPoint{185.39029625, -35.6938896}}
|
||||||
|
constellationPolygons["CEP"] = []constellationPoint{constellationPoint{300.5732625, 59.8510780}, constellationPoint{300.48520041667, 61.8506203}, constellationPoint{306.8118675, 61.9143791}, constellationPoint{306.51738125, 67.4129562}, constellationPoint{310.33401458333, 67.4490280}, constellationPoint{309.57304041667, 75.4455261}, constellationPoint{301.87339791667, 75.3708725}, constellationPoint{300.6738, 80.3647766}, constellationPoint{313.70587375, 80.4867859}, constellationPoint{308.72097, 86.4656219}, constellationPoint{308.33135541667, 86.6306305}, constellationPoint{343.51066625, 86.8368912}, constellationPoint{339.26098791667, 88.6638870}, constellationPoint{135.83247125, 87.5689163}, constellationPoint{130.40275041667, 86.0975418}, constellationPoint{127.953615, 84.6103745}, constellationPoint{84.536117916667, 85.1239471}, constellationPoint{80.488894583333, 80.1478500}, constellationPoint{57.53049, 80.3986664}, constellationPoint{56.726209583333, 77.4025955}, constellationPoint{55.30874875, 77.4163132}, constellationPoint{6.92291375, 77.6923447}, constellationPoint{6.76376375, 66.6924438}, constellationPoint{355.19785958333, 66.6928711}, constellationPoint{355.21757125, 63.6928787}, constellationPoint{348.81649041667, 63.6812897}, constellationPoint{348.85966375, 59.7646751}, constellationPoint{344.26912375, 59.7512321}, constellationPoint{344.30402708333, 56.9179611}, constellationPoint{335.91093, 56.8825760}, constellationPoint{335.93130125, 55.6326256}, constellationPoint{333.13762625, 55.6178436}, constellationPoint{333.17467625, 53.3679428}, constellationPoint{330.63921, 53.3532715}, constellationPoint{330.60218875, 55.4364891}, constellationPoint{309.83136125, 55.2753258}, constellationPoint{309.62379458333, 61.3576965}, constellationPoint{308.66080375, 61.3486443}, constellationPoint{308.71659291667, 59.9322395}}
|
||||||
|
constellationPolygons["CET"] = []constellationPoint{constellationPoint{6.60132875, 0.6925398}, constellationPoint{6.6037875, 2.6925383}, constellationPoint{31.61526625, 2.5978806}, constellationPoint{31.6652475, 10.5143948}, constellationPoint{50.94641125, 10.3632069}, constellationPoint{50.85298375, 0.4469725}, constellationPoint{50.836682916667, -1.3029516}, constellationPoint{41.33922125, -1.2210265}, constellationPoint{41.14875875, -23.8536034}, constellationPoint{26.46599875, -23.7562580}, constellationPoint{26.45888875, -24.8729095}, constellationPoint{359.11056458333, -24.8042011}, constellationPoint{359.10329875, -6.3042021}, constellationPoint{6.5927025, -6.3074551}}
|
||||||
|
constellationPolygons["CHA"] = []constellationPoint{constellationPoint{111.65211458333, -82.7758865}, constellationPoint{209.11110875, -83.1200714}, constellationPoint{207.78143375, -75.6235962}, constellationPoint{169.85697291667, -75.6840134}, constellationPoint{135.24368708333, -75.4954681}, constellationPoint{114.21470375, -75.2899170}}
|
||||||
|
constellationPolygons["CIR"] = []constellationPoint{constellationPoint{204.68028625, -64.6379395}, constellationPoint{204.70747958333, -65.6378784}, constellationPoint{207.26802291667, -65.6249542}, constellationPoint{207.46087041667, -70.6244431}, constellationPoint{224.16644125, -70.5115433}, constellationPoint{224.00363375, -68.0122070}, constellationPoint{226.55712625, -67.9909286}, constellationPoint{226.35353541667, -64.0751266}, constellationPoint{230.16657875, -64.0415649}, constellationPoint{230.05456958333, -61.4587479}, constellationPoint{232.58976458333, -61.4353065}, constellationPoint{232.5498675, -60.4354935}, constellationPoint{232.38191125, -55.4362831}, constellationPoint{228.08351125, -55.4754944}, constellationPoint{220.23446541667, -55.5400887}, constellationPoint{220.51497458333, -64.5390244}}
|
||||||
|
constellationPolygons["COL"] = []constellationPoint{constellationPoint{75.97444375, -42.8255501}, constellationPoint{76.2549375, -27.0772038}, constellationPoint{92.99256625, -27.2787991}, constellationPoint{92.899067916667, -33.0282326}, constellationPoint{99.903859583333, -33.1128159}, constellationPoint{99.70891625, -43.1116486}, constellationPoint{90.951777083333, -43.0057793}}
|
||||||
|
constellationPolygons["COM"] = []constellationPoint{constellationPoint{179.60453541667, 13.3040485}, constellationPoint{179.60894125, 28.3040466}, constellationPoint{181.59566375, 28.3039627}, constellationPoint{181.59450625, 33.3039627}, constellationPoint{186.55426041667, 33.3074303}, constellationPoint{186.55769375, 31.3074341}, constellationPoint{200.20774791667, 31.3437366}, constellationPoint{200.22657458333, 27.8437748}, constellationPoint{203.95387208333, 27.8603134}, constellationPoint{204.02893041667, 14.3604937}, constellationPoint{194.05906625, 14.3225088}, constellationPoint{194.0620275, 13.3225126}}
|
||||||
|
constellationPolygons["CRA"] = []constellationPoint{constellationPoint{269.62546375, -37.0174599}, constellationPoint{289.59631958333, -36.7785645}, constellationPoint{289.76964, -45.2775650}, constellationPoint{272.3090175, -45.4859734}, constellationPoint{269.80928375, -45.5163460}}
|
||||||
|
constellationPolygons["CRB"] = []constellationPoint{constellationPoint{229.09951625, 25.5380573}, constellationPoint{229.01591875, 32.5376778}, constellationPoint{232.74697791667, 32.5726128}, constellationPoint{232.64365208333, 39.5721130}, constellationPoint{237.36542708333, 39.6189079}, constellationPoint{246.07194875, 39.7117195}, constellationPoint{246.2798025, 26.7128716}, constellationPoint{243.78670625, 26.6855240}, constellationPoint{243.8001825, 25.6855946}, constellationPoint{241.80573458333, 25.6641407}}
|
||||||
|
constellationPolygons["CRV"] = []constellationPoint{constellationPoint{194.1330525, -11.6773882}, constellationPoint{179.09676, -11.6957970}, constellationPoint{179.09131041667, -25.1957951}, constellationPoint{190.404525, -25.1864014}, constellationPoint{190.3985025, -22.6864090}, constellationPoint{194.16687, -22.6773415}}
|
||||||
|
constellationPolygons["CRT"] = []constellationPoint{constellationPoint{162.82713875, -6.6621790}, constellationPoint{162.80791375, -11.6621428}, constellationPoint{162.77554041667, -19.6620827}, constellationPoint{164.03058625, -19.6666222}, constellationPoint{164.00808291667, -25.1665821}, constellationPoint{179.09131041667, -25.1957951}, constellationPoint{179.09676, -11.6957970}, constellationPoint{179.09860625, -6.6957974}, constellationPoint{174.34229875, -6.6916924}}
|
||||||
|
constellationPolygons["CRU"] = []constellationPoint{constellationPoint{179.07076791667, -55.6957932}, constellationPoint{179.05736375, -64.6957855}, constellationPoint{194.43838041667, -64.6769638}, constellationPoint{194.33451125, -55.6771049}}
|
||||||
|
constellationPolygons["CYG"] = []constellationPoint{constellationPoint{290.13264625, 27.7324085}, constellationPoint{290.0952525, 30.2321968}, constellationPoint{291.5987775, 30.2493153}, constellationPoint{291.49260458333, 36.7487144}, constellationPoint{292.11965541667, 36.7558022}, constellationPoint{291.9835275, 43.7550354}, constellationPoint{288.47030708333, 43.7149391}, constellationPoint{288.37552041667, 47.7143936}, constellationPoint{287.1206475, 47.6998672}, constellationPoint{286.87645958333, 55.6984482}, constellationPoint{291.90459291667, 55.7560043}, constellationPoint{291.80955, 58.2554703}, constellationPoint{297.10055375, 58.3138733}, constellationPoint{297.03924125, 59.8135414}, constellationPoint{300.5732625, 59.8510780}, constellationPoint{308.71659291667, 59.9322395}, constellationPoint{308.66080375, 61.3486443}, constellationPoint{309.62379458333, 61.3576965}, constellationPoint{309.83136125, 55.2753258}, constellationPoint{330.60218875, 55.4364891}, constellationPoint{330.63921, 53.3532715}, constellationPoint{330.76266291667, 44.6036453}, constellationPoint{329.87860625, 44.5982513}, constellationPoint{329.88163958333, 44.3482628}, constellationPoint{329.37664041667, 44.3451195}, constellationPoint{329.4610125, 36.5953827}, constellationPoint{327.319965, 36.5815468}, constellationPoint{327.39518125, 28.5817947}, constellationPoint{322.62016375, 28.5480537}, constellationPoint{315.08391458333, 28.4871883}, constellationPoint{315.07258375, 29.4871387}, constellationPoint{296.25094375, 29.3010578}, constellationPoint{296.27220125, 27.8011742}}
|
||||||
|
constellationPolygons["DEL"] = []constellationPoint{constellationPoint{309.5798775, 2.4360874}, constellationPoint{306.07910125, 2.4021468}, constellationPoint{306.01395625, 8.9018240}, constellationPoint{303.636675, 8.8779116}, constellationPoint{303.5589975, 16.1275158}, constellationPoint{305.18694875, 16.1439629}, constellationPoint{305.13404875, 20.8936996}, constellationPoint{309.89693708333, 20.9399471}, constellationPoint{309.907665, 19.9399967}, constellationPoint{317.17878291667, 20.0046406}, constellationPoint{317.24836375, 12.3382607}, constellationPoint{314.61859625, 12.3157644}, constellationPoint{314.67109625, 6.4826641}, constellationPoint{314.045505, 6.4771614}, constellationPoint{314.08109708333, 2.4773185}}
|
||||||
|
constellationPolygons["DOR"] = []constellationPoint{constellationPoint{58.318787916667, -52.7968445}, constellationPoint{60.79789625, -52.8228111}, constellationPoint{60.69291875, -56.1555862}, constellationPoint{65.6504625, -56.2093849}, constellationPoint{65.55459, -58.7088547}, constellationPoint{69.274534583333, -58.7506638}, constellationPoint{68.79401875, -67.2479248}, constellationPoint{68.58152375, -69.7467194}, constellationPoint{98.454422916667, -70.1041336}, constellationPoint{98.93724875, -64.1070251}, constellationPoint{90.173642916667, -64.0010529}, constellationPoint{90.34506125, -61.0020981}, constellationPoint{82.85761375, -60.9112892}, constellationPoint{83.01880375, -57.4122620}, constellationPoint{75.547742916667, -57.3230400}, constellationPoint{75.6770175, -53.8238029}, constellationPoint{68.217745416667, -53.7376366}, constellationPoint{68.362247916667, -48.7384491}, constellationPoint{64.88238625, -48.6996651}, constellationPoint{62.149907916667, -48.6699715}, constellationPoint{62.098639583333, -50.6697006}, constellationPoint{58.37716625, -50.6304779}}
|
||||||
|
constellationPolygons["DRA"] = []constellationPoint{constellationPoint{140.61547375, 72.9741364}, constellationPoint{142.191195, 81.4677658}, constellationPoint{163.10541625, 81.3396072}, constellationPoint{162.81859791667, 79.3401794}, constellationPoint{174.53158375, 79.3083420}, constellationPoint{174.43479625, 76.3084106}, constellationPoint{195.8206125, 76.3289108}, constellationPoint{196.09747375, 69.3293610}, constellationPoint{210.65081125, 69.3991165}, constellationPoint{210.82055541667, 65.3996506}, constellationPoint{235.32956541667, 65.6023483}, constellationPoint{235.05063, 69.6009445}, constellationPoint{247.8410625, 69.7383041}, constellationPoint{247.2207075, 74.7347870}, constellationPoint{261.53663708333, 74.9033127}, constellationPoint{260.21790458333, 79.8953476}, constellationPoint{267.65602041667, 79.9857483}, constellationPoint{261.72223041667, 85.9495697}, constellationPoint{308.72097, 86.4656219}, constellationPoint{313.70587375, 80.4867859}, constellationPoint{300.6738, 80.3647766}, constellationPoint{301.87339791667, 75.3708725}, constellationPoint{309.57304041667, 75.4455261}, constellationPoint{310.33401458333, 67.4490280}, constellationPoint{306.51738125, 67.4129562}, constellationPoint{306.8118675, 61.9143791}, constellationPoint{300.48520041667, 61.8506203}, constellationPoint{300.5732625, 59.8510780}, constellationPoint{297.03924125, 59.8135414}, constellationPoint{297.10055375, 58.3138733}, constellationPoint{291.80955, 58.2554703}, constellationPoint{291.90459291667, 55.7560043}, constellationPoint{286.87645958333, 55.6984482}, constellationPoint{287.1206475, 47.6998672}, constellationPoint{274.34237541667, 47.5476036}, constellationPoint{274.25768875, 50.5470886}, constellationPoint{255.7863525, 50.3244438}, constellationPoint{255.75682625, 51.3242683}, constellationPoint{237.12458541667, 51.1176796}, constellationPoint{237.08447375, 52.6174774}, constellationPoint{229.65737375, 52.5451736}, constellationPoint{229.59105458333, 55.0448647}, constellationPoint{217.25124875, 54.9422379}, constellationPoint{217.04525375, 62.4414825}, constellationPoint{203.57364125, 62.3593979}, constellationPoint{203.55053875, 63.3593445}, constellationPoint{181.58155958333, 63.3039627}, constellationPoint{181.57925541667, 65.8039627}, constellationPoint{171.84934625, 65.8126068}, constellationPoint{171.96136958333, 72.8125000}}
|
||||||
|
constellationPolygons["EQU"] = []constellationPoint{constellationPoint{314.08109708333, 2.4773185}, constellationPoint{314.045505, 6.4771614}, constellationPoint{314.67109625, 6.4826641}, constellationPoint{314.61859625, 12.3157644}, constellationPoint{317.24836375, 12.3382607}, constellationPoint{318.25026458333, 12.3465548}, constellationPoint{318.244515, 13.0132008}, constellationPoint{321.50110208333, 13.0390635}, constellationPoint{321.58347125, 2.5393796}}
|
||||||
|
constellationPolygons["ERI"] = []constellationPoint{constellationPoint{55.352905416667, 0.4037257}, constellationPoint{70.852360416667, 0.2375014}, constellationPoint{71.60231375, 0.2289162}, constellationPoint{71.55635875, -3.7708201}, constellationPoint{77.804395416667, -3.8437285}, constellationPoint{77.72003125, -10.8432293}, constellationPoint{75.22175125, -10.8138046}, constellationPoint{75.178714583333, -14.3135529}, constellationPoint{73.929797916667, -14.2989721}, constellationPoint{73.75929625, -27.0479794}, constellationPoint{71.76333125, -27.0248775}, constellationPoint{71.72241875, -29.7746429}, constellationPoint{69.97665125, -29.7546597}, constellationPoint{69.862375416667, -36.7540054}, constellationPoint{65.12985, -36.7010231}, constellationPoint{65.0764125, -39.7007294}, constellationPoint{59.105884583333, -39.6368256}, constellationPoint{59.031364583333, -43.6364403}, constellationPoint{52.3267725, -43.5694046}, constellationPoint{52.2890025, -45.5692215}, constellationPoint{46.09077125, -45.5124779}, constellationPoint{46.03451375, -48.5122337}, constellationPoint{41.08530875, -48.4710045}, constellationPoint{41.04769375, -50.4708595}, constellationPoint{37.34121, -50.4425697}, constellationPoint{37.28334625, -53.4423561}, constellationPoint{33.58414375, -53.4164696}, constellationPoint{33.489424583333, -57.9161568}, constellationPoint{21.20622875, -57.8484154}, constellationPoint{21.2732625, -52.8485603}, constellationPoint{24.967354583333, -52.8658562}, constellationPoint{24.9938475, -50.8659210}, constellationPoint{28.693257083333, -50.8859215}, constellationPoint{28.738322916667, -47.5527229}, constellationPoint{36.1529475, -47.6004944}, constellationPoint{36.26401375, -39.4342155}, constellationPoint{46.187260416667, -39.5128975}, constellationPoint{46.193322083333, -39.0962563}, constellationPoint{53.64428375, -39.1650963}, constellationPoint{53.699605416667, -35.5820351}, constellationPoint{57.430512083333, -35.6192436}, constellationPoint{57.58890125, -24.0033779}, constellationPoint{41.14875875, -23.8536034}, constellationPoint{41.33922125, -1.2210265}, constellationPoint{50.836682916667, -1.3029516}, constellationPoint{55.335582083333, -1.3461887}}
|
||||||
|
constellationPolygons["FOR"] = []constellationPoint{constellationPoint{26.46599875, -23.7562580}, constellationPoint{41.14875875, -23.8536034}, constellationPoint{57.58890125, -24.0033779}, constellationPoint{57.430512083333, -35.6192436}, constellationPoint{53.699605416667, -35.5820351}, constellationPoint{53.64428375, -39.1650963}, constellationPoint{46.193322083333, -39.0962563}, constellationPoint{46.187260416667, -39.5128975}, constellationPoint{36.26401375, -39.4342155}, constellationPoint{26.350727916667, -39.3726234}, constellationPoint{26.45888875, -24.8729095}}
|
||||||
|
constellationPolygons["GEM"] = []constellationPoint{constellationPoint{96.37276375, 11.9332972}, constellationPoint{96.4439175, 17.4328651}, constellationPoint{95.069575416667, 17.4495068}, constellationPoint{95.1241275, 21.4491768}, constellationPoint{90.125155416667, 21.5098724}, constellationPoint{90.1440375, 22.8430862}, constellationPoint{90.22107125, 28.0092907}, constellationPoint{99.965657916667, 27.8913116}, constellationPoint{100.09027625, 35.3905640}, constellationPoint{112.56071875, 35.2445297}, constellationPoint{118.28970875, 35.1810532}, constellationPoint{118.25808, 33.1812286}, constellationPoint{121.99323125, 33.1415138}, constellationPoint{121.91596958333, 27.6419144}, constellationPoint{120.17164625, 27.6602821}, constellationPoint{120.07012375, 19.6608200}, constellationPoint{118.94754458333, 19.6728077}, constellationPoint{118.87160541667, 13.1732168}, constellationPoint{114.2527275, 13.2238064}, constellationPoint{114.24100375, 12.2238722}, constellationPoint{106.7482275, 12.3095980}, constellationPoint{106.71787958333, 9.8097754}, constellationPoint{105.71845958333, 9.8214874}, constellationPoint{105.742815, 11.8213453}}
|
||||||
|
constellationPolygons["GRU"] = []constellationPoint{constellationPoint{321.92805291667, -36.4592972}, constellationPoint{322.04232125, -44.9588585}, constellationPoint{322.11736625, -49.4585724}, constellationPoint{331.998825, -49.3911743}, constellationPoint{332.113695, -56.3908348}, constellationPoint{351.76852208333, -56.3126869}, constellationPoint{351.69270458333, -39.3127594}, constellationPoint{351.6833775, -36.3127670}, constellationPoint{346.72751375, -36.3249741}}
|
||||||
|
constellationPolygons["HER"] = []constellationPoint{constellationPoint{245.558595, 3.7033811}, constellationPoint{242.80966791667, 3.6735139}, constellationPoint{242.67663, 15.6728001}, constellationPoint{240.18107375, 15.6463346}, constellationPoint{240.1110075, 21.6459675}, constellationPoint{241.85657541667, 21.6644115}, constellationPoint{241.80573458333, 25.6641407}, constellationPoint{243.8001825, 25.6855946}, constellationPoint{243.78670625, 26.6855240}, constellationPoint{246.2798025, 26.7128716}, constellationPoint{246.07194875, 39.7117195}, constellationPoint{237.36542708333, 39.6189079}, constellationPoint{237.12458541667, 51.1176796}, constellationPoint{255.75682625, 51.3242683}, constellationPoint{255.7863525, 50.3244438}, constellationPoint{274.25768875, 50.5470886}, constellationPoint{274.34237541667, 47.5476036}, constellationPoint{273.46687375, 47.5369873}, constellationPoint{273.82438625, 30.0391560}, constellationPoint{276.70077291667, 30.0739765}, constellationPoint{276.76288625, 26.0743504}, constellationPoint{284.2698675, 26.1640968}, constellationPoint{284.27716208333, 25.6641407}, constellationPoint{284.33913291667, 21.2478352}, constellationPoint{284.37363625, 18.6647091}, constellationPoint{284.45626208333, 12.1651964}, constellationPoint{281.388045, 12.1287737}, constellationPoint{275.20308458333, 12.0543308}, constellationPoint{275.17327375, 14.3874788}, constellationPoint{260.17687791667, 14.2060347}, constellationPoint{260.19584708333, 12.7061481}, constellationPoint{252.7014825, 12.6179380}, constellationPoint{252.80590958333, 3.7852108}}
|
||||||
|
constellationPolygons["HOR"] = []constellationPoint{constellationPoint{65.0764125, -39.7007294}, constellationPoint{64.88238625, -48.6996651}, constellationPoint{62.149907916667, -48.6699715}, constellationPoint{62.098639583333, -50.6697006}, constellationPoint{58.37716625, -50.6304779}, constellationPoint{58.318787916667, -52.7968445}, constellationPoint{53.365002083333, -52.7470779}, constellationPoint{53.23681625, -57.0797844}, constellationPoint{48.79113125, -57.0377846}, constellationPoint{48.362689583333, -67.0358200}, constellationPoint{33.202360416667, -66.9151917}, constellationPoint{33.489424583333, -57.9161568}, constellationPoint{33.58414375, -53.4164696}, constellationPoint{37.28334625, -53.4423561}, constellationPoint{37.34121, -50.4425697}, constellationPoint{41.04769375, -50.4708595}, constellationPoint{41.08530875, -48.4710045}, constellationPoint{46.03451375, -48.5122337}, constellationPoint{46.09077125, -45.5124779}, constellationPoint{52.2890025, -45.5692215}, constellationPoint{52.3267725, -43.5694046}, constellationPoint{59.031364583333, -43.6364403}, constellationPoint{59.105884583333, -39.6368256}}
|
||||||
|
constellationPolygons["HYA"] = []constellationPoint{constellationPoint{122.84900708333, -0.3693900}, constellationPoint{122.92139125, 6.6302376}, constellationPoint{140.40425875, 6.4700689}, constellationPoint{145.39841708333, 6.4327669}, constellationPoint{145.34890625, -0.5670585}, constellationPoint{145.27027208333, -11.5667810}, constellationPoint{162.80791375, -11.6621428}, constellationPoint{162.77554041667, -19.6620827}, constellationPoint{164.03058625, -19.6666222}, constellationPoint{164.00808291667, -25.1665821}, constellationPoint{179.09131041667, -25.1957951}, constellationPoint{190.404525, -25.1864014}, constellationPoint{190.3985025, -22.6864090}, constellationPoint{194.16687, -22.6773415}, constellationPoint{215.51309125, -22.5727749}, constellationPoint{215.53369041667, -25.0727024}, constellationPoint{225.57655375, -24.9951096}, constellationPoint{225.63076958333, -29.9948788}, constellationPoint{190.41739958333, -30.1863899}, constellationPoint{190.42719875, -33.6863785}, constellationPoint{185.38743458333, -33.6938934}, constellationPoint{185.39029625, -35.6938896}, constellationPoint{166.47936291667, -35.6746559}, constellationPoint{163.95851541667, -35.6664963}, constellationPoint{163.977885, -31.8332005}, constellationPoint{160.20137875, -31.8185863}, constellationPoint{160.21289375, -29.8186131}, constellationPoint{155.18132708333, -29.7947845}, constellationPoint{155.1993375, -27.1281624}, constellationPoint{147.65928291667, -27.0835037}, constellationPoint{147.67968125, -24.5835705}, constellationPoint{141.904335, -24.5425186}, constellationPoint{137.63677625, -24.5086308}, constellationPoint{137.68497, -19.5088310}, constellationPoint{130.1635125, -19.4423733}, constellationPoint{130.18434, -17.4424706}, constellationPoint{126.92709458333, -17.4112568}, constellationPoint{126.98977958333, -11.4115648}, constellationPoint{122.73417875, -11.3687992}}
|
||||||
|
constellationPolygons["HYI"] = []constellationPoint{constellationPoint{68.79401875, -67.2479248}, constellationPoint{68.58152375, -69.7467194}, constellationPoint{67.957485, -74.7431641}, constellationPoint{52.075782083333, -74.5741272}, constellationPoint{50.091655416667, -82.0644531}, constellationPoint{1.53339125, -81.8039551}, constellationPoint{1.5662970833333, -74.3039627}, constellationPoint{12.3324375, -74.3185730}, constellationPoint{12.295414583333, -75.3185272}, constellationPoint{20.654050416667, -75.3472214}, constellationPoint{21.20622875, -57.8484154}, constellationPoint{33.489424583333, -57.9161568}, constellationPoint{33.202360416667, -66.9151917}, constellationPoint{48.362689583333, -67.0358200}}
|
||||||
|
constellationPolygons["IND"] = []constellationPoint{constellationPoint{323.1847575, -74.4544678}, constellationPoint{351.99783291667, -74.3124619}, constellationPoint{351.86139125, -66.8125992}, constellationPoint{332.3985675, -66.8899918}, constellationPoint{332.113695, -56.3908348}, constellationPoint{331.998825, -49.3911743}, constellationPoint{322.11736625, -49.4585724}, constellationPoint{322.04232125, -44.9588585}, constellationPoint{307.169295, -45.0900002}, constellationPoint{307.45880125, -56.5885773}, constellationPoint{307.56480125, -59.5880547}, constellationPoint{322.34865125, -59.4576836}}
|
||||||
|
constellationPolygons["LAC"] = []constellationPoint{constellationPoint{329.4610125, 36.5953827}, constellationPoint{329.37664041667, 44.3451195}, constellationPoint{329.88163958333, 44.3482628}, constellationPoint{329.87860625, 44.5982513}, constellationPoint{330.76266291667, 44.6036453}, constellationPoint{330.63921, 53.3532715}, constellationPoint{333.17467625, 53.3679428}, constellationPoint{333.13762625, 55.6178436}, constellationPoint{335.93130125, 55.6326256}, constellationPoint{335.91093, 56.8825760}, constellationPoint{344.30402708333, 56.9179611}, constellationPoint{344.34285125, 53.1680298}, constellationPoint{344.46530375, 35.1682358}, constellationPoint{343.70919291667, 35.1656151}, constellationPoint{343.70653208333, 35.6656113}, constellationPoint{331.35955791667, 35.6069336}, constellationPoint{331.35046041667, 36.6069069}}
|
||||||
|
constellationPolygons["LEO"] = []constellationPoint{constellationPoint{162.8497125, -0.6622211}, constellationPoint{162.87601958333, 6.3377299}, constellationPoint{145.39841708333, 6.4327669}, constellationPoint{140.40425875, 6.4700689}, constellationPoint{140.645985, 32.9691162}, constellationPoint{150.08438541667, 32.9022789}, constellationPoint{150.04234375, 27.9024086}, constellationPoint{159.23840125, 27.8529167}, constellationPoint{159.2108775, 22.8529778}, constellationPoint{162.94253875, 22.8376045}, constellationPoint{162.95149375, 24.8375893}, constellationPoint{166.6809375, 24.8250446}, constellationPoint{166.69399791667, 28.3250256}, constellationPoint{179.60894125, 28.3040466}, constellationPoint{179.60453541667, 13.3040485}, constellationPoint{179.60373458333, 10.3040485}, constellationPoint{174.36568791667, 10.3082914}, constellationPoint{174.35052458333, -0.6916979}, constellationPoint{174.34229875, -6.6916924}, constellationPoint{162.82713875, -6.6621790}}
|
||||||
|
constellationPolygons["LMI"] = []constellationPoint{constellationPoint{140.645985, 32.9691162}, constellationPoint{140.72163125, 39.2188187}, constellationPoint{145.6819725, 39.1817665}, constellationPoint{145.70923791667, 41.4316750}, constellationPoint{154.37822375, 41.3773613}, constellationPoint{154.3594125, 39.3774109}, constellationPoint{163.52316875, 39.3356133}, constellationPoint{163.48940875, 33.3356781}, constellationPoint{166.71422541667, 33.3249931}, constellationPoint{166.69399791667, 28.3250256}, constellationPoint{166.6809375, 24.8250446}, constellationPoint{162.95149375, 24.8375893}, constellationPoint{162.94253875, 22.8376045}, constellationPoint{159.2108775, 22.8529778}, constellationPoint{159.23840125, 27.8529167}, constellationPoint{150.04234375, 27.9024086}, constellationPoint{150.08438541667, 32.9022789}}
|
||||||
|
constellationPolygons["LEP"] = []constellationPoint{constellationPoint{73.75929625, -27.0479794}, constellationPoint{76.2549375, -27.0772038}, constellationPoint{92.99256625, -27.2787991}, constellationPoint{93.215625, -11.0301533}, constellationPoint{88.965790416667, -10.9785318}, constellationPoint{77.72003125, -10.8432293}, constellationPoint{75.22175125, -10.8138046}, constellationPoint{75.178714583333, -14.3135529}, constellationPoint{73.929797916667, -14.2989721}}
|
||||||
|
constellationPolygons["LIB"] = []constellationPoint{constellationPoint{227.85301208333, -0.4742887}, constellationPoint{221.6030925, -0.5269387}, constellationPoint{221.66710791667, -8.5266848}, constellationPoint{215.40850625, -8.5731344}, constellationPoint{215.51309125, -22.5727749}, constellationPoint{215.53369041667, -25.0727024}, constellationPoint{225.57655375, -24.9951096}, constellationPoint{225.63076958333, -29.9948788}, constellationPoint{236.92998, -29.8896160}, constellationPoint{236.81306375, -20.3902016}, constellationPoint{240.57177625, -20.3516178}, constellationPoint{240.43727875, -8.3523235}, constellationPoint{240.38695375, -3.6025870}, constellationPoint{227.88195125, -3.7241600}}
|
||||||
|
constellationPolygons["LUP"] = []constellationPoint{constellationPoint{214.65681625, -55.5799522}, constellationPoint{220.23446541667, -55.5400887}, constellationPoint{228.08351125, -55.4754944}, constellationPoint{228.05671625, -54.4756165}, constellationPoint{232.35337208333, -54.4364166}, constellationPoint{232.20724625, -48.4371071}, constellationPoint{237.24728125, -48.3880234}, constellationPoint{237.12458541667, -42.3886375}, constellationPoint{242.15280625, -42.3366776}, constellationPoint{241.94769875, -29.8377628}, constellationPoint{236.92998, -29.8896160}, constellationPoint{225.63076958333, -29.9948788}, constellationPoint{225.79627958333, -42.4941750}, constellationPoint{214.45026458333, -42.5806465}}
|
||||||
|
constellationPolygons["LYN"] = []constellationPoint{constellationPoint{112.56071875, 35.2445297}, constellationPoint{112.73412458333, 44.2435493}, constellationPoint{104.26530291667, 44.3418388}, constellationPoint{104.40635875, 49.8410034}, constellationPoint{99.919459583333, 49.8945885}, constellationPoint{100.04603125, 53.8938293}, constellationPoint{94.05736625, 53.9662552}, constellationPoint{94.13108875, 55.9658089}, constellationPoint{94.40745625, 61.9641266}, constellationPoint{107.8515525, 61.8031464}, constellationPoint{107.7531975, 59.8037262}, constellationPoint{122.12910125, 59.6433983}, constellationPoint{128.79913375, 59.5759888}, constellationPoint{128.44010375, 46.5777283}, constellationPoint{139.59071125, 46.4782791}, constellationPoint{139.51249041667, 41.4785957}, constellationPoint{145.70923791667, 41.4316750}, constellationPoint{145.6819725, 39.1817665}, constellationPoint{140.72163125, 39.2188187}, constellationPoint{140.645985, 32.9691162}, constellationPoint{121.99323125, 33.1415138}, constellationPoint{118.25808, 33.1812286}, constellationPoint{118.28970875, 35.1810532}}
|
||||||
|
constellationPolygons["LYR"] = []constellationPoint{constellationPoint{284.27716208333, 25.6641407}, constellationPoint{284.2698675, 26.1640968}, constellationPoint{276.76288625, 26.0743504}, constellationPoint{276.70077291667, 30.0739765}, constellationPoint{273.82438625, 30.0391560}, constellationPoint{273.46687375, 47.5369873}, constellationPoint{274.34237541667, 47.5476036}, constellationPoint{287.1206475, 47.6998672}, constellationPoint{288.37552041667, 47.7143936}, constellationPoint{288.47030708333, 43.7149391}, constellationPoint{291.9835275, 43.7550354}, constellationPoint{292.11965541667, 36.7558022}, constellationPoint{291.49260458333, 36.7487144}, constellationPoint{291.5987775, 30.2493153}, constellationPoint{290.0952525, 30.2321968}, constellationPoint{290.13264625, 27.7324085}, constellationPoint{290.16131375, 25.7325745}}
|
||||||
|
constellationPolygons["MEN"] = []constellationPoint{constellationPoint{109.01970875, -85.2614441}, constellationPoint{48.23292, -84.5553818}, constellationPoint{50.091655416667, -82.0644531}, constellationPoint{52.075782083333, -74.5741272}, constellationPoint{67.957485, -74.7431641}, constellationPoint{68.58152375, -69.7467194}, constellationPoint{98.454422916667, -70.1041336}, constellationPoint{97.770709583333, -75.1000366}, constellationPoint{114.21470375, -75.2899170}, constellationPoint{111.65211458333, -82.7758865}}
|
||||||
|
constellationPolygons["MIC"] = []constellationPoint{constellationPoint{306.89795541667, -27.5913391}, constellationPoint{321.83163625, -27.4596672}, constellationPoint{321.92805291667, -36.4592972}, constellationPoint{322.04232125, -44.9588585}, constellationPoint{307.169295, -45.0900002}}
|
||||||
|
constellationPolygons["MON"] = []constellationPoint{constellationPoint{95.225680416667, -0.0537102}, constellationPoint{95.34803125, 9.9455481}, constellationPoint{96.347665416667, 9.9334478}, constellationPoint{96.37276375, 11.9332972}, constellationPoint{105.742815, 11.8213453}, constellationPoint{105.71845958333, 9.8214874}, constellationPoint{106.71787958333, 9.8097754}, constellationPoint{106.66432208333, 5.3100886}, constellationPoint{106.91427458333, 5.3071680}, constellationPoint{106.86739625, 1.3074419}, constellationPoint{109.61691875, 1.2755718}, constellationPoint{109.59966625, -0.2243290}, constellationPoint{122.84900708333, -0.3693900}, constellationPoint{122.73417875, -11.3687992}, constellationPoint{111.97339958333, -11.2521448}, constellationPoint{93.215625, -11.0301533}, constellationPoint{88.965790416667, -10.9785318}, constellationPoint{89.052372083333, -3.9790573}, constellationPoint{95.177142083333, -4.0534163}}
|
||||||
|
constellationPolygons["MUS"] = []constellationPoint{constellationPoint{170.08481125, -64.6842651}, constellationPoint{169.85697291667, -75.6840134}, constellationPoint{207.78143375, -75.6235962}, constellationPoint{207.46087041667, -70.6244431}, constellationPoint{207.26802291667, -65.6249542}, constellationPoint{204.70747958333, -65.6378784}, constellationPoint{204.68028625, -64.6379395}, constellationPoint{194.43838041667, -64.6769638}, constellationPoint{179.05736375, -64.6957855}}
|
||||||
|
constellationPolygons["NOR"] = []constellationPoint{constellationPoint{232.5498675, -60.4354935}, constellationPoint{249.03468125, -60.2644577}, constellationPoint{248.57062375, -45.7670517}, constellationPoint{248.4947775, -42.2674789}, constellationPoint{242.15280625, -42.3366776}, constellationPoint{237.12458541667, -42.3886375}, constellationPoint{237.24728125, -48.3880234}, constellationPoint{232.20724625, -48.4371071}, constellationPoint{232.35337208333, -54.4364166}, constellationPoint{228.05671625, -54.4756165}, constellationPoint{228.08351125, -55.4754944}, constellationPoint{232.38191125, -55.4362831}}
|
||||||
|
constellationPolygons["OCT"] = []constellationPoint{constellationPoint{0.80064625, -89.3039017}, constellationPoint{1.53339125, -81.8039551}, constellationPoint{50.091655416667, -82.0644531}, constellationPoint{48.23292, -84.5553818}, constellationPoint{109.01970875, -85.2614441}, constellationPoint{111.65211458333, -82.7758865}, constellationPoint{209.11110875, -83.1200714}, constellationPoint{276.86599791667, -82.4582748}, constellationPoint{274.19506041667, -74.9745178}, constellationPoint{323.1847575, -74.4544678}, constellationPoint{351.99783291667, -74.3124619}, constellationPoint{1.56630625, -74.3039627}, constellationPoint{0.80064625, -89.3039017}, constellationPoint{0.80065208333333, -89.3038940}}
|
||||||
|
constellationPolygons["OPH"] = []constellationPoint{constellationPoint{245.6026275, -0.2963768}, constellationPoint{245.558595, 3.7033811}, constellationPoint{252.80590958333, 3.7852108}, constellationPoint{252.7014825, 12.6179380}, constellationPoint{260.19584708333, 12.7061481}, constellationPoint{260.17687791667, 14.2060347}, constellationPoint{275.17327375, 14.3874788}, constellationPoint{275.20308458333, 12.0543308}, constellationPoint{281.388045, 12.1287737}, constellationPoint{281.45856875, 6.3791943}, constellationPoint{275.27461041667, 6.3047633}, constellationPoint{275.29601125, 4.5548930}, constellationPoint{277.87113125, 4.5860157}, constellationPoint{277.88929958333, 3.0861249}, constellationPoint{275.31423625, 3.0550034}, constellationPoint{275.35059875, 0.0552235}, constellationPoint{269.10103791667, -0.0206471}, constellationPoint{269.14970375, -4.0203514}, constellationPoint{271.14967375, -3.9960551}, constellationPoint{271.22371625, -9.9956055}, constellationPoint{266.72375708333, -10.0502338}, constellationPoint{266.7447, -11.7167768}, constellationPoint{265.49440375, -11.7319136}, constellationPoint{265.47349041667, -10.0653696}, constellationPoint{259.22206958333, -10.1404381}, constellationPoint{259.29742791667, -16.1399899}, constellationPoint{265.80019041667, -16.0618820}, constellationPoint{266.00183541667, -30.0606632}, constellationPoint{253.23534875, -30.2123089}, constellationPoint{253.15567041667, -24.7960968}, constellationPoint{245.89144625, -24.8781185}, constellationPoint{245.82298375, -19.5451660}, constellationPoint{247.45067541667, -19.5271549}, constellationPoint{247.43823, -18.5272255}, constellationPoint{245.81068041667, -18.5452347}, constellationPoint{245.69120375, -8.2958899}, constellationPoint{240.43727875, -8.3523235}, constellationPoint{240.38695375, -3.6025870}, constellationPoint{245.63838875, -3.5461800}}
|
||||||
|
constellationPolygons["ORI"] = []constellationPoint{constellationPoint{70.852360416667, 0.2375014}, constellationPoint{71.03402875, 15.7364635}, constellationPoint{76.28892625, 15.6755352}, constellationPoint{76.29527875, 16.1754990}, constellationPoint{81.7987125, 16.1101055}, constellationPoint{81.7922325, 15.6101446}, constellationPoint{85.79364625, 15.5619202}, constellationPoint{85.755057083333, 12.5621548}, constellationPoint{88.255369583333, 12.5318508}, constellationPoint{88.327167083333, 18.0314159}, constellationPoint{87.326982083333, 18.0435486}, constellationPoint{87.39379375, 22.8764725}, constellationPoint{90.1440375, 22.8430862}, constellationPoint{90.125155416667, 21.5098724}, constellationPoint{95.1241275, 21.4491768}, constellationPoint{95.069575416667, 17.4495068}, constellationPoint{96.4439175, 17.4328651}, constellationPoint{96.37276375, 11.9332972}, constellationPoint{96.347665416667, 9.9334478}, constellationPoint{95.34803125, 9.9455481}, constellationPoint{95.225680416667, -0.0537102}, constellationPoint{95.177142083333, -4.0534163}, constellationPoint{89.052372083333, -3.9790573}, constellationPoint{88.965790416667, -10.9785318}, constellationPoint{77.72003125, -10.8432293}, constellationPoint{77.804395416667, -3.8437285}, constellationPoint{71.55635875, -3.7708201}, constellationPoint{71.60231375, 0.2289162}}
|
||||||
|
constellationPolygons["PAV"] = []constellationPoint{constellationPoint{274.19506041667, -74.9745178}, constellationPoint{323.1847575, -74.4544678}, constellationPoint{322.34865125, -59.4576836}, constellationPoint{307.56480125, -59.5880547}, constellationPoint{307.45880125, -56.5885773}, constellationPoint{272.67225291667, -56.9837723}, constellationPoint{265.16818958333, -57.0747757}, constellationPoint{265.77572875, -67.5711060}, constellationPoint{273.28007708333, -67.4800797}}
|
||||||
|
constellationPolygons["PEG"] = []constellationPoint{constellationPoint{321.58347125, 2.5393796}, constellationPoint{321.50110208333, 13.0390635}, constellationPoint{318.244515, 13.0132008}, constellationPoint{318.25026458333, 12.3465548}, constellationPoint{317.24836375, 12.3382607}, constellationPoint{317.17878291667, 20.0046406}, constellationPoint{320.18840875, 20.0290813}, constellationPoint{320.15170041667, 24.0289364}, constellationPoint{322.66201958333, 24.0482101}, constellationPoint{322.62016375, 28.5480537}, constellationPoint{327.39518125, 28.5817947}, constellationPoint{327.319965, 36.5815468}, constellationPoint{329.4610125, 36.5953827}, constellationPoint{331.35046041667, 36.6069069}, constellationPoint{331.35955791667, 35.6069336}, constellationPoint{343.70653208333, 35.6656113}, constellationPoint{343.70919291667, 35.1656151}, constellationPoint{344.46530375, 35.1682358}, constellationPoint{354.04417958333, 35.1913109}, constellationPoint{354.04915791667, 32.7746468}, constellationPoint{357.8280825, 32.7785072}, constellationPoint{357.82874125, 32.0285034}, constellationPoint{1.6069770833333, 32.0293655}, constellationPoint{1.60621625, 28.6960354}, constellationPoint{2.6128425, 28.6957588}, constellationPoint{2.61001625, 22.6957588}, constellationPoint{3.7406445833333, 22.6951923}, constellationPoint{3.73992125, 21.6951923}, constellationPoint{3.7341179166667, 13.1951942}, constellationPoint{1.6031745833333, 13.1960354}, constellationPoint{1.6027304166667, 10.6960354}, constellationPoint{359.09711875, 10.6957970}, constellationPoint{359.09803375, 8.1957970}, constellationPoint{342.82141625, 8.1621685}, constellationPoint{342.84221708333, 2.6622071}, constellationPoint{331.58726708333, 2.6076074}, constellationPoint{331.588755, 2.3576119}, constellationPoint{326.58708625, 2.3256910}, constellationPoint{326.58021875, 3.3256676}, constellationPoint{323.57874875, 3.3043909}, constellationPoint{323.58427041667, 2.5544112}}
|
||||||
|
constellationPolygons["PER"] = []constellationPoint{constellationPoint{42.62838, 31.1865025}, constellationPoint{42.666467916667, 34.5196762}, constellationPoint{40.402382916667, 34.5375137}, constellationPoint{40.43465875, 37.2873878}, constellationPoint{39.67934125, 37.2931557}, constellationPoint{39.88547875, 51.0423737}, constellationPoint{32.67380125, 51.0925827}, constellationPoint{32.62149125, 47.5927505}, constellationPoint{26.931439583333, 47.6258430}, constellationPoint{26.96852375, 50.6257439}, constellationPoint{22.40793625, 50.6478767}, constellationPoint{22.45601375, 54.6477699}, constellationPoint{27.53364125, 54.6228828}, constellationPoint{27.5952225, 58.1227188}, constellationPoint{30.77362375, 58.1046753}, constellationPoint{30.795625416667, 59.1046104}, constellationPoint{38.802355416667, 59.0511551}, constellationPoint{38.762337083333, 57.5513000}, constellationPoint{48.90093, 57.4684982}, constellationPoint{49.91349625, 57.4593849}, constellationPoint{49.8540225, 55.4596519}, constellationPoint{52.381900416667, 55.4362831}, constellationPoint{52.31308625, 52.9366074}, constellationPoint{72.840285, 52.7196465}, constellationPoint{72.45734375, 36.2218513}, constellationPoint{69.57384125, 36.2547150}, constellationPoint{69.4869375, 30.9218750}, constellationPoint{52.426667916667, 31.1003609}}
|
||||||
|
constellationPolygons["PHE"] = []constellationPoint{constellationPoint{351.69270458333, -39.3127594}, constellationPoint{351.76852208333, -56.3126869}, constellationPoint{351.77839375, -57.8126793}, constellationPoint{21.20622875, -57.8484154}, constellationPoint{21.2732625, -52.8485603}, constellationPoint{24.967354583333, -52.8658562}, constellationPoint{24.9938475, -50.8659210}, constellationPoint{28.693257083333, -50.8859215}, constellationPoint{28.738322916667, -47.5527229}, constellationPoint{36.1529475, -47.6004944}, constellationPoint{36.26401375, -39.4342155}, constellationPoint{26.350727916667, -39.3726234}}
|
||||||
|
constellationPolygons["PIC"] = []constellationPoint{constellationPoint{90.951777083333, -43.0057793}, constellationPoint{75.97444375, -42.8255501}, constellationPoint{73.482370416667, -42.7963676}, constellationPoint{73.402082916667, -46.2959023}, constellationPoint{68.42409625, -46.2387962}, constellationPoint{68.362247916667, -48.7384491}, constellationPoint{68.217745416667, -53.7376366}, constellationPoint{75.6770175, -53.8238029}, constellationPoint{75.547742916667, -57.3230400}, constellationPoint{83.01880375, -57.4122620}, constellationPoint{82.85761375, -60.9112892}, constellationPoint{90.34506125, -61.0020981}, constellationPoint{90.173642916667, -64.0010529}, constellationPoint{98.93724875, -64.1070251}, constellationPoint{102.70331375, -64.1518784}, constellationPoint{103.01111708333, -58.1537018}, constellationPoint{97.995077916667, -58.0938416}, constellationPoint{98.114275416667, -55.0945587}, constellationPoint{93.1074, -55.0340500}, constellationPoint{93.19435375, -52.5345764}, constellationPoint{90.693705, -52.5042114}, constellationPoint{90.748902083333, -50.7545471}}
|
||||||
|
constellationPolygons["PSC"] = []constellationPoint{constellationPoint{342.8497125, 0.6622211}, constellationPoint{342.84221708333, 2.6622071}, constellationPoint{342.82141625, 8.1621685}, constellationPoint{359.09803375, 8.1957970}, constellationPoint{359.09711875, 10.6957970}, constellationPoint{1.6027304166667, 10.6960354}, constellationPoint{1.6031745833333, 13.1960354}, constellationPoint{3.7341179166667, 13.1951942}, constellationPoint{3.73992125, 21.6951923}, constellationPoint{14.414815416667, 21.6766376}, constellationPoint{14.424064583333, 24.4266243}, constellationPoint{12.41349125, 24.4319324}, constellationPoint{12.44306375, 33.6818962}, constellationPoint{22.89742625, 33.6453705}, constellationPoint{22.86642, 28.6454391}, constellationPoint{26.76471, 28.6262817}, constellationPoint{26.744674583333, 25.6263351}, constellationPoint{26.65573375, 10.5432396}, constellationPoint{31.6652475, 10.5143948}, constellationPoint{31.61526625, 2.5978806}, constellationPoint{6.6037875, 2.6925383}, constellationPoint{6.60132875, 0.6925398}, constellationPoint{6.5927025, -6.3074551}, constellationPoint{359.10329875, -6.3042021}, constellationPoint{359.10221125, -3.3042023}, constellationPoint{342.86470375, -3.3377509}}
|
||||||
|
constellationPolygons["PSA"] = []constellationPoint{constellationPoint{346.68096625, -24.8250446}, constellationPoint{329.77028875, -24.9040413}, constellationPoint{321.80777541667, -24.9597607}, constellationPoint{321.83163625, -27.4596672}, constellationPoint{321.92805291667, -36.4592972}, constellationPoint{346.72751375, -36.3249741}}
|
||||||
|
constellationPolygons["PUP"] = []constellationPoint{constellationPoint{111.97339958333, -11.2521448}, constellationPoint{111.67719875, -33.2504692}, constellationPoint{99.903859583333, -33.1128159}, constellationPoint{99.70891625, -43.1116486}, constellationPoint{90.951777083333, -43.0057793}, constellationPoint{90.748902083333, -50.7545471}, constellationPoint{120.8616975, -51.1025848}, constellationPoint{121.03827875, -43.3535042}, constellationPoint{126.57231291667, -43.4095192}, constellationPoint{126.67779875, -37.1600380}, constellationPoint{126.92709458333, -17.4112568}, constellationPoint{126.98977958333, -11.4115648}, constellationPoint{122.73417875, -11.3687992}}
|
||||||
|
constellationPolygons["PYX"] = []constellationPoint{constellationPoint{126.92709458333, -17.4112568}, constellationPoint{130.18434, -17.4424706}, constellationPoint{130.1635125, -19.4423733}, constellationPoint{137.68497, -19.5088310}, constellationPoint{137.63677625, -24.5086308}, constellationPoint{141.904335, -24.5425186}, constellationPoint{141.77159875, -37.2920151}, constellationPoint{126.67779875, -37.1600380}}
|
||||||
|
constellationPolygons["RET"] = []constellationPoint{constellationPoint{48.362689583333, -67.0358200}, constellationPoint{68.79401875, -67.2479248}, constellationPoint{69.274534583333, -58.7506638}, constellationPoint{65.55459, -58.7088547}, constellationPoint{65.6504625, -56.2093849}, constellationPoint{60.69291875, -56.1555862}, constellationPoint{60.79789625, -52.8228111}, constellationPoint{58.318787916667, -52.7968445}, constellationPoint{53.365002083333, -52.7470779}, constellationPoint{53.23681625, -57.0797844}, constellationPoint{48.79113125, -57.0377846}}
|
||||||
|
constellationPolygons["SGE"] = []constellationPoint{constellationPoint{284.37363625, 18.6647091}, constellationPoint{284.33913291667, 21.2478352}, constellationPoint{290.09631125, 21.3148155}, constellationPoint{290.12128791667, 19.3982983}, constellationPoint{298.88568875, 19.4955387}, constellationPoint{298.860255, 21.5787334}, constellationPoint{305.12540875, 21.6436558}, constellationPoint{305.13404875, 20.8936996}, constellationPoint{305.18694875, 16.1439629}, constellationPoint{303.5589975, 16.1275158}, constellationPoint{298.926, 16.0790844}, constellationPoint{298.92116541667, 16.4957294}, constellationPoint{286.4054775, 16.3550682}, constellationPoint{286.37549375, 18.6882229}}
|
||||||
|
constellationPolygons["SGR"] = []constellationPoint{constellationPoint{284.74405375, -11.8664360}, constellationPoint{284.79372, -15.8328123}, constellationPoint{275.54952625, -15.9435720}, constellationPoint{265.80019041667, -16.0618820}, constellationPoint{266.00183541667, -30.0606632}, constellationPoint{269.50281125, -30.0182076}, constellationPoint{269.62546375, -37.0174599}, constellationPoint{289.59631958333, -36.7785645}, constellationPoint{289.76964, -45.2775650}, constellationPoint{307.169295, -45.0900002}, constellationPoint{306.89795541667, -27.5913391}, constellationPoint{301.91596958333, -27.6419144}, constellationPoint{301.72636958333, -11.6762342}}
|
||||||
|
constellationPolygons["SCO"] = []constellationPoint{constellationPoint{240.43727875, -8.3523235}, constellationPoint{245.69120375, -8.2958899}, constellationPoint{245.81068041667, -18.5452347}, constellationPoint{247.43823, -18.5272255}, constellationPoint{247.45067541667, -19.5271549}, constellationPoint{245.82298375, -19.5451660}, constellationPoint{245.89144625, -24.8781185}, constellationPoint{253.15567041667, -24.7960968}, constellationPoint{253.23534875, -30.2123089}, constellationPoint{266.00183541667, -30.0606632}, constellationPoint{269.50281125, -30.0182076}, constellationPoint{269.62546375, -37.0174599}, constellationPoint{269.80928375, -45.5163460}, constellationPoint{248.57062375, -45.7670517}, constellationPoint{248.4947775, -42.2674789}, constellationPoint{242.15280625, -42.3366776}, constellationPoint{241.94769875, -29.8377628}, constellationPoint{236.92998, -29.8896160}, constellationPoint{236.81306375, -20.3902016}, constellationPoint{240.57177625, -20.3516178}}
|
||||||
|
constellationPolygons["SCL"] = []constellationPoint{constellationPoint{346.68096625, -24.8250446}, constellationPoint{359.11056458333, -24.8042011}, constellationPoint{26.45888875, -24.8729095}, constellationPoint{26.350727916667, -39.3726234}, constellationPoint{351.69270458333, -39.3127594}, constellationPoint{351.6833775, -36.3127670}, constellationPoint{346.72751375, -36.3249741}}
|
||||||
|
constellationPolygons["SCT"] = []constellationPoint{constellationPoint{275.54952625, -15.9435720}, constellationPoint{284.79372, -15.8328123}, constellationPoint{284.74405375, -11.8664360}, constellationPoint{284.64729291667, -3.8336766}, constellationPoint{280.3981875, -3.8842230}, constellationPoint{275.3991225, -3.9444826}}
|
||||||
|
constellationPolygons["SER1"] = []constellationPoint{constellationPoint{227.85301208333, -0.4742887}, constellationPoint{227.78148625, 7.5253930}, constellationPoint{227.60549125, 25.5246105}, constellationPoint{229.09951625, 25.5380573}, constellationPoint{241.80573458333, 25.6641407}, constellationPoint{241.85657541667, 21.6644115}, constellationPoint{240.1110075, 21.6459675}, constellationPoint{240.18107375, 15.6463346}, constellationPoint{242.67663, 15.6728001}, constellationPoint{242.80966791667, 3.6735139}, constellationPoint{245.558595, 3.7033811}, constellationPoint{245.6026275, -0.2963768}, constellationPoint{245.63838875, -3.5461800}, constellationPoint{240.38695375, -3.6025870}, constellationPoint{227.88195125, -3.7241600}}
|
||||||
|
constellationPolygons["SER2"] = []constellationPoint{constellationPoint{275.35059875, 0.0552235}, constellationPoint{275.31423625, 3.0550034}, constellationPoint{277.93922375, 3.0867271}, constellationPoint{277.92105625, 4.5866175}, constellationPoint{275.29601125, 4.5548930}, constellationPoint{275.27461041667, 6.3047633}, constellationPoint{281.45856875, 6.3791943}, constellationPoint{284.525985, 6.4156075}, constellationPoint{284.57642541667, 2.1659052}, constellationPoint{280.3262325, 2.1153460}, constellationPoint{280.35020875, 0.1154895}, constellationPoint{280.3981875, -3.8842230}, constellationPoint{275.3991225, -3.9444826}, constellationPoint{275.54952625, -15.9435720}, constellationPoint{265.80019041667, -16.0618820}, constellationPoint{259.29742791667, -16.1399899}, constellationPoint{259.22206958333, -10.1404381}, constellationPoint{265.47349041667, -10.0653696}, constellationPoint{265.49440375, -11.7319136}, constellationPoint{266.7447, -11.7167768}, constellationPoint{266.72375708333, -10.0502338}, constellationPoint{271.22371625, -9.9956055}, constellationPoint{271.14967375, -3.9960551}, constellationPoint{269.14970375, -4.0203514}, constellationPoint{269.10103791667, -0.0206471}}
|
||||||
|
constellationPolygons["SEX"] = []constellationPoint{constellationPoint{145.34890625, -0.5670585}, constellationPoint{145.39841708333, 6.4327669}, constellationPoint{162.87601958333, 6.3377299}, constellationPoint{162.8497125, -0.6622211}, constellationPoint{162.82713875, -6.6621790}, constellationPoint{162.80791375, -11.6621428}, constellationPoint{145.27027208333, -11.5667810}}
|
||||||
|
constellationPolygons["TAU"] = []constellationPoint{constellationPoint{50.836682916667, -1.3029516}, constellationPoint{50.85298375, 0.4469725}, constellationPoint{50.94641125, 10.3632069}, constellationPoint{51.037234583333, 19.4461136}, constellationPoint{52.2906225, 19.4343338}, constellationPoint{52.426667916667, 31.1003609}, constellationPoint{69.4869375, 30.9218750}, constellationPoint{69.47678875, 30.2552605}, constellationPoint{73.235342916667, 30.2123089}, constellationPoint{73.212475416667, 28.7124405}, constellationPoint{90.228902916667, 28.5092430}, constellationPoint{90.22107125, 28.0092907}, constellationPoint{90.1440375, 22.8430862}, constellationPoint{87.39379375, 22.8764725}, constellationPoint{87.326982083333, 18.0435486}, constellationPoint{88.327167083333, 18.0314159}, constellationPoint{88.255369583333, 12.5318508}, constellationPoint{85.755057083333, 12.5621548}, constellationPoint{85.79364625, 15.5619202}, constellationPoint{81.7922325, 15.6101446}, constellationPoint{81.7987125, 16.1101055}, constellationPoint{76.29527875, 16.1754990}, constellationPoint{76.28892625, 15.6755352}, constellationPoint{71.03402875, 15.7364635}, constellationPoint{70.852360416667, 0.2375014}, constellationPoint{55.352905416667, 0.4037257}, constellationPoint{55.335582083333, -1.3461887}}
|
||||||
|
constellationPolygons["TEL"] = []constellationPoint{constellationPoint{307.45880125, -56.5885773}, constellationPoint{307.169295, -45.0900002}, constellationPoint{289.76964, -45.2775650}, constellationPoint{272.3090175, -45.4859734}, constellationPoint{272.67225291667, -56.9837723}}
|
||||||
|
constellationPolygons["TRI"] = []constellationPoint{constellationPoint{26.744674583333, 25.6263351}, constellationPoint{26.76471, 28.6262817}, constellationPoint{22.86642, 28.6454391}, constellationPoint{22.89742625, 33.6453705}, constellationPoint{22.910835, 35.6453362}, constellationPoint{31.854250416667, 35.5971375}, constellationPoint{31.87109125, 37.3470840}, constellationPoint{39.67934125, 37.2931557}, constellationPoint{40.43465875, 37.2873878}, constellationPoint{40.402382916667, 34.5375137}, constellationPoint{42.666467916667, 34.5196762}, constellationPoint{42.62838, 31.1865025}, constellationPoint{38.10319375, 31.2213154}, constellationPoint{38.07014875, 27.8047638}, constellationPoint{30.53061625, 27.8550186}, constellationPoint{30.51371125, 25.6050701}}
|
||||||
|
constellationPolygons["TRA"] = []constellationPoint{constellationPoint{224.16644125, -70.5115433}, constellationPoint{224.00363375, -68.0122070}, constellationPoint{226.55712625, -67.9909286}, constellationPoint{226.35353541667, -64.0751266}, constellationPoint{230.16657875, -64.0415649}, constellationPoint{230.05456958333, -61.4587479}, constellationPoint{232.58976458333, -61.4353065}, constellationPoint{232.5498675, -60.4354935}, constellationPoint{249.03468125, -60.2644577}, constellationPoint{249.08163, -61.2641945}, constellationPoint{251.53784708333, -61.2364578}, constellationPoint{251.67632125, -63.8189964}, constellationPoint{254.1951375, -63.7900925}, constellationPoint{254.28351458333, -65.2062531}, constellationPoint{255.5423925, -65.1916428}, constellationPoint{255.72498291667, -67.6905823}, constellationPoint{258.2424825, -67.6610870}, constellationPoint{258.47067875, -70.1597443}}
|
||||||
|
constellationPolygons["TUC"] = []constellationPoint{constellationPoint{351.99783291667, -74.3124619}, constellationPoint{1.5662970833333, -74.3039627}, constellationPoint{12.3324375, -74.3185730}, constellationPoint{12.295414583333, -75.3185272}, constellationPoint{20.654050416667, -75.3472214}, constellationPoint{21.20622875, -57.8484154}, constellationPoint{351.77839375, -57.8126793}, constellationPoint{351.76852208333, -56.3126869}, constellationPoint{332.113695, -56.3908348}, constellationPoint{332.3985675, -66.8899918}, constellationPoint{351.86139125, -66.8125992}}
|
||||||
|
constellationPolygons["UMA"] = []constellationPoint{constellationPoint{145.70923791667, 41.4316750}, constellationPoint{139.51249041667, 41.4785957}, constellationPoint{139.59071125, 46.4782791}, constellationPoint{128.44010375, 46.5777283}, constellationPoint{128.79913375, 59.5759888}, constellationPoint{122.12910125, 59.6433983}, constellationPoint{123.08622875, 73.1383743}, constellationPoint{140.61547375, 72.9741364}, constellationPoint{171.96136958333, 72.8125000}, constellationPoint{171.84934625, 65.8126068}, constellationPoint{181.57925541667, 65.8039627}, constellationPoint{181.58155958333, 63.3039627}, constellationPoint{203.55053875, 63.3593445}, constellationPoint{203.57364125, 62.3593979}, constellationPoint{217.04525375, 62.4414825}, constellationPoint{217.25124875, 54.9422379}, constellationPoint{211.58439125, 54.9035759}, constellationPoint{211.69873208333, 47.9039383}, constellationPoint{203.79511375, 47.8599281}, constellationPoint{203.74239875, 52.3598061}, constellationPoint{182.8185225, 52.3043365}, constellationPoint{182.82643375, 44.3043365}, constellationPoint{181.59141625, 44.3039627}, constellationPoint{181.59450625, 33.3039627}, constellationPoint{181.59566375, 28.3039627}, constellationPoint{179.60894125, 28.3040466}, constellationPoint{166.69399791667, 28.3250256}, constellationPoint{166.71422541667, 33.3249931}, constellationPoint{163.48940875, 33.3356781}, constellationPoint{163.52316875, 39.3356133}, constellationPoint{154.3594125, 39.3774109}, constellationPoint{154.37822375, 41.3773613}}
|
||||||
|
constellationPolygons["UMI"] = []constellationPoint{constellationPoint{195.8206125, 76.3289108}, constellationPoint{196.09747375, 69.3293610}, constellationPoint{210.65081125, 69.3991165}, constellationPoint{210.82055541667, 65.3996506}, constellationPoint{235.32956541667, 65.6023483}, constellationPoint{235.05063, 69.6009445}, constellationPoint{247.8410625, 69.7383041}, constellationPoint{247.2207075, 74.7347870}, constellationPoint{261.53663708333, 74.9033127}, constellationPoint{260.21790458333, 79.8953476}, constellationPoint{267.65602041667, 79.9857483}, constellationPoint{261.72223041667, 85.9495697}, constellationPoint{308.72097, 86.4656219}, constellationPoint{308.33135541667, 86.6306305}, constellationPoint{343.51066625, 86.8368912}, constellationPoint{339.26098791667, 88.6638870}, constellationPoint{135.83247125, 87.5689163}, constellationPoint{130.40275041667, 86.0975418}, constellationPoint{213.0229575, 85.9308090}, constellationPoint{216.78285625, 79.4449844}, constellationPoint{203.80918958333, 79.3629303}, constellationPoint{204.15701875, 76.3638153}}
|
||||||
|
constellationPolygons["VEL"] = []constellationPoint{constellationPoint{166.33725625, -57.1744423}, constellationPoint{166.45650458333, -40.4246216}, constellationPoint{141.73406125, -40.2918739}, constellationPoint{141.77159875, -37.2920151}, constellationPoint{126.67779875, -37.1600380}, constellationPoint{126.57231291667, -43.4095192}, constellationPoint{121.03827875, -43.3535042}, constellationPoint{120.8616975, -51.1025848}, constellationPoint{123.38112875, -51.1285286}, constellationPoint{123.32011625, -53.3782196}, constellationPoint{127.60929125, -53.4206772}, constellationPoint{127.56711875, -54.9204712}, constellationPoint{133.38017375, -54.9742203}, constellationPoint{133.32365541667, -56.9739723}}
|
||||||
|
constellationPolygons["VIR"] = []constellationPoint{constellationPoint{174.35052458333, -0.6916979}, constellationPoint{174.36568791667, 10.3082914}, constellationPoint{179.60373458333, 10.3040485}, constellationPoint{179.60453541667, 13.3040485}, constellationPoint{194.0620275, 13.3225126}, constellationPoint{194.05906625, 14.3225088}, constellationPoint{204.02893041667, 14.3604937}, constellationPoint{204.06384875, 7.3605771}, constellationPoint{227.78148625, 7.5253930}, constellationPoint{227.85301208333, -0.4742887}, constellationPoint{221.6030925, -0.5269387}, constellationPoint{221.66710791667, -8.5266848}, constellationPoint{215.40850625, -8.5731344}, constellationPoint{215.51309125, -22.5727749}, constellationPoint{194.16687, -22.6773415}, constellationPoint{194.1330525, -11.6773882}, constellationPoint{179.09676, -11.6957970}, constellationPoint{179.09860625, -6.6957974}, constellationPoint{174.34229875, -6.6916924}}
|
||||||
|
constellationPolygons["VOL"] = []constellationPoint{constellationPoint{98.93724875, -64.1070251}, constellationPoint{98.454422916667, -70.1041336}, constellationPoint{97.770709583333, -75.1000366}, constellationPoint{114.21470375, -75.2899170}, constellationPoint{135.24368708333, -75.4954681}, constellationPoint{136.09472708333, -64.4990387}, constellationPoint{102.70331375, -64.1518784}}
|
||||||
|
constellationPolygons["VUL"] = []constellationPoint{constellationPoint{284.33913291667, 21.2478352}, constellationPoint{284.27716208333, 25.6641407}, constellationPoint{290.16131375, 25.7325745}, constellationPoint{290.13264625, 27.7324085}, constellationPoint{296.27220125, 27.8011742}, constellationPoint{296.25094375, 29.3010578}, constellationPoint{315.07258375, 29.4871387}, constellationPoint{315.08391458333, 28.4871883}, constellationPoint{322.62016375, 28.5480537}, constellationPoint{322.66201958333, 24.0482101}, constellationPoint{320.15170041667, 24.0289364}, constellationPoint{320.18840875, 20.0290813}, constellationPoint{317.17878291667, 20.0046406}, constellationPoint{309.907665, 19.9399967}, constellationPoint{309.89693708333, 20.9399471}, constellationPoint{305.13404875, 20.8936996}, constellationPoint{305.12540875, 21.6436558}, constellationPoint{298.860255, 21.5787334}, constellationPoint{298.88568875, 19.4955387}, constellationPoint{290.12128791667, 19.3982983}, constellationPoint{290.09631125, 21.3148155}}
|
||||||
|
change := []string{"PSC", "TUC", "PHE", "SCL", "CET", "PEG", "AND", "CAS", "CEP"}
|
||||||
|
for _, v := range change {
|
||||||
|
for k, v2 := range constellationPolygons[v] {
|
||||||
|
if v2.RA < 270 {
|
||||||
|
constellationPolygons[v][k].RA = v2.RA + 360
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//选定 RA=277.5 DEC=-40
|
||||||
|
|
||||||
|
func isCross(a, b, c, d constellationPoint) bool {
|
||||||
|
var ac, bc, ad, bd, ca, cb, da, db constellationPoint
|
||||||
|
var r1, r2 float64
|
||||||
|
ac.RA = a.RA - c.RA
|
||||||
|
ac.DEC = a.DEC - c.DEC
|
||||||
|
ad.RA = a.RA - d.RA
|
||||||
|
ad.DEC = a.DEC - d.DEC
|
||||||
|
r1 = ac.RA*ad.DEC - ad.RA*ac.DEC
|
||||||
|
bc.RA = b.RA - c.RA
|
||||||
|
bc.DEC = b.DEC - c.DEC
|
||||||
|
bd.RA = b.RA - d.RA
|
||||||
|
bd.DEC = b.DEC - d.DEC
|
||||||
|
r2 = bc.RA*bd.DEC - bd.RA*bc.DEC
|
||||||
|
//echo r1.' '.r2;
|
||||||
|
if r1*r2 > 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
ca.RA = c.RA - a.RA
|
||||||
|
ca.DEC = c.DEC - a.DEC
|
||||||
|
cb.RA = c.RA - b.RA
|
||||||
|
cb.DEC = c.DEC - b.DEC
|
||||||
|
r1 = ca.RA*cb.DEC - cb.RA*ca.DEC
|
||||||
|
da.RA = d.RA - a.RA
|
||||||
|
da.DEC = d.DEC - a.DEC
|
||||||
|
db.RA = d.RA - b.RA
|
||||||
|
db.DEC = d.DEC - b.DEC
|
||||||
|
r2 = da.RA*db.DEC - db.RA*da.DEC
|
||||||
|
if r1*r2 > 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func resolveConstellationCode(ra, dec, jde float64) string {
|
||||||
|
var nra, ndec float64
|
||||||
|
initConstellationData()
|
||||||
|
nra = ra
|
||||||
|
if ra >= 360 {
|
||||||
|
nra -= 360
|
||||||
|
}
|
||||||
|
nra, ndec = Precess(nra, dec, jde, 2451545.0)
|
||||||
|
if ra >= 360 && nra < 270 {
|
||||||
|
nra += 360
|
||||||
|
}
|
||||||
|
if code := matchConstellationCode(nra, ndec); code != "" {
|
||||||
|
return code
|
||||||
|
}
|
||||||
|
if nra <= 270 {
|
||||||
|
ra = ra + 360
|
||||||
|
return resolveConstellationCode(ra, dec, jde)
|
||||||
|
}
|
||||||
|
if ndec > 50 {
|
||||||
|
return "UMI"
|
||||||
|
} else if ndec < -50 {
|
||||||
|
return "OCT"
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func matchConstellationCode(ra, dec float64) string {
|
||||||
|
target := constellationPoint{RA: ra, DEC: dec}
|
||||||
|
for _, boundary := range constellationBoundaries {
|
||||||
|
if ra < boundary.minRA || ra > boundary.maxRA || dec < boundary.minDec || dec > boundary.maxDec {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
count := 0
|
||||||
|
pointCount := len(boundary.points)
|
||||||
|
for index := 0; index < pointCount-1; index++ {
|
||||||
|
if index == 0 && isCross(constellationRayStart, target, boundary.points[pointCount-1], boundary.points[0]) {
|
||||||
|
count++
|
||||||
|
}
|
||||||
|
if isCross(constellationRayStart, target, boundary.points[index], boundary.points[index+1]) {
|
||||||
|
count++
|
||||||
|
}
|
||||||
|
if FR((ra-constellationRayStart.RA)*(boundary.points[index].DEC-constellationRayStart.DEC)) == FR((boundary.points[index].RA-constellationRayStart.RA)*(dec-constellationRayStart.DEC)) {
|
||||||
|
count++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if count%2 == 1 {
|
||||||
|
return boundary.code
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func ConstellationNameZH(ra, dec, jde float64) string {
|
||||||
|
return ConstellationNameByCodeZH(ConstellationCode(ra, dec, jde))
|
||||||
|
}
|
||||||
@@ -0,0 +1,182 @@
|
|||||||
|
package basic
|
||||||
|
|
||||||
|
import "sync"
|
||||||
|
|
||||||
|
type constellationMeta struct {
|
||||||
|
code string
|
||||||
|
zh string
|
||||||
|
en string
|
||||||
|
}
|
||||||
|
|
||||||
|
type constellationBoundary struct {
|
||||||
|
code string
|
||||||
|
points []constellationPoint
|
||||||
|
minRA float64
|
||||||
|
maxRA float64
|
||||||
|
minDec float64
|
||||||
|
maxDec float64
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
constellationInitOnce sync.Once
|
||||||
|
constellationMetas = [...]constellationMeta{
|
||||||
|
{code: "AND", zh: "仙女座", en: "Andromeda"},
|
||||||
|
{code: "ANT", zh: "唧筒座", en: "Antlia"},
|
||||||
|
{code: "APS", zh: "天燕座", en: "Apus"},
|
||||||
|
{code: "AQR", zh: "宝瓶座", en: "Aquarius"},
|
||||||
|
{code: "AQL", zh: "天鹰座", en: "Aquila"},
|
||||||
|
{code: "ARA", zh: "天坛座", en: "Ara"},
|
||||||
|
{code: "ARI", zh: "白羊座", en: "Aries"},
|
||||||
|
{code: "AUR", zh: "御夫座", en: "Auriga"},
|
||||||
|
{code: "BOO", zh: "牧夫座", en: "Bootes"},
|
||||||
|
{code: "CAE", zh: "雕具座", en: "Caelum"},
|
||||||
|
{code: "CAM", zh: "鹿豹座", en: "Camelopardalis"},
|
||||||
|
{code: "CNC", zh: "巨蟹座", en: "Cancer"},
|
||||||
|
{code: "CVN", zh: "猎犬座", en: "Canes Venatici"},
|
||||||
|
{code: "CMA", zh: "大犬座", en: "Canis Major"},
|
||||||
|
{code: "CMI", zh: "小犬座", en: "Canis Minor"},
|
||||||
|
{code: "CAP", zh: "摩羯座", en: "Capricornus"},
|
||||||
|
{code: "CAR", zh: "船底座", en: "Carina"},
|
||||||
|
{code: "CAS", zh: "仙后座", en: "Cassiopeia"},
|
||||||
|
{code: "CEN", zh: "半人马座", en: "Centaurus"},
|
||||||
|
{code: "CEP", zh: "仙王座", en: "Cepheus"},
|
||||||
|
{code: "CET", zh: "鲸鱼座", en: "Cetus"},
|
||||||
|
{code: "CHA", zh: "蝘蜓座", en: "Chamaeleon"},
|
||||||
|
{code: "CIR", zh: "圆规座", en: "Circinus"},
|
||||||
|
{code: "COL", zh: "天鸽座", en: "Columba"},
|
||||||
|
{code: "COM", zh: "后发座", en: "Coma Berenices"},
|
||||||
|
{code: "CRA", zh: "南冕座", en: "Corona Australis"},
|
||||||
|
{code: "CRB", zh: "北冕座", en: "Corona Borealis"},
|
||||||
|
{code: "CRV", zh: "乌鸦座", en: "Corvus"},
|
||||||
|
{code: "CRT", zh: "巨爵座", en: "Crater"},
|
||||||
|
{code: "CRU", zh: "南十字座", en: "Crux"},
|
||||||
|
{code: "CYG", zh: "天鹅座", en: "Cygnus"},
|
||||||
|
{code: "DEL", zh: "海豚座", en: "Delphinus"},
|
||||||
|
{code: "DOR", zh: "剑鱼座", en: "Dorado"},
|
||||||
|
{code: "DRA", zh: "天龙座", en: "Draco"},
|
||||||
|
{code: "EQU", zh: "小马座", en: "Equuleus"},
|
||||||
|
{code: "ERI", zh: "波江座", en: "Eridanus"},
|
||||||
|
{code: "FOR", zh: "天炉座", en: "Fornax"},
|
||||||
|
{code: "GEM", zh: "双子座", en: "Gemini"},
|
||||||
|
{code: "GRU", zh: "天鹤座", en: "Grus"},
|
||||||
|
{code: "HER", zh: "武仙座", en: "Hercules"},
|
||||||
|
{code: "HOR", zh: "时钟座", en: "Horologium"},
|
||||||
|
{code: "HYA", zh: "长蛇座", en: "Hydra"},
|
||||||
|
{code: "HYI", zh: "水蛇座", en: "Hydrus"},
|
||||||
|
{code: "IND", zh: "印第安座", en: "Indus"},
|
||||||
|
{code: "LAC", zh: "蝎虎座", en: "Lacerta"},
|
||||||
|
{code: "LEO", zh: "狮子座", en: "Leo"},
|
||||||
|
{code: "LMI", zh: "小狮座", en: "Leo Minor"},
|
||||||
|
{code: "LEP", zh: "天兔座", en: "Lepus"},
|
||||||
|
{code: "LIB", zh: "天秤座", en: "Libra"},
|
||||||
|
{code: "LUP", zh: "豺狼座", en: "Lupus"},
|
||||||
|
{code: "LYN", zh: "天猫座", en: "Lynx"},
|
||||||
|
{code: "LYR", zh: "天琴座", en: "Lyra"},
|
||||||
|
{code: "MEN", zh: "山案座", en: "Mensa"},
|
||||||
|
{code: "MIC", zh: "显微镜座", en: "Microscopium"},
|
||||||
|
{code: "MON", zh: "麒麟座", en: "Monoceros"},
|
||||||
|
{code: "MUS", zh: "苍蝇座", en: "Musca"},
|
||||||
|
{code: "NOR", zh: "矩尺座", en: "Norma"},
|
||||||
|
{code: "OCT", zh: "南极座", en: "Octans"},
|
||||||
|
{code: "OPH", zh: "蛇夫座", en: "Ophiuchus"},
|
||||||
|
{code: "ORI", zh: "猎户座", en: "Orion"},
|
||||||
|
{code: "PAV", zh: "孔雀座", en: "Pavo"},
|
||||||
|
{code: "PEG", zh: "飞马座", en: "Pegasus"},
|
||||||
|
{code: "PER", zh: "英仙座", en: "Perseus"},
|
||||||
|
{code: "PHE", zh: "凤凰座", en: "Phoenix"},
|
||||||
|
{code: "PIC", zh: "绘架座", en: "Pictor"},
|
||||||
|
{code: "PSC", zh: "双鱼座", en: "Pisces"},
|
||||||
|
{code: "PSA", zh: "南鱼座", en: "Piscis Austrinus"},
|
||||||
|
{code: "PUP", zh: "船尾座", en: "Puppis"},
|
||||||
|
{code: "PYX", zh: "罗盘座", en: "Pyxis"},
|
||||||
|
{code: "RET", zh: "网罟座", en: "Reticulum"},
|
||||||
|
{code: "SGE", zh: "天箭座", en: "Sagitta"},
|
||||||
|
{code: "SGR", zh: "人马座", en: "Sagittarius"},
|
||||||
|
{code: "SCO", zh: "天蝎座", en: "Scorpius"},
|
||||||
|
{code: "SCL", zh: "玉夫座", en: "Sculptor"},
|
||||||
|
{code: "SCT", zh: "盾牌座", en: "Scutum"},
|
||||||
|
{code: "SER1", zh: "巨蛇座", en: "Serpens Caput"},
|
||||||
|
{code: "SER2", zh: "巨蛇座", en: "Serpens Cauda"},
|
||||||
|
{code: "SEX", zh: "六分仪座", en: "Sextans"},
|
||||||
|
{code: "TAU", zh: "金牛座", en: "Taurus"},
|
||||||
|
{code: "TEL", zh: "望远镜座", en: "Telescopium"},
|
||||||
|
{code: "TRI", zh: "三角座", en: "Triangulum"},
|
||||||
|
{code: "TRA", zh: "南三角座", en: "Triangulum Australe"},
|
||||||
|
{code: "TUC", zh: "杜鹃座", en: "Tucana"},
|
||||||
|
{code: "UMA", zh: "大熊座", en: "Ursa Major"},
|
||||||
|
{code: "UMI", zh: "小熊座", en: "Ursa Minor"},
|
||||||
|
{code: "VEL", zh: "船帆座", en: "Vela"},
|
||||||
|
{code: "VIR", zh: "室女座", en: "Virgo"},
|
||||||
|
{code: "VOL", zh: "飞鱼座", en: "Volans"},
|
||||||
|
{code: "VUL", zh: "狐狸座", en: "Vulpecula"},
|
||||||
|
}
|
||||||
|
constellationNameZH map[string]string
|
||||||
|
constellationNameEN map[string]string
|
||||||
|
constellationBoundaries []constellationBoundary
|
||||||
|
constellationRayStart = constellationPoint{RA: 277.5, DEC: -100}
|
||||||
|
)
|
||||||
|
|
||||||
|
func initConstellationData() {
|
||||||
|
constellationInitOnce.Do(func() {
|
||||||
|
initConstellationPolygons()
|
||||||
|
constellationNameZH = make(map[string]string, len(constellationMetas))
|
||||||
|
constellationNameEN = make(map[string]string, len(constellationMetas))
|
||||||
|
constellationBoundaries = make([]constellationBoundary, 0, len(constellationMetas)-2)
|
||||||
|
for _, meta := range constellationMetas {
|
||||||
|
constellationNameZH[meta.code] = meta.zh
|
||||||
|
constellationNameEN[meta.code] = meta.en
|
||||||
|
if meta.code == "UMI" || meta.code == "OCT" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
points := constellationPolygons[meta.code]
|
||||||
|
if len(points) == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
boundary := constellationBoundary{
|
||||||
|
code: meta.code,
|
||||||
|
points: points,
|
||||||
|
minRA: points[0].RA,
|
||||||
|
maxRA: points[0].RA,
|
||||||
|
minDec: points[0].DEC,
|
||||||
|
maxDec: points[0].DEC,
|
||||||
|
}
|
||||||
|
for _, point := range points[1:] {
|
||||||
|
if point.RA < boundary.minRA {
|
||||||
|
boundary.minRA = point.RA
|
||||||
|
}
|
||||||
|
if point.RA > boundary.maxRA {
|
||||||
|
boundary.maxRA = point.RA
|
||||||
|
}
|
||||||
|
if point.DEC < boundary.minDec {
|
||||||
|
boundary.minDec = point.DEC
|
||||||
|
}
|
||||||
|
if point.DEC > boundary.maxDec {
|
||||||
|
boundary.maxDec = point.DEC
|
||||||
|
}
|
||||||
|
}
|
||||||
|
constellationBoundaries = append(constellationBoundaries, boundary)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// ConstellationCode returns the IAU constellation code / 返回 IAU 星座代码。
|
||||||
|
func ConstellationCode(ra, dec, jde float64) string {
|
||||||
|
return resolveConstellationCode(ra, dec, jde)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ConstellationNameEN returns the English constellation name / 返回英文星座名。
|
||||||
|
func ConstellationNameEN(ra, dec, jde float64) string {
|
||||||
|
return ConstellationNameByCodeEN(ConstellationCode(ra, dec, jde))
|
||||||
|
}
|
||||||
|
|
||||||
|
// ConstellationNameByCodeZH returns the Chinese name for a code / 返回星座代码对应的中文名。
|
||||||
|
func ConstellationNameByCodeZH(code string) string {
|
||||||
|
initConstellationData()
|
||||||
|
return constellationNameZH[code]
|
||||||
|
}
|
||||||
|
|
||||||
|
// ConstellationNameByCodeEN returns the English name for a code / 返回星座代码对应的英文名。
|
||||||
|
func ConstellationNameByCodeEN(code string) string {
|
||||||
|
initConstellationData()
|
||||||
|
return constellationNameEN[code]
|
||||||
|
}
|
||||||
@@ -0,0 +1,72 @@
|
|||||||
|
package basic
|
||||||
|
|
||||||
|
import (
|
||||||
|
_ "embed"
|
||||||
|
"encoding/json"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type constellationBaselineSample struct {
|
||||||
|
RA float64 `json:"ra"`
|
||||||
|
Dec float64 `json:"dec"`
|
||||||
|
JDE float64 `json:"jde"`
|
||||||
|
Code string `json:"code"`
|
||||||
|
ZH string `json:"zh"`
|
||||||
|
}
|
||||||
|
|
||||||
|
//go:embed testdata/cst_baseline.json
|
||||||
|
var constellationBaselineJSON []byte
|
||||||
|
|
||||||
|
func loadCstBaseline(t *testing.T) []constellationBaselineSample {
|
||||||
|
t.Helper()
|
||||||
|
var samples []constellationBaselineSample
|
||||||
|
if err := json.Unmarshal(constellationBaselineJSON, &samples); err != nil {
|
||||||
|
t.Fatalf("unmarshal constellation baseline: %v", err)
|
||||||
|
}
|
||||||
|
return samples
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestConstellationBaseline(t *testing.T) {
|
||||||
|
samples := loadCstBaseline(t)
|
||||||
|
for index, sample := range samples {
|
||||||
|
if code := resolveConstellationCode(sample.RA, sample.Dec, sample.JDE); code != sample.Code {
|
||||||
|
t.Fatalf("sample %d code mismatch: ra=%.6f dec=%.6f jde=%.6f got=%q want=%q", index, sample.RA, sample.Dec, sample.JDE, code, sample.Code)
|
||||||
|
}
|
||||||
|
if code := ConstellationCode(sample.RA, sample.Dec, sample.JDE); code != sample.Code {
|
||||||
|
t.Fatalf("sample %d wrapper mismatch: ra=%.6f dec=%.6f jde=%.6f got=%q want=%q", index, sample.RA, sample.Dec, sample.JDE, code, sample.Code)
|
||||||
|
}
|
||||||
|
if zh := ConstellationNameZH(sample.RA, sample.Dec, sample.JDE); zh != sample.ZH {
|
||||||
|
t.Fatalf("sample %d zh mismatch: ra=%.6f dec=%.6f jde=%.6f got=%q want=%q", index, sample.RA, sample.Dec, sample.JDE, zh, sample.ZH)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestConstellationNameLookups(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
code string
|
||||||
|
zh string
|
||||||
|
en string
|
||||||
|
}{
|
||||||
|
{code: "ORI", zh: "猎户座", en: "Orion"},
|
||||||
|
{code: "PSC", zh: "双鱼座", en: "Pisces"},
|
||||||
|
{code: "SER1", zh: "巨蛇座", en: "Serpens Caput"},
|
||||||
|
{code: "SER2", zh: "巨蛇座", en: "Serpens Cauda"},
|
||||||
|
{code: "UMI", zh: "小熊座", en: "Ursa Minor"},
|
||||||
|
}
|
||||||
|
for _, testCase := range tests {
|
||||||
|
if zh := ConstellationNameByCodeZH(testCase.code); zh != testCase.zh {
|
||||||
|
t.Fatalf("ConstellationNameByCodeZH(%q) = %q, want %q", testCase.code, zh, testCase.zh)
|
||||||
|
}
|
||||||
|
if en := ConstellationNameByCodeEN(testCase.code); en != testCase.en {
|
||||||
|
t.Fatalf("ConstellationNameByCodeEN(%q) = %q, want %q", testCase.code, en, testCase.en)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestConstellationNameEN(t *testing.T) {
|
||||||
|
jde := Date2JDE(time.Date(2000, 1, 1, 0, 0, 0, 0, time.UTC))
|
||||||
|
if en := ConstellationNameEN(88.792939, 7.407064, jde); en != "Orion" {
|
||||||
|
t.Fatalf("ConstellationNameEN() = %q, want %q", en, "Orion")
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,21 +4,21 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
func Test_Isxz(t *testing.T) {
|
func TestConstellationNameZH(t *testing.T) {
|
||||||
now := GetNowJDE()
|
now := GetNowJDE()
|
||||||
//finish on 30s
|
//finish on 30s
|
||||||
for i := 0.00; i <= 360.00; i += 0.5 {
|
for i := 0.00; i <= 360.00; i += 0.5 {
|
||||||
for j := -90.00; j <= 90.00; j += 0.5 {
|
for j := -90.00; j <= 90.00; j += 0.5 {
|
||||||
WhichCst(float64(i), float64(j), now)
|
ConstellationNameZH(float64(i), float64(j), now)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func Benchmark_IsXZ(b *testing.B) {
|
func BenchmarkConstellationNameZH(b *testing.B) {
|
||||||
jde := GetNowJDE()
|
jde := GetNowJDE()
|
||||||
for i := 0; i < b.N; i++ {
|
for i := 0; i < b.N; i++ {
|
||||||
//GetNowJDE()
|
//GetNowJDE()
|
||||||
WhichCst(11.11, 12.12, jde)
|
ConstellationNameZH(11.11, 12.12, jde)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
+25
-46
@@ -10,7 +10,7 @@ import (
|
|||||||
* 坐标变换,黄道转赤道
|
* 坐标变换,黄道转赤道
|
||||||
*/
|
*/
|
||||||
func LoToRa(jde, lo, bo float64) float64 {
|
func LoToRa(jde, lo, bo float64) float64 {
|
||||||
ra := math.Atan2(Sin(lo)*Cos(Sita(jde))-Tan(bo)*Sin(Sita(jde)), Cos(lo))
|
ra := math.Atan2(Sin(lo)*Cos(TrueObliquity(jde))-Tan(bo)*Sin(TrueObliquity(jde)), Cos(lo))
|
||||||
ra = ra * 180 / math.Pi
|
ra = ra * 180 / math.Pi
|
||||||
if ra < 0 {
|
if ra < 0 {
|
||||||
ra += 360
|
ra += 360
|
||||||
@@ -19,13 +19,13 @@ func LoToRa(jde, lo, bo float64) float64 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func BoToDec(jde, lo, bo float64) float64 {
|
func BoToDec(jde, lo, bo float64) float64 {
|
||||||
dec := ArcSin(Sin(bo)*Cos(Sita(jde)) + Cos(bo)*Sin(Sita(jde))*Sin(lo))
|
dec := ArcSin(Sin(bo)*Cos(TrueObliquity(jde)) + Cos(bo)*Sin(TrueObliquity(jde))*Sin(lo))
|
||||||
return dec
|
return dec
|
||||||
}
|
}
|
||||||
|
|
||||||
func LoBoToRaDec(jde, lo, bo float64) (float64, float64) {
|
func LoBoToRaDec(jde, lo, bo float64) (float64, float64) {
|
||||||
dec := ArcSin(Sin(bo)*Cos(Sita(jde)) + Cos(bo)*Sin(Sita(jde))*Sin(lo))
|
dec := ArcSin(Sin(bo)*Cos(TrueObliquity(jde)) + Cos(bo)*Sin(TrueObliquity(jde))*Sin(lo))
|
||||||
ra := math.Atan2(Sin(lo)*Cos(Sita(jde))-Tan(bo)*Sin(Sita(jde)), Cos(lo))
|
ra := math.Atan2(Sin(lo)*Cos(TrueObliquity(jde))-Tan(bo)*Sin(TrueObliquity(jde)), Cos(lo))
|
||||||
ra = ra * 180 / math.Pi
|
ra = ra * 180 / math.Pi
|
||||||
if ra < 0 {
|
if ra < 0 {
|
||||||
ra += 360
|
ra += 360
|
||||||
@@ -36,9 +36,9 @@ func LoBoToRaDec(jde, lo, bo float64) (float64, float64) {
|
|||||||
func RaDecToLoBo(jde, ra, dec float64) (float64, float64) {
|
func RaDecToLoBo(jde, ra, dec float64) (float64, float64) {
|
||||||
//tan(λ) = (sin(α)*cos(ε) + tan(δ)*sin(ε)) / cos(α)
|
//tan(λ) = (sin(α)*cos(ε) + tan(δ)*sin(ε)) / cos(α)
|
||||||
//sin(β)=sin(δ)*cos(ε)-cos(δ)*sin(ε)*sin(α)
|
//sin(β)=sin(δ)*cos(ε)-cos(δ)*sin(ε)*sin(α)
|
||||||
sita := Sita(jde)
|
eps := TrueObliquity(jde)
|
||||||
sinBo := Sin(dec)*Cos(sita) - Cos(dec)*Sin(sita)*Sin(ra)
|
sinBo := Sin(dec)*Cos(eps) - Cos(dec)*Sin(eps)*Sin(ra)
|
||||||
lo := math.Atan2((Sin(ra)*Cos(sita) + Tan(dec)*Sin(sita)), Cos(ra))
|
lo := math.Atan2((Sin(ra)*Cos(eps) + Tan(dec)*Sin(eps)), Cos(ra))
|
||||||
lo = Limit360(lo * 180 / math.Pi)
|
lo = Limit360(lo * 180 / math.Pi)
|
||||||
return lo, ArcSin(sinBo)
|
return lo, ArcSin(sinBo)
|
||||||
}
|
}
|
||||||
@@ -46,8 +46,8 @@ func RaDecToLoBo(jde, ra, dec float64) (float64, float64) {
|
|||||||
func RaToLo(jde, ra, dec float64) float64 {
|
func RaToLo(jde, ra, dec float64) float64 {
|
||||||
//tan(λ) = (sin(α)*cos(ε) + tan(δ)*sin(ε)) / cos(α)
|
//tan(λ) = (sin(α)*cos(ε) + tan(δ)*sin(ε)) / cos(α)
|
||||||
//sin(β)=sin(δ)*cos(ε)-cos(δ)*sin(ε)*sin(α)
|
//sin(β)=sin(δ)*cos(ε)-cos(δ)*sin(ε)*sin(α)
|
||||||
sita := Sita(jde)
|
eps := TrueObliquity(jde)
|
||||||
lo := math.Atan2((Sin(ra)*Cos(sita) + Tan(dec)*Sin(sita)), Cos(ra))
|
lo := math.Atan2((Sin(ra)*Cos(eps) + Tan(dec)*Sin(eps)), Cos(ra))
|
||||||
lo = Limit360(lo * 180 / math.Pi)
|
lo = Limit360(lo * 180 / math.Pi)
|
||||||
return lo
|
return lo
|
||||||
}
|
}
|
||||||
@@ -55,32 +55,11 @@ func RaToLo(jde, ra, dec float64) float64 {
|
|||||||
func DecToBo(jde, ra, dec float64) float64 {
|
func DecToBo(jde, ra, dec float64) float64 {
|
||||||
//tan(λ) = (sin(α)*cos(ε) + tan(δ)*sin(ε)) / cos(α)
|
//tan(λ) = (sin(α)*cos(ε) + tan(δ)*sin(ε)) / cos(α)
|
||||||
//sin(β)=sin(δ)*cos(ε)-cos(δ)*sin(ε)*sin(α)
|
//sin(β)=sin(δ)*cos(ε)-cos(δ)*sin(ε)*sin(α)
|
||||||
sita := Sita(jde)
|
eps := TrueObliquity(jde)
|
||||||
sinBo := Sin(dec)*Cos(sita) - Cos(dec)*Sin(sita)*Sin(ra)
|
sinBo := Sin(dec)*Cos(eps) - Cos(dec)*Sin(eps)*Sin(ra)
|
||||||
return ArcSin(sinBo)
|
return ArcSin(sinBo)
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
* 赤道坐标岁差变换st end 为JDE时刻
|
|
||||||
*/
|
|
||||||
func ZuoBiaoSuiCha(ra, dec, st, end float64) (float64, float64) {
|
|
||||||
t := (end - st) / 36525.0
|
|
||||||
l := (2306.2181*t + 0.30188*t*t + 0.017998*t*t*t) / 3600
|
|
||||||
z := (2306.2181*t + 1.09468*t*t + 0.018203*t*t*t) / 3600
|
|
||||||
o := (2004.3109*t - 0.42665*t*t + 0.041833*t*t*t) / 3600
|
|
||||||
A := Cos(dec) * Sin(ra+l)
|
|
||||||
B := Cos(o)*Cos(dec)*Cos(ra+l) - Sin(o)*Sin(dec)
|
|
||||||
C := Sin(o)*Cos(dec)*Cos(ra+l) + Cos(o)*Sin(dec)
|
|
||||||
ras := math.Atan2(A, B)
|
|
||||||
ras = ras * 180 / math.Pi
|
|
||||||
if ras < 0 {
|
|
||||||
ras += 360
|
|
||||||
}
|
|
||||||
ra = ras + z
|
|
||||||
dec = ArcSin(C)
|
|
||||||
return ra, dec
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* 地心坐标转站心坐标,参数分别为,地心赤经赤纬 纬度经度,jde,离地心位置au
|
* 地心坐标转站心坐标,参数分别为,地心赤经赤纬 纬度经度,jde,离地心位置au
|
||||||
*/
|
*/
|
||||||
@@ -101,7 +80,7 @@ func psini(lat, h float64) float64 {
|
|||||||
return psin
|
return psin
|
||||||
}
|
}
|
||||||
|
|
||||||
func ZhanXinRaDec(ra, dec, lat, lon, jd, au, h float64) (float64, float64) {
|
func TopocentricRaDec(ra, dec, lat, lon, jd, au, h float64) (float64, float64) {
|
||||||
sinpi := Sin(0.0024427777777) / au
|
sinpi := Sin(0.0024427777777) / au
|
||||||
pcosi := pcosi(lat, h)
|
pcosi := pcosi(lat, h)
|
||||||
psini := psini(lat, h)
|
psini := psini(lat, h)
|
||||||
@@ -112,14 +91,14 @@ func ZhanXinRaDec(ra, dec, lat, lon, jd, au, h float64) (float64, float64) {
|
|||||||
return ra + nra, ndec
|
return ra + nra, ndec
|
||||||
}
|
}
|
||||||
|
|
||||||
func ZhanXinRa(ra, dec, lat, lon, jd, au, h float64) float64 { //jd为格林尼治标准时
|
func TopocentricRa(ra, dec, lat, lon, jd, au, h float64) float64 { //jd为格林尼治标准时
|
||||||
sinpi := Sin(0.0024427777777) / au
|
sinpi := Sin(0.0024427777777) / au
|
||||||
pcosi := pcosi(lat, h)
|
pcosi := pcosi(lat, h)
|
||||||
tH := Limit360(TD2UT(ApparentSiderealTime(jd), false)*15 + lon - ra)
|
tH := Limit360(TD2UT(ApparentSiderealTime(jd), false)*15 + lon - ra)
|
||||||
nra := math.Atan2(-pcosi*sinpi*Sin(tH), (Cos(dec)-pcosi*sinpi*Cos(tH))) * 180 / math.Pi
|
nra := math.Atan2(-pcosi*sinpi*Sin(tH), (Cos(dec)-pcosi*sinpi*Cos(tH))) * 180 / math.Pi
|
||||||
return ra + nra
|
return ra + nra
|
||||||
}
|
}
|
||||||
func ZhanXinDec(ra, dec, lat, lon, jd, au, h float64) float64 { //jd为格林尼治标准时
|
func TopocentricDec(ra, dec, lat, lon, jd, au, h float64) float64 { //jd为格林尼治标准时
|
||||||
|
|
||||||
sinpi := Sin(0.0024427777777) / au
|
sinpi := Sin(0.0024427777777) / au
|
||||||
pcosi := pcosi(lat, h)
|
pcosi := pcosi(lat, h)
|
||||||
@@ -131,26 +110,26 @@ func ZhanXinDec(ra, dec, lat, lon, jd, au, h float64) float64 { //jd为格林尼
|
|||||||
return ndec
|
return ndec
|
||||||
}
|
}
|
||||||
|
|
||||||
func ZhanXinLo(lo, bo, lat, lon, jd, au, h float64) float64 { //jd为格林尼治标准时
|
func TopocentricLo(lo, bo, lat, lon, jd, au, h float64) float64 { //jd为格林尼治标准时
|
||||||
C := pcosi(lat, h)
|
c := pcosi(lat, h)
|
||||||
S := psini(lat, h)
|
s := psini(lat, h)
|
||||||
sinpi := Sin(0.0024427777777) / au
|
sinpi := Sin(0.0024427777777) / au
|
||||||
ra := LoToRa(jd, lo, bo)
|
ra := LoToRa(jd, lo, bo)
|
||||||
tH := Limit360(TD2UT(ApparentSiderealTime(jd), false)*15 + lon - ra)
|
tH := Limit360(TD2UT(ApparentSiderealTime(jd), false)*15 + lon - ra)
|
||||||
N := Cos(lo)*Cos(bo) - C*sinpi*Cos(tH)
|
n := Cos(lo)*Cos(bo) - c*sinpi*Cos(tH)
|
||||||
nlo := math.Atan2(Sin(lo)*Cos(bo)-sinpi*(S*Sin(Sita(jd))+C*Cos(Sita(jd))*Sin(tH)), N) * 180 / math.Pi
|
nlo := math.Atan2(Sin(lo)*Cos(bo)-sinpi*(s*Sin(TrueObliquity(jd))+c*Cos(TrueObliquity(jd))*Sin(tH)), n) * 180 / math.Pi
|
||||||
return nlo
|
return nlo
|
||||||
}
|
}
|
||||||
|
|
||||||
func ZhanXinBo(lo, bo, lat, lon, jd, au, h float64) float64 { //jd为格林尼治标准时
|
func TopocentricBo(lo, bo, lat, lon, jd, au, h float64) float64 { //jd为格林尼治标准时
|
||||||
C := pcosi(lat, h)
|
c := pcosi(lat, h)
|
||||||
S := psini(lat, h)
|
s := psini(lat, h)
|
||||||
sinpi := Sin(0.0024427777777) / au
|
sinpi := Sin(0.0024427777777) / au
|
||||||
ra := LoToRa(jd, lo, bo)
|
ra := LoToRa(jd, lo, bo)
|
||||||
tH := Limit360(TD2UT(ApparentSiderealTime(jd), false)*15 + lon - ra)
|
tH := Limit360(TD2UT(ApparentSiderealTime(jd), false)*15 + lon - ra)
|
||||||
N := Cos(lo)*Cos(bo) - C*sinpi*Cos(tH)
|
n := Cos(lo)*Cos(bo) - c*sinpi*Cos(tH)
|
||||||
nlo := math.Atan2(Sin(lo)*Cos(bo)-sinpi*(S*Sin(Sita(jd))+C*Cos(Sita(jd))*Sin(tH)), N) * 180 / math.Pi
|
nlo := math.Atan2(Sin(lo)*Cos(bo)-sinpi*(s*Sin(TrueObliquity(jd))+c*Cos(TrueObliquity(jd))*Sin(tH)), n) * 180 / math.Pi
|
||||||
nbo := math.Atan2(Cos(nlo)*(Sin(bo)-sinpi*(S*Cos(Sita(jd))-C*Sin(Sita(jd))*Sin(tH))), N) * 180 / math.Pi
|
nbo := math.Atan2(Cos(nlo)*(Sin(bo)-sinpi*(s*Cos(TrueObliquity(jd))-c*Sin(TrueObliquity(jd))*Sin(tH))), n) * 180 / math.Pi
|
||||||
return nbo
|
return nbo
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,50 +0,0 @@
|
|||||||
package basic
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
func Test_LoBo(t *testing.T) {
|
|
||||||
fmt.Printf("%.9f", dt_cal(2020.5))
|
|
||||||
}
|
|
||||||
|
|
||||||
func Test_LoBoRaDec(t *testing.T) {
|
|
||||||
jde := 2451545.0
|
|
||||||
lo, bo := RaDecToLoBo(jde, 10, 50)
|
|
||||||
fmt.Println("LO,BO", lo, bo)
|
|
||||||
fmt.Println(LoBoToRaDec(jde, lo, bo))
|
|
||||||
lo, bo = RaDecToLoBo(jde, 40, 80)
|
|
||||||
fmt.Println("LO,BO", lo, bo)
|
|
||||||
fmt.Println(LoBoToRaDec(jde, lo, bo))
|
|
||||||
lo, bo = RaDecToLoBo(jde, 90, 50)
|
|
||||||
fmt.Println("LO,BO", lo, bo)
|
|
||||||
fmt.Println(LoBoToRaDec(jde, lo, bo))
|
|
||||||
lo, bo = RaDecToLoBo(jde, 130, 50)
|
|
||||||
fmt.Println("LO,BO", lo, bo)
|
|
||||||
fmt.Println(LoBoToRaDec(jde, lo, bo))
|
|
||||||
lo, bo = RaDecToLoBo(jde, 160, 50)
|
|
||||||
fmt.Println("LO,BO", lo, bo)
|
|
||||||
fmt.Println(LoBoToRaDec(jde, lo, bo))
|
|
||||||
lo, bo = RaDecToLoBo(jde, 180, 50)
|
|
||||||
fmt.Println("LO,BO", lo, bo)
|
|
||||||
fmt.Println(LoBoToRaDec(jde, lo, bo))
|
|
||||||
lo, bo = RaDecToLoBo(jde, 210, 50)
|
|
||||||
fmt.Println("LO,BO", lo, bo)
|
|
||||||
fmt.Println(LoBoToRaDec(jde, lo, bo))
|
|
||||||
lo, bo = RaDecToLoBo(jde, 260, 50)
|
|
||||||
fmt.Println("LO,BO", lo, bo)
|
|
||||||
fmt.Println(LoBoToRaDec(jde, lo, bo))
|
|
||||||
lo, bo = RaDecToLoBo(jde, 270, 50)
|
|
||||||
fmt.Println("LO,BO", lo, bo)
|
|
||||||
fmt.Println(LoBoToRaDec(jde, lo, bo))
|
|
||||||
lo, bo = RaDecToLoBo(jde, 300, 50)
|
|
||||||
fmt.Println("LO,BO", lo, bo)
|
|
||||||
fmt.Println(LoBoToRaDec(jde, lo, bo))
|
|
||||||
lo, bo = RaDecToLoBo(jde, 350, 50)
|
|
||||||
fmt.Println("LO,BO", lo, bo)
|
|
||||||
fmt.Println(LoBoToRaDec(jde, lo, bo))
|
|
||||||
lo, bo = RaDecToLoBo(jde, 0, 50)
|
|
||||||
fmt.Println("LO,BO", lo, bo)
|
|
||||||
fmt.Println(LoBoToRaDec(jde, lo, bo))
|
|
||||||
}
|
|
||||||
-290
@@ -1,290 +0,0 @@
|
|||||||
package basic
|
|
||||||
|
|
||||||
import (
|
|
||||||
. "b612.me/astro/tools"
|
|
||||||
)
|
|
||||||
|
|
||||||
type cst struct {
|
|
||||||
RA float64 //赤经
|
|
||||||
DEC float64 //赤纬
|
|
||||||
}
|
|
||||||
|
|
||||||
var cstLists map[string][]cst
|
|
||||||
|
|
||||||
func initCstData() {
|
|
||||||
cstLists = make(map[string][]cst, 89)
|
|
||||||
cstLists["AND"] = []cst{cst{344.46530375, 35.1682358}, cst{344.34285125, 53.1680298}, cst{351.45289375, 53.1870041}, cst{351.4656825, 50.6870193}, cst{355.27055708333, 50.6929131}, cst{355.27607875, 48.6929169}, cst{4.1463675, 48.6949348}, cst{4.14327875, 46.6949348}, cst{14.776077083333, 46.6757545}, cst{14.7888675, 48.6757393}, cst{18.588407916667, 48.6632690}, cst{18.60590375, 50.6632347}, cst{22.40793625, 50.6478767}, cst{26.96852375, 50.6257439}, cst{26.931439583333, 47.6258430}, cst{32.62149125, 47.5927505}, cst{32.67380125, 51.0925827}, cst{39.88547875, 51.0423737}, cst{39.67934125, 37.2931557}, cst{31.87109125, 37.3470840}, cst{31.854250416667, 35.5971375}, cst{22.910835, 35.6453362}, cst{22.89742625, 33.6453705}, cst{12.44306375, 33.6818962}, cst{12.41349125, 24.4319324}, cst{14.424064583333, 24.4266243}, cst{14.414815416667, 21.6766376}, cst{3.73992125, 21.6951923}, cst{3.7406445833333, 22.6951923}, cst{2.61001625, 22.6957588}, cst{2.6128425, 28.6957588}, cst{1.60621625, 28.6960354}, cst{1.6069770833333, 32.0293655}, cst{357.82874125, 32.0285034}, cst{357.8280825, 32.7785072}, cst{354.04915791667, 32.7746468}, cst{354.04417958333, 35.1913109}}
|
|
||||||
cstLists["ANT"] = []cst{cst{141.904335, -24.5425186}, cst{141.77159875, -37.2920151}, cst{141.73406125, -40.2918739}, cst{166.45650458333, -40.4246216}, cst{166.47936291667, -35.6746559}, cst{163.95851541667, -35.6664963}, cst{163.977885, -31.8332005}, cst{160.20137875, -31.8185863}, cst{160.21289375, -29.8186131}, cst{155.18132708333, -29.7947845}, cst{155.1993375, -27.1281624}, cst{147.65928291667, -27.0835037}, cst{147.67968125, -24.5835705}}
|
|
||||||
cstLists["APS"] = []cst{cst{209.11110875, -83.1200714}, cst{276.86599791667, -82.4582748}, cst{274.19506041667, -74.9745178}, cst{273.28007708333, -67.4800797}, cst{265.77572875, -67.5711060}, cst{258.2424825, -67.6610870}, cst{258.47067875, -70.1597443}, cst{224.16644125, -70.5115433}, cst{207.46087041667, -70.6244431}, cst{207.78143375, -75.6235962}}
|
|
||||||
cstLists["AQR"] = []cst{cst{309.59884625, 0.4361772}, cst{309.5798775, 2.4360874}, cst{314.08109708333, 2.4773185}, cst{321.58347125, 2.5393796}, cst{323.58427041667, 2.5544112}, cst{323.57874875, 3.3043909}, cst{326.58021875, 3.3256676}, cst{326.58708625, 2.3256910}, cst{331.588755, 2.3576119}, cst{331.58726708333, 2.6076074}, cst{342.84221708333, 2.6622071}, cst{342.8497125, 0.6622211}, cst{342.86470375, -3.3377509}, cst{359.10221125, -3.3042023}, cst{359.10329875, -6.3042021}, cst{359.11056458333, -24.8042011}, cst{346.68096625, -24.8250446}, cst{329.77028875, -24.9040413}, cst{329.6561625, -8.4043999}, cst{321.668415, -8.4602947}, cst{321.71645125, -14.4601107}, cst{309.74390125, -14.5631361}, cst{309.68464791667, -8.5634165}}
|
|
||||||
cstLists["AQL"] = []cst{cst{280.35020875, 0.1154895}, cst{280.3262325, 2.1153460}, cst{284.57642541667, 2.1659052}, cst{284.525985, 6.4156075}, cst{281.45856875, 6.3791943}, cst{281.388045, 12.1287737}, cst{284.45626208333, 12.1651964}, cst{284.37363625, 18.6647091}, cst{286.37549375, 18.6882229}, cst{286.4054775, 16.3550682}, cst{298.92116541667, 16.4957294}, cst{298.926, 16.0790844}, cst{303.5589975, 16.1275158}, cst{303.636675, 8.8779116}, cst{306.01395625, 8.9018240}, cst{306.07910125, 2.4021468}, cst{309.5798775, 2.4360874}, cst{309.59884625, 0.4361772}, cst{309.68464791667, -8.5634165}, cst{301.69369625, -8.6430750}, cst{301.72636958333, -11.6762342}, cst{284.74405375, -11.8664360}, cst{284.64729291667, -3.8336766}, cst{280.3981875, -3.8842230}}
|
|
||||||
cstLists["ARA"] = []cst{cst{249.03468125, -60.2644577}, cst{248.57062375, -45.7670517}, cst{269.80928375, -45.5163460}, cst{272.3090175, -45.4859734}, cst{272.67225291667, -56.9837723}, cst{265.16818958333, -57.0747757}, cst{265.77572875, -67.5711060}, cst{258.2424825, -67.6610870}, cst{255.72498291667, -67.6905823}, cst{255.5423925, -65.1916428}, cst{254.28351458333, -65.2062531}, cst{254.1951375, -63.7900925}, cst{251.67632125, -63.8189964}, cst{251.53784708333, -61.2364578}, cst{249.08163, -61.2641945}}
|
|
||||||
cstLists["ARI"] = []cst{cst{31.6652475, 10.5143948}, cst{26.65573375, 10.5432396}, cst{26.744674583333, 25.6263351}, cst{30.51371125, 25.6050701}, cst{30.53061625, 27.8550186}, cst{38.07014875, 27.8047638}, cst{38.10319375, 31.2213154}, cst{42.62838, 31.1865025}, cst{52.426667916667, 31.1003609}, cst{52.2906225, 19.4343338}, cst{51.037234583333, 19.4461136}, cst{50.94641125, 10.3632069}}
|
|
||||||
cstLists["AUR"] = []cst{cst{69.4869375, 30.9218750}, cst{69.57384125, 36.2547150}, cst{72.45734375, 36.2218513}, cst{72.840285, 52.7196465}, cst{77.484762083333, 52.6655540}, cst{77.606764583333, 56.1648331}, cst{94.13108875, 55.9658089}, cst{94.05736625, 53.9662552}, cst{100.04603125, 53.8938293}, cst{99.919459583333, 49.8945885}, cst{104.40635875, 49.8410034}, cst{104.26530291667, 44.3418388}, cst{112.73412458333, 44.2435493}, cst{112.56071875, 35.2445297}, cst{100.09027625, 35.3905640}, cst{99.965657916667, 27.8913116}, cst{90.22107125, 28.0092907}, cst{90.228902916667, 28.5092430}, cst{73.212475416667, 28.7124405}, cst{73.235342916667, 30.2123089}, cst{69.47678875, 30.2552605}}
|
|
||||||
cstLists["BOO"] = []cst{cst{227.78148625, 7.5253930}, cst{204.06384875, 7.3605771}, cst{204.02893041667, 14.3604937}, cst{203.95387208333, 27.8603134}, cst{210.7888275, 27.8976517}, cst{210.77085875, 30.1475964}, cst{211.88893375, 30.1545391}, cst{211.69873208333, 47.9039383}, cst{211.58439125, 54.9035759}, cst{217.25124875, 54.9422379}, cst{229.59105458333, 55.0448647}, cst{229.65737375, 52.5451736}, cst{237.08447375, 52.6174774}, cst{237.12458541667, 51.1176796}, cst{237.36542708333, 39.6189079}, cst{232.64365208333, 39.5721130}, cst{232.74697791667, 32.5726128}, cst{229.01591875, 32.5376778}, cst{229.09951625, 25.5380573}, cst{227.60549125, 25.5246105}}
|
|
||||||
cstLists["CAE"] = []cst{cst{65.0764125, -39.7007294}, cst{64.88238625, -48.6996651}, cst{68.362247916667, -48.7384491}, cst{68.42409625, -46.2387962}, cst{73.402082916667, -46.2959023}, cst{73.482370416667, -42.7963676}, cst{75.97444375, -42.8255501}, cst{76.2549375, -27.0772038}, cst{73.75929625, -27.0479794}, cst{71.76333125, -27.0248775}, cst{71.72241875, -29.7746429}, cst{69.97665125, -29.7546597}, cst{69.862375416667, -36.7540054}, cst{65.12985, -36.7010231}}
|
|
||||||
cstLists["CAM"] = []cst{cst{94.13108875, 55.9658089}, cst{77.606764583333, 56.1648331}, cst{77.484762083333, 52.6655540}, cst{72.840285, 52.7196465}, cst{52.31308625, 52.9366074}, cst{52.381900416667, 55.4362831}, cst{49.8540225, 55.4596519}, cst{49.91349625, 57.4593849}, cst{48.90093, 57.4684982}, cst{49.3954575, 68.4662857}, cst{54.237034583333, 68.4214401}, cst{55.30874875, 77.4163132}, cst{56.726209583333, 77.4025955}, cst{57.53049, 80.3986664}, cst{80.488894583333, 80.1478500}, cst{84.536117916667, 85.1239471}, cst{127.953615, 84.6103745}, cst{130.40275041667, 86.0975418}, cst{213.0229575, 85.9308090}, cst{216.78285625, 79.4449844}, cst{203.80918958333, 79.3629303}, cst{204.15701875, 76.3638153}, cst{195.8206125, 76.3289108}, cst{174.43479625, 76.3084106}, cst{174.53158375, 79.3083420}, cst{162.81859791667, 79.3401794}, cst{163.10541625, 81.3396072}, cst{142.191195, 81.4677658}, cst{140.61547375, 72.9741364}, cst{123.08622875, 73.1383743}, cst{122.12910125, 59.6433983}, cst{107.7531975, 59.8037262}, cst{107.8515525, 61.8031464}, cst{94.40745625, 61.9641266}}
|
|
||||||
cstLists["CNC"] = []cst{cst{140.40425875, 6.4700689}, cst{122.92139125, 6.6302376}, cst{120.54834291667, 6.6549850}, cst{120.5806725, 9.6548138}, cst{118.83248875, 9.6734257}, cst{118.87160541667, 13.1732168}, cst{118.94754458333, 19.6728077}, cst{120.07012375, 19.6608200}, cst{120.17164625, 27.6602821}, cst{121.91596958333, 27.6419144}, cst{121.99323125, 33.1415138}, cst{140.645985, 32.9691162}}
|
|
||||||
cstLists["CVN"] = []cst{cst{181.59450625, 33.3039627}, cst{181.59141625, 44.3039627}, cst{182.82643375, 44.3043365}, cst{182.8185225, 52.3043365}, cst{203.74239875, 52.3598061}, cst{203.79511375, 47.8599281}, cst{211.69873208333, 47.9039383}, cst{211.88893375, 30.1545391}, cst{210.77085875, 30.1475964}, cst{210.7888275, 27.8976517}, cst{203.95387208333, 27.8603134}, cst{200.22657458333, 27.8437748}, cst{200.20774791667, 31.3437366}, cst{186.55769375, 31.3074341}, cst{186.55426041667, 33.3074303}}
|
|
||||||
cstLists["CMA"] = []cst{cst{93.215625, -11.0301533}, cst{111.97339958333, -11.2521448}, cst{111.67719875, -33.2504692}, cst{99.903859583333, -33.1128159}, cst{92.899067916667, -33.0282326}, cst{92.99256625, -27.2787991}}
|
|
||||||
cstLists["CMI"] = []cst{cst{122.84900708333, -0.3693900}, cst{109.59966625, -0.2243290}, cst{109.61691875, 1.2755718}, cst{106.86739625, 1.3074419}, cst{106.91427458333, 5.3071680}, cst{106.66432208333, 5.3100886}, cst{106.71787958333, 9.8097754}, cst{106.7482275, 12.3095980}, cst{114.24100375, 12.2238722}, cst{114.2527275, 13.2238064}, cst{118.87160541667, 13.1732168}, cst{118.83248875, 9.6734257}, cst{120.5806725, 9.6548138}, cst{120.54834291667, 6.6549850}, cst{122.92139125, 6.6302376}}
|
|
||||||
cstLists["CAP"] = []cst{cst{309.68464791667, -8.5634165}, cst{301.69369625, -8.6430750}, cst{301.72636958333, -11.6762342}, cst{301.91596958333, -27.6419144}, cst{306.89795541667, -27.5913391}, cst{321.83163625, -27.4596672}, cst{321.80777541667, -24.9597607}, cst{329.77028875, -24.9040413}, cst{329.6561625, -8.4043999}, cst{321.668415, -8.4602947}, cst{321.71645125, -14.4601107}, cst{309.74390125, -14.5631361}}
|
|
||||||
cstLists["CAR"] = []cst{cst{170.15592125, -57.1843452}, cst{166.33725625, -57.1744423}, cst{133.32365541667, -56.9739723}, cst{133.38017375, -54.9742203}, cst{127.56711875, -54.9204712}, cst{127.60929125, -53.4206772}, cst{123.32011625, -53.3782196}, cst{123.38112875, -51.1285286}, cst{120.8616975, -51.1025848}, cst{90.748902083333, -50.7545471}, cst{90.693705, -52.5042114}, cst{93.19435375, -52.5345764}, cst{93.1074, -55.0340500}, cst{98.114275416667, -55.0945587}, cst{97.995077916667, -58.0938416}, cst{103.01111708333, -58.1537018}, cst{102.70331375, -64.1518784}, cst{136.09472708333, -64.4990387}, cst{135.24368708333, -75.4954681}, cst{169.85697291667, -75.6840134}, cst{170.08481125, -64.6842651}}
|
|
||||||
cstLists["CAS"] = []cst{cst{344.34285125, 53.1680298}, cst{344.30402708333, 56.9179611}, cst{344.26912375, 59.7512321}, cst{348.85966375, 59.7646751}, cst{348.81649041667, 63.6812897}, cst{355.21757125, 63.6928787}, cst{355.19785958333, 66.6928711}, cst{6.76376375, 66.6924438}, cst{6.92291375, 77.6923447}, cst{55.30874875, 77.4163132}, cst{54.237034583333, 68.4214401}, cst{49.3954575, 68.4662857}, cst{48.90093, 57.4684982}, cst{38.762337083333, 57.5513000}, cst{38.802355416667, 59.0511551}, cst{30.795625416667, 59.1046104}, cst{30.77362375, 58.1046753}, cst{27.5952225, 58.1227188}, cst{27.53364125, 54.6228828}, cst{22.45601375, 54.6477699}, cst{22.40793625, 50.6478767}, cst{18.60590375, 50.6632347}, cst{18.588407916667, 48.6632690}, cst{14.7888675, 48.6757393}, cst{14.776077083333, 46.6757545}, cst{4.14327875, 46.6949348}, cst{4.1463675, 48.6949348}, cst{355.27607875, 48.6929169}, cst{355.27055708333, 50.6929131}, cst{351.4656825, 50.6870193}, cst{351.45289375, 53.1870041}}
|
|
||||||
cstLists["CEN"] = []cst{cst{166.47936291667, -35.6746559}, cst{166.45650458333, -40.4246216}, cst{166.33725625, -57.1744423}, cst{170.15592125, -57.1843452}, cst{170.08481125, -64.6842651}, cst{179.05736375, -64.6957855}, cst{179.07076791667, -55.6957932}, cst{194.33451125, -55.6771049}, cst{194.43838041667, -64.6769638}, cst{204.68028625, -64.6379395}, cst{220.51497458333, -64.5390244}, cst{220.23446541667, -55.5400887}, cst{214.65681625, -55.5799522}, cst{214.45026458333, -42.5806465}, cst{225.79627958333, -42.4941750}, cst{225.63076958333, -29.9948788}, cst{190.41739958333, -30.1863899}, cst{190.42719875, -33.6863785}, cst{185.38743458333, -33.6938934}, cst{185.39029625, -35.6938896}}
|
|
||||||
cstLists["CEP"] = []cst{cst{300.5732625, 59.8510780}, cst{300.48520041667, 61.8506203}, cst{306.8118675, 61.9143791}, cst{306.51738125, 67.4129562}, cst{310.33401458333, 67.4490280}, cst{309.57304041667, 75.4455261}, cst{301.87339791667, 75.3708725}, cst{300.6738, 80.3647766}, cst{313.70587375, 80.4867859}, cst{308.72097, 86.4656219}, cst{308.33135541667, 86.6306305}, cst{343.51066625, 86.8368912}, cst{339.26098791667, 88.6638870}, cst{135.83247125, 87.5689163}, cst{130.40275041667, 86.0975418}, cst{127.953615, 84.6103745}, cst{84.536117916667, 85.1239471}, cst{80.488894583333, 80.1478500}, cst{57.53049, 80.3986664}, cst{56.726209583333, 77.4025955}, cst{55.30874875, 77.4163132}, cst{6.92291375, 77.6923447}, cst{6.76376375, 66.6924438}, cst{355.19785958333, 66.6928711}, cst{355.21757125, 63.6928787}, cst{348.81649041667, 63.6812897}, cst{348.85966375, 59.7646751}, cst{344.26912375, 59.7512321}, cst{344.30402708333, 56.9179611}, cst{335.91093, 56.8825760}, cst{335.93130125, 55.6326256}, cst{333.13762625, 55.6178436}, cst{333.17467625, 53.3679428}, cst{330.63921, 53.3532715}, cst{330.60218875, 55.4364891}, cst{309.83136125, 55.2753258}, cst{309.62379458333, 61.3576965}, cst{308.66080375, 61.3486443}, cst{308.71659291667, 59.9322395}}
|
|
||||||
cstLists["CET"] = []cst{cst{6.60132875, 0.6925398}, cst{6.6037875, 2.6925383}, cst{31.61526625, 2.5978806}, cst{31.6652475, 10.5143948}, cst{50.94641125, 10.3632069}, cst{50.85298375, 0.4469725}, cst{50.836682916667, -1.3029516}, cst{41.33922125, -1.2210265}, cst{41.14875875, -23.8536034}, cst{26.46599875, -23.7562580}, cst{26.45888875, -24.8729095}, cst{359.11056458333, -24.8042011}, cst{359.10329875, -6.3042021}, cst{6.5927025, -6.3074551}}
|
|
||||||
cstLists["CHA"] = []cst{cst{111.65211458333, -82.7758865}, cst{209.11110875, -83.1200714}, cst{207.78143375, -75.6235962}, cst{169.85697291667, -75.6840134}, cst{135.24368708333, -75.4954681}, cst{114.21470375, -75.2899170}}
|
|
||||||
cstLists["CIR"] = []cst{cst{204.68028625, -64.6379395}, cst{204.70747958333, -65.6378784}, cst{207.26802291667, -65.6249542}, cst{207.46087041667, -70.6244431}, cst{224.16644125, -70.5115433}, cst{224.00363375, -68.0122070}, cst{226.55712625, -67.9909286}, cst{226.35353541667, -64.0751266}, cst{230.16657875, -64.0415649}, cst{230.05456958333, -61.4587479}, cst{232.58976458333, -61.4353065}, cst{232.5498675, -60.4354935}, cst{232.38191125, -55.4362831}, cst{228.08351125, -55.4754944}, cst{220.23446541667, -55.5400887}, cst{220.51497458333, -64.5390244}}
|
|
||||||
cstLists["COL"] = []cst{cst{75.97444375, -42.8255501}, cst{76.2549375, -27.0772038}, cst{92.99256625, -27.2787991}, cst{92.899067916667, -33.0282326}, cst{99.903859583333, -33.1128159}, cst{99.70891625, -43.1116486}, cst{90.951777083333, -43.0057793}}
|
|
||||||
cstLists["COM"] = []cst{cst{179.60453541667, 13.3040485}, cst{179.60894125, 28.3040466}, cst{181.59566375, 28.3039627}, cst{181.59450625, 33.3039627}, cst{186.55426041667, 33.3074303}, cst{186.55769375, 31.3074341}, cst{200.20774791667, 31.3437366}, cst{200.22657458333, 27.8437748}, cst{203.95387208333, 27.8603134}, cst{204.02893041667, 14.3604937}, cst{194.05906625, 14.3225088}, cst{194.0620275, 13.3225126}}
|
|
||||||
cstLists["CRA"] = []cst{cst{269.62546375, -37.0174599}, cst{289.59631958333, -36.7785645}, cst{289.76964, -45.2775650}, cst{272.3090175, -45.4859734}, cst{269.80928375, -45.5163460}}
|
|
||||||
cstLists["CRB"] = []cst{cst{229.09951625, 25.5380573}, cst{229.01591875, 32.5376778}, cst{232.74697791667, 32.5726128}, cst{232.64365208333, 39.5721130}, cst{237.36542708333, 39.6189079}, cst{246.07194875, 39.7117195}, cst{246.2798025, 26.7128716}, cst{243.78670625, 26.6855240}, cst{243.8001825, 25.6855946}, cst{241.80573458333, 25.6641407}}
|
|
||||||
cstLists["CRV"] = []cst{cst{194.1330525, -11.6773882}, cst{179.09676, -11.6957970}, cst{179.09131041667, -25.1957951}, cst{190.404525, -25.1864014}, cst{190.3985025, -22.6864090}, cst{194.16687, -22.6773415}}
|
|
||||||
cstLists["CRT"] = []cst{cst{162.82713875, -6.6621790}, cst{162.80791375, -11.6621428}, cst{162.77554041667, -19.6620827}, cst{164.03058625, -19.6666222}, cst{164.00808291667, -25.1665821}, cst{179.09131041667, -25.1957951}, cst{179.09676, -11.6957970}, cst{179.09860625, -6.6957974}, cst{174.34229875, -6.6916924}}
|
|
||||||
cstLists["CRU"] = []cst{cst{179.07076791667, -55.6957932}, cst{179.05736375, -64.6957855}, cst{194.43838041667, -64.6769638}, cst{194.33451125, -55.6771049}}
|
|
||||||
cstLists["CYG"] = []cst{cst{290.13264625, 27.7324085}, cst{290.0952525, 30.2321968}, cst{291.5987775, 30.2493153}, cst{291.49260458333, 36.7487144}, cst{292.11965541667, 36.7558022}, cst{291.9835275, 43.7550354}, cst{288.47030708333, 43.7149391}, cst{288.37552041667, 47.7143936}, cst{287.1206475, 47.6998672}, cst{286.87645958333, 55.6984482}, cst{291.90459291667, 55.7560043}, cst{291.80955, 58.2554703}, cst{297.10055375, 58.3138733}, cst{297.03924125, 59.8135414}, cst{300.5732625, 59.8510780}, cst{308.71659291667, 59.9322395}, cst{308.66080375, 61.3486443}, cst{309.62379458333, 61.3576965}, cst{309.83136125, 55.2753258}, cst{330.60218875, 55.4364891}, cst{330.63921, 53.3532715}, cst{330.76266291667, 44.6036453}, cst{329.87860625, 44.5982513}, cst{329.88163958333, 44.3482628}, cst{329.37664041667, 44.3451195}, cst{329.4610125, 36.5953827}, cst{327.319965, 36.5815468}, cst{327.39518125, 28.5817947}, cst{322.62016375, 28.5480537}, cst{315.08391458333, 28.4871883}, cst{315.07258375, 29.4871387}, cst{296.25094375, 29.3010578}, cst{296.27220125, 27.8011742}}
|
|
||||||
cstLists["DEL"] = []cst{cst{309.5798775, 2.4360874}, cst{306.07910125, 2.4021468}, cst{306.01395625, 8.9018240}, cst{303.636675, 8.8779116}, cst{303.5589975, 16.1275158}, cst{305.18694875, 16.1439629}, cst{305.13404875, 20.8936996}, cst{309.89693708333, 20.9399471}, cst{309.907665, 19.9399967}, cst{317.17878291667, 20.0046406}, cst{317.24836375, 12.3382607}, cst{314.61859625, 12.3157644}, cst{314.67109625, 6.4826641}, cst{314.045505, 6.4771614}, cst{314.08109708333, 2.4773185}}
|
|
||||||
cstLists["DOR"] = []cst{cst{58.318787916667, -52.7968445}, cst{60.79789625, -52.8228111}, cst{60.69291875, -56.1555862}, cst{65.6504625, -56.2093849}, cst{65.55459, -58.7088547}, cst{69.274534583333, -58.7506638}, cst{68.79401875, -67.2479248}, cst{68.58152375, -69.7467194}, cst{98.454422916667, -70.1041336}, cst{98.93724875, -64.1070251}, cst{90.173642916667, -64.0010529}, cst{90.34506125, -61.0020981}, cst{82.85761375, -60.9112892}, cst{83.01880375, -57.4122620}, cst{75.547742916667, -57.3230400}, cst{75.6770175, -53.8238029}, cst{68.217745416667, -53.7376366}, cst{68.362247916667, -48.7384491}, cst{64.88238625, -48.6996651}, cst{62.149907916667, -48.6699715}, cst{62.098639583333, -50.6697006}, cst{58.37716625, -50.6304779}}
|
|
||||||
cstLists["DRA"] = []cst{cst{140.61547375, 72.9741364}, cst{142.191195, 81.4677658}, cst{163.10541625, 81.3396072}, cst{162.81859791667, 79.3401794}, cst{174.53158375, 79.3083420}, cst{174.43479625, 76.3084106}, cst{195.8206125, 76.3289108}, cst{196.09747375, 69.3293610}, cst{210.65081125, 69.3991165}, cst{210.82055541667, 65.3996506}, cst{235.32956541667, 65.6023483}, cst{235.05063, 69.6009445}, cst{247.8410625, 69.7383041}, cst{247.2207075, 74.7347870}, cst{261.53663708333, 74.9033127}, cst{260.21790458333, 79.8953476}, cst{267.65602041667, 79.9857483}, cst{261.72223041667, 85.9495697}, cst{308.72097, 86.4656219}, cst{313.70587375, 80.4867859}, cst{300.6738, 80.3647766}, cst{301.87339791667, 75.3708725}, cst{309.57304041667, 75.4455261}, cst{310.33401458333, 67.4490280}, cst{306.51738125, 67.4129562}, cst{306.8118675, 61.9143791}, cst{300.48520041667, 61.8506203}, cst{300.5732625, 59.8510780}, cst{297.03924125, 59.8135414}, cst{297.10055375, 58.3138733}, cst{291.80955, 58.2554703}, cst{291.90459291667, 55.7560043}, cst{286.87645958333, 55.6984482}, cst{287.1206475, 47.6998672}, cst{274.34237541667, 47.5476036}, cst{274.25768875, 50.5470886}, cst{255.7863525, 50.3244438}, cst{255.75682625, 51.3242683}, cst{237.12458541667, 51.1176796}, cst{237.08447375, 52.6174774}, cst{229.65737375, 52.5451736}, cst{229.59105458333, 55.0448647}, cst{217.25124875, 54.9422379}, cst{217.04525375, 62.4414825}, cst{203.57364125, 62.3593979}, cst{203.55053875, 63.3593445}, cst{181.58155958333, 63.3039627}, cst{181.57925541667, 65.8039627}, cst{171.84934625, 65.8126068}, cst{171.96136958333, 72.8125000}}
|
|
||||||
cstLists["EQU"] = []cst{cst{314.08109708333, 2.4773185}, cst{314.045505, 6.4771614}, cst{314.67109625, 6.4826641}, cst{314.61859625, 12.3157644}, cst{317.24836375, 12.3382607}, cst{318.25026458333, 12.3465548}, cst{318.244515, 13.0132008}, cst{321.50110208333, 13.0390635}, cst{321.58347125, 2.5393796}}
|
|
||||||
cstLists["ERI"] = []cst{cst{55.352905416667, 0.4037257}, cst{70.852360416667, 0.2375014}, cst{71.60231375, 0.2289162}, cst{71.55635875, -3.7708201}, cst{77.804395416667, -3.8437285}, cst{77.72003125, -10.8432293}, cst{75.22175125, -10.8138046}, cst{75.178714583333, -14.3135529}, cst{73.929797916667, -14.2989721}, cst{73.75929625, -27.0479794}, cst{71.76333125, -27.0248775}, cst{71.72241875, -29.7746429}, cst{69.97665125, -29.7546597}, cst{69.862375416667, -36.7540054}, cst{65.12985, -36.7010231}, cst{65.0764125, -39.7007294}, cst{59.105884583333, -39.6368256}, cst{59.031364583333, -43.6364403}, cst{52.3267725, -43.5694046}, cst{52.2890025, -45.5692215}, cst{46.09077125, -45.5124779}, cst{46.03451375, -48.5122337}, cst{41.08530875, -48.4710045}, cst{41.04769375, -50.4708595}, cst{37.34121, -50.4425697}, cst{37.28334625, -53.4423561}, cst{33.58414375, -53.4164696}, cst{33.489424583333, -57.9161568}, cst{21.20622875, -57.8484154}, cst{21.2732625, -52.8485603}, cst{24.967354583333, -52.8658562}, cst{24.9938475, -50.8659210}, cst{28.693257083333, -50.8859215}, cst{28.738322916667, -47.5527229}, cst{36.1529475, -47.6004944}, cst{36.26401375, -39.4342155}, cst{46.187260416667, -39.5128975}, cst{46.193322083333, -39.0962563}, cst{53.64428375, -39.1650963}, cst{53.699605416667, -35.5820351}, cst{57.430512083333, -35.6192436}, cst{57.58890125, -24.0033779}, cst{41.14875875, -23.8536034}, cst{41.33922125, -1.2210265}, cst{50.836682916667, -1.3029516}, cst{55.335582083333, -1.3461887}}
|
|
||||||
cstLists["FOR"] = []cst{cst{26.46599875, -23.7562580}, cst{41.14875875, -23.8536034}, cst{57.58890125, -24.0033779}, cst{57.430512083333, -35.6192436}, cst{53.699605416667, -35.5820351}, cst{53.64428375, -39.1650963}, cst{46.193322083333, -39.0962563}, cst{46.187260416667, -39.5128975}, cst{36.26401375, -39.4342155}, cst{26.350727916667, -39.3726234}, cst{26.45888875, -24.8729095}}
|
|
||||||
cstLists["GEM"] = []cst{cst{96.37276375, 11.9332972}, cst{96.4439175, 17.4328651}, cst{95.069575416667, 17.4495068}, cst{95.1241275, 21.4491768}, cst{90.125155416667, 21.5098724}, cst{90.1440375, 22.8430862}, cst{90.22107125, 28.0092907}, cst{99.965657916667, 27.8913116}, cst{100.09027625, 35.3905640}, cst{112.56071875, 35.2445297}, cst{118.28970875, 35.1810532}, cst{118.25808, 33.1812286}, cst{121.99323125, 33.1415138}, cst{121.91596958333, 27.6419144}, cst{120.17164625, 27.6602821}, cst{120.07012375, 19.6608200}, cst{118.94754458333, 19.6728077}, cst{118.87160541667, 13.1732168}, cst{114.2527275, 13.2238064}, cst{114.24100375, 12.2238722}, cst{106.7482275, 12.3095980}, cst{106.71787958333, 9.8097754}, cst{105.71845958333, 9.8214874}, cst{105.742815, 11.8213453}}
|
|
||||||
cstLists["GRU"] = []cst{cst{321.92805291667, -36.4592972}, cst{322.04232125, -44.9588585}, cst{322.11736625, -49.4585724}, cst{331.998825, -49.3911743}, cst{332.113695, -56.3908348}, cst{351.76852208333, -56.3126869}, cst{351.69270458333, -39.3127594}, cst{351.6833775, -36.3127670}, cst{346.72751375, -36.3249741}}
|
|
||||||
cstLists["HER"] = []cst{cst{245.558595, 3.7033811}, cst{242.80966791667, 3.6735139}, cst{242.67663, 15.6728001}, cst{240.18107375, 15.6463346}, cst{240.1110075, 21.6459675}, cst{241.85657541667, 21.6644115}, cst{241.80573458333, 25.6641407}, cst{243.8001825, 25.6855946}, cst{243.78670625, 26.6855240}, cst{246.2798025, 26.7128716}, cst{246.07194875, 39.7117195}, cst{237.36542708333, 39.6189079}, cst{237.12458541667, 51.1176796}, cst{255.75682625, 51.3242683}, cst{255.7863525, 50.3244438}, cst{274.25768875, 50.5470886}, cst{274.34237541667, 47.5476036}, cst{273.46687375, 47.5369873}, cst{273.82438625, 30.0391560}, cst{276.70077291667, 30.0739765}, cst{276.76288625, 26.0743504}, cst{284.2698675, 26.1640968}, cst{284.27716208333, 25.6641407}, cst{284.33913291667, 21.2478352}, cst{284.37363625, 18.6647091}, cst{284.45626208333, 12.1651964}, cst{281.388045, 12.1287737}, cst{275.20308458333, 12.0543308}, cst{275.17327375, 14.3874788}, cst{260.17687791667, 14.2060347}, cst{260.19584708333, 12.7061481}, cst{252.7014825, 12.6179380}, cst{252.80590958333, 3.7852108}}
|
|
||||||
cstLists["HOR"] = []cst{cst{65.0764125, -39.7007294}, cst{64.88238625, -48.6996651}, cst{62.149907916667, -48.6699715}, cst{62.098639583333, -50.6697006}, cst{58.37716625, -50.6304779}, cst{58.318787916667, -52.7968445}, cst{53.365002083333, -52.7470779}, cst{53.23681625, -57.0797844}, cst{48.79113125, -57.0377846}, cst{48.362689583333, -67.0358200}, cst{33.202360416667, -66.9151917}, cst{33.489424583333, -57.9161568}, cst{33.58414375, -53.4164696}, cst{37.28334625, -53.4423561}, cst{37.34121, -50.4425697}, cst{41.04769375, -50.4708595}, cst{41.08530875, -48.4710045}, cst{46.03451375, -48.5122337}, cst{46.09077125, -45.5124779}, cst{52.2890025, -45.5692215}, cst{52.3267725, -43.5694046}, cst{59.031364583333, -43.6364403}, cst{59.105884583333, -39.6368256}}
|
|
||||||
cstLists["HYA"] = []cst{cst{122.84900708333, -0.3693900}, cst{122.92139125, 6.6302376}, cst{140.40425875, 6.4700689}, cst{145.39841708333, 6.4327669}, cst{145.34890625, -0.5670585}, cst{145.27027208333, -11.5667810}, cst{162.80791375, -11.6621428}, cst{162.77554041667, -19.6620827}, cst{164.03058625, -19.6666222}, cst{164.00808291667, -25.1665821}, cst{179.09131041667, -25.1957951}, cst{190.404525, -25.1864014}, cst{190.3985025, -22.6864090}, cst{194.16687, -22.6773415}, cst{215.51309125, -22.5727749}, cst{215.53369041667, -25.0727024}, cst{225.57655375, -24.9951096}, cst{225.63076958333, -29.9948788}, cst{190.41739958333, -30.1863899}, cst{190.42719875, -33.6863785}, cst{185.38743458333, -33.6938934}, cst{185.39029625, -35.6938896}, cst{166.47936291667, -35.6746559}, cst{163.95851541667, -35.6664963}, cst{163.977885, -31.8332005}, cst{160.20137875, -31.8185863}, cst{160.21289375, -29.8186131}, cst{155.18132708333, -29.7947845}, cst{155.1993375, -27.1281624}, cst{147.65928291667, -27.0835037}, cst{147.67968125, -24.5835705}, cst{141.904335, -24.5425186}, cst{137.63677625, -24.5086308}, cst{137.68497, -19.5088310}, cst{130.1635125, -19.4423733}, cst{130.18434, -17.4424706}, cst{126.92709458333, -17.4112568}, cst{126.98977958333, -11.4115648}, cst{122.73417875, -11.3687992}}
|
|
||||||
cstLists["HYI"] = []cst{cst{68.79401875, -67.2479248}, cst{68.58152375, -69.7467194}, cst{67.957485, -74.7431641}, cst{52.075782083333, -74.5741272}, cst{50.091655416667, -82.0644531}, cst{1.53339125, -81.8039551}, cst{1.5662970833333, -74.3039627}, cst{12.3324375, -74.3185730}, cst{12.295414583333, -75.3185272}, cst{20.654050416667, -75.3472214}, cst{21.20622875, -57.8484154}, cst{33.489424583333, -57.9161568}, cst{33.202360416667, -66.9151917}, cst{48.362689583333, -67.0358200}}
|
|
||||||
cstLists["IND"] = []cst{cst{323.1847575, -74.4544678}, cst{351.99783291667, -74.3124619}, cst{351.86139125, -66.8125992}, cst{332.3985675, -66.8899918}, cst{332.113695, -56.3908348}, cst{331.998825, -49.3911743}, cst{322.11736625, -49.4585724}, cst{322.04232125, -44.9588585}, cst{307.169295, -45.0900002}, cst{307.45880125, -56.5885773}, cst{307.56480125, -59.5880547}, cst{322.34865125, -59.4576836}}
|
|
||||||
cstLists["LAC"] = []cst{cst{329.4610125, 36.5953827}, cst{329.37664041667, 44.3451195}, cst{329.88163958333, 44.3482628}, cst{329.87860625, 44.5982513}, cst{330.76266291667, 44.6036453}, cst{330.63921, 53.3532715}, cst{333.17467625, 53.3679428}, cst{333.13762625, 55.6178436}, cst{335.93130125, 55.6326256}, cst{335.91093, 56.8825760}, cst{344.30402708333, 56.9179611}, cst{344.34285125, 53.1680298}, cst{344.46530375, 35.1682358}, cst{343.70919291667, 35.1656151}, cst{343.70653208333, 35.6656113}, cst{331.35955791667, 35.6069336}, cst{331.35046041667, 36.6069069}}
|
|
||||||
cstLists["LEO"] = []cst{cst{162.8497125, -0.6622211}, cst{162.87601958333, 6.3377299}, cst{145.39841708333, 6.4327669}, cst{140.40425875, 6.4700689}, cst{140.645985, 32.9691162}, cst{150.08438541667, 32.9022789}, cst{150.04234375, 27.9024086}, cst{159.23840125, 27.8529167}, cst{159.2108775, 22.8529778}, cst{162.94253875, 22.8376045}, cst{162.95149375, 24.8375893}, cst{166.6809375, 24.8250446}, cst{166.69399791667, 28.3250256}, cst{179.60894125, 28.3040466}, cst{179.60453541667, 13.3040485}, cst{179.60373458333, 10.3040485}, cst{174.36568791667, 10.3082914}, cst{174.35052458333, -0.6916979}, cst{174.34229875, -6.6916924}, cst{162.82713875, -6.6621790}}
|
|
||||||
cstLists["LMI"] = []cst{cst{140.645985, 32.9691162}, cst{140.72163125, 39.2188187}, cst{145.6819725, 39.1817665}, cst{145.70923791667, 41.4316750}, cst{154.37822375, 41.3773613}, cst{154.3594125, 39.3774109}, cst{163.52316875, 39.3356133}, cst{163.48940875, 33.3356781}, cst{166.71422541667, 33.3249931}, cst{166.69399791667, 28.3250256}, cst{166.6809375, 24.8250446}, cst{162.95149375, 24.8375893}, cst{162.94253875, 22.8376045}, cst{159.2108775, 22.8529778}, cst{159.23840125, 27.8529167}, cst{150.04234375, 27.9024086}, cst{150.08438541667, 32.9022789}}
|
|
||||||
cstLists["LEP"] = []cst{cst{73.75929625, -27.0479794}, cst{76.2549375, -27.0772038}, cst{92.99256625, -27.2787991}, cst{93.215625, -11.0301533}, cst{88.965790416667, -10.9785318}, cst{77.72003125, -10.8432293}, cst{75.22175125, -10.8138046}, cst{75.178714583333, -14.3135529}, cst{73.929797916667, -14.2989721}}
|
|
||||||
cstLists["LIB"] = []cst{cst{227.85301208333, -0.4742887}, cst{221.6030925, -0.5269387}, cst{221.66710791667, -8.5266848}, cst{215.40850625, -8.5731344}, cst{215.51309125, -22.5727749}, cst{215.53369041667, -25.0727024}, cst{225.57655375, -24.9951096}, cst{225.63076958333, -29.9948788}, cst{236.92998, -29.8896160}, cst{236.81306375, -20.3902016}, cst{240.57177625, -20.3516178}, cst{240.43727875, -8.3523235}, cst{240.38695375, -3.6025870}, cst{227.88195125, -3.7241600}}
|
|
||||||
cstLists["LUP"] = []cst{cst{214.65681625, -55.5799522}, cst{220.23446541667, -55.5400887}, cst{228.08351125, -55.4754944}, cst{228.05671625, -54.4756165}, cst{232.35337208333, -54.4364166}, cst{232.20724625, -48.4371071}, cst{237.24728125, -48.3880234}, cst{237.12458541667, -42.3886375}, cst{242.15280625, -42.3366776}, cst{241.94769875, -29.8377628}, cst{236.92998, -29.8896160}, cst{225.63076958333, -29.9948788}, cst{225.79627958333, -42.4941750}, cst{214.45026458333, -42.5806465}}
|
|
||||||
cstLists["LYN"] = []cst{cst{112.56071875, 35.2445297}, cst{112.73412458333, 44.2435493}, cst{104.26530291667, 44.3418388}, cst{104.40635875, 49.8410034}, cst{99.919459583333, 49.8945885}, cst{100.04603125, 53.8938293}, cst{94.05736625, 53.9662552}, cst{94.13108875, 55.9658089}, cst{94.40745625, 61.9641266}, cst{107.8515525, 61.8031464}, cst{107.7531975, 59.8037262}, cst{122.12910125, 59.6433983}, cst{128.79913375, 59.5759888}, cst{128.44010375, 46.5777283}, cst{139.59071125, 46.4782791}, cst{139.51249041667, 41.4785957}, cst{145.70923791667, 41.4316750}, cst{145.6819725, 39.1817665}, cst{140.72163125, 39.2188187}, cst{140.645985, 32.9691162}, cst{121.99323125, 33.1415138}, cst{118.25808, 33.1812286}, cst{118.28970875, 35.1810532}}
|
|
||||||
cstLists["LYR"] = []cst{cst{284.27716208333, 25.6641407}, cst{284.2698675, 26.1640968}, cst{276.76288625, 26.0743504}, cst{276.70077291667, 30.0739765}, cst{273.82438625, 30.0391560}, cst{273.46687375, 47.5369873}, cst{274.34237541667, 47.5476036}, cst{287.1206475, 47.6998672}, cst{288.37552041667, 47.7143936}, cst{288.47030708333, 43.7149391}, cst{291.9835275, 43.7550354}, cst{292.11965541667, 36.7558022}, cst{291.49260458333, 36.7487144}, cst{291.5987775, 30.2493153}, cst{290.0952525, 30.2321968}, cst{290.13264625, 27.7324085}, cst{290.16131375, 25.7325745}}
|
|
||||||
cstLists["MEN"] = []cst{cst{109.01970875, -85.2614441}, cst{48.23292, -84.5553818}, cst{50.091655416667, -82.0644531}, cst{52.075782083333, -74.5741272}, cst{67.957485, -74.7431641}, cst{68.58152375, -69.7467194}, cst{98.454422916667, -70.1041336}, cst{97.770709583333, -75.1000366}, cst{114.21470375, -75.2899170}, cst{111.65211458333, -82.7758865}}
|
|
||||||
cstLists["MIC"] = []cst{cst{306.89795541667, -27.5913391}, cst{321.83163625, -27.4596672}, cst{321.92805291667, -36.4592972}, cst{322.04232125, -44.9588585}, cst{307.169295, -45.0900002}}
|
|
||||||
cstLists["MON"] = []cst{cst{95.225680416667, -0.0537102}, cst{95.34803125, 9.9455481}, cst{96.347665416667, 9.9334478}, cst{96.37276375, 11.9332972}, cst{105.742815, 11.8213453}, cst{105.71845958333, 9.8214874}, cst{106.71787958333, 9.8097754}, cst{106.66432208333, 5.3100886}, cst{106.91427458333, 5.3071680}, cst{106.86739625, 1.3074419}, cst{109.61691875, 1.2755718}, cst{109.59966625, -0.2243290}, cst{122.84900708333, -0.3693900}, cst{122.73417875, -11.3687992}, cst{111.97339958333, -11.2521448}, cst{93.215625, -11.0301533}, cst{88.965790416667, -10.9785318}, cst{89.052372083333, -3.9790573}, cst{95.177142083333, -4.0534163}}
|
|
||||||
cstLists["MUS"] = []cst{cst{170.08481125, -64.6842651}, cst{169.85697291667, -75.6840134}, cst{207.78143375, -75.6235962}, cst{207.46087041667, -70.6244431}, cst{207.26802291667, -65.6249542}, cst{204.70747958333, -65.6378784}, cst{204.68028625, -64.6379395}, cst{194.43838041667, -64.6769638}, cst{179.05736375, -64.6957855}}
|
|
||||||
cstLists["NOR"] = []cst{cst{232.5498675, -60.4354935}, cst{249.03468125, -60.2644577}, cst{248.57062375, -45.7670517}, cst{248.4947775, -42.2674789}, cst{242.15280625, -42.3366776}, cst{237.12458541667, -42.3886375}, cst{237.24728125, -48.3880234}, cst{232.20724625, -48.4371071}, cst{232.35337208333, -54.4364166}, cst{228.05671625, -54.4756165}, cst{228.08351125, -55.4754944}, cst{232.38191125, -55.4362831}}
|
|
||||||
cstLists["OCT"] = []cst{cst{0.80064625, -89.3039017}, cst{1.53339125, -81.8039551}, cst{50.091655416667, -82.0644531}, cst{48.23292, -84.5553818}, cst{109.01970875, -85.2614441}, cst{111.65211458333, -82.7758865}, cst{209.11110875, -83.1200714}, cst{276.86599791667, -82.4582748}, cst{274.19506041667, -74.9745178}, cst{323.1847575, -74.4544678}, cst{351.99783291667, -74.3124619}, cst{1.56630625, -74.3039627}, cst{0.80064625, -89.3039017}, cst{0.80065208333333, -89.3038940}}
|
|
||||||
cstLists["OPH"] = []cst{cst{245.6026275, -0.2963768}, cst{245.558595, 3.7033811}, cst{252.80590958333, 3.7852108}, cst{252.7014825, 12.6179380}, cst{260.19584708333, 12.7061481}, cst{260.17687791667, 14.2060347}, cst{275.17327375, 14.3874788}, cst{275.20308458333, 12.0543308}, cst{281.388045, 12.1287737}, cst{281.45856875, 6.3791943}, cst{275.27461041667, 6.3047633}, cst{275.29601125, 4.5548930}, cst{277.87113125, 4.5860157}, cst{277.88929958333, 3.0861249}, cst{275.31423625, 3.0550034}, cst{275.35059875, 0.0552235}, cst{269.10103791667, -0.0206471}, cst{269.14970375, -4.0203514}, cst{271.14967375, -3.9960551}, cst{271.22371625, -9.9956055}, cst{266.72375708333, -10.0502338}, cst{266.7447, -11.7167768}, cst{265.49440375, -11.7319136}, cst{265.47349041667, -10.0653696}, cst{259.22206958333, -10.1404381}, cst{259.29742791667, -16.1399899}, cst{265.80019041667, -16.0618820}, cst{266.00183541667, -30.0606632}, cst{253.23534875, -30.2123089}, cst{253.15567041667, -24.7960968}, cst{245.89144625, -24.8781185}, cst{245.82298375, -19.5451660}, cst{247.45067541667, -19.5271549}, cst{247.43823, -18.5272255}, cst{245.81068041667, -18.5452347}, cst{245.69120375, -8.2958899}, cst{240.43727875, -8.3523235}, cst{240.38695375, -3.6025870}, cst{245.63838875, -3.5461800}}
|
|
||||||
cstLists["ORI"] = []cst{cst{70.852360416667, 0.2375014}, cst{71.03402875, 15.7364635}, cst{76.28892625, 15.6755352}, cst{76.29527875, 16.1754990}, cst{81.7987125, 16.1101055}, cst{81.7922325, 15.6101446}, cst{85.79364625, 15.5619202}, cst{85.755057083333, 12.5621548}, cst{88.255369583333, 12.5318508}, cst{88.327167083333, 18.0314159}, cst{87.326982083333, 18.0435486}, cst{87.39379375, 22.8764725}, cst{90.1440375, 22.8430862}, cst{90.125155416667, 21.5098724}, cst{95.1241275, 21.4491768}, cst{95.069575416667, 17.4495068}, cst{96.4439175, 17.4328651}, cst{96.37276375, 11.9332972}, cst{96.347665416667, 9.9334478}, cst{95.34803125, 9.9455481}, cst{95.225680416667, -0.0537102}, cst{95.177142083333, -4.0534163}, cst{89.052372083333, -3.9790573}, cst{88.965790416667, -10.9785318}, cst{77.72003125, -10.8432293}, cst{77.804395416667, -3.8437285}, cst{71.55635875, -3.7708201}, cst{71.60231375, 0.2289162}}
|
|
||||||
cstLists["PAV"] = []cst{cst{274.19506041667, -74.9745178}, cst{323.1847575, -74.4544678}, cst{322.34865125, -59.4576836}, cst{307.56480125, -59.5880547}, cst{307.45880125, -56.5885773}, cst{272.67225291667, -56.9837723}, cst{265.16818958333, -57.0747757}, cst{265.77572875, -67.5711060}, cst{273.28007708333, -67.4800797}}
|
|
||||||
cstLists["PEG"] = []cst{cst{321.58347125, 2.5393796}, cst{321.50110208333, 13.0390635}, cst{318.244515, 13.0132008}, cst{318.25026458333, 12.3465548}, cst{317.24836375, 12.3382607}, cst{317.17878291667, 20.0046406}, cst{320.18840875, 20.0290813}, cst{320.15170041667, 24.0289364}, cst{322.66201958333, 24.0482101}, cst{322.62016375, 28.5480537}, cst{327.39518125, 28.5817947}, cst{327.319965, 36.5815468}, cst{329.4610125, 36.5953827}, cst{331.35046041667, 36.6069069}, cst{331.35955791667, 35.6069336}, cst{343.70653208333, 35.6656113}, cst{343.70919291667, 35.1656151}, cst{344.46530375, 35.1682358}, cst{354.04417958333, 35.1913109}, cst{354.04915791667, 32.7746468}, cst{357.8280825, 32.7785072}, cst{357.82874125, 32.0285034}, cst{1.6069770833333, 32.0293655}, cst{1.60621625, 28.6960354}, cst{2.6128425, 28.6957588}, cst{2.61001625, 22.6957588}, cst{3.7406445833333, 22.6951923}, cst{3.73992125, 21.6951923}, cst{3.7341179166667, 13.1951942}, cst{1.6031745833333, 13.1960354}, cst{1.6027304166667, 10.6960354}, cst{359.09711875, 10.6957970}, cst{359.09803375, 8.1957970}, cst{342.82141625, 8.1621685}, cst{342.84221708333, 2.6622071}, cst{331.58726708333, 2.6076074}, cst{331.588755, 2.3576119}, cst{326.58708625, 2.3256910}, cst{326.58021875, 3.3256676}, cst{323.57874875, 3.3043909}, cst{323.58427041667, 2.5544112}}
|
|
||||||
cstLists["PER"] = []cst{cst{42.62838, 31.1865025}, cst{42.666467916667, 34.5196762}, cst{40.402382916667, 34.5375137}, cst{40.43465875, 37.2873878}, cst{39.67934125, 37.2931557}, cst{39.88547875, 51.0423737}, cst{32.67380125, 51.0925827}, cst{32.62149125, 47.5927505}, cst{26.931439583333, 47.6258430}, cst{26.96852375, 50.6257439}, cst{22.40793625, 50.6478767}, cst{22.45601375, 54.6477699}, cst{27.53364125, 54.6228828}, cst{27.5952225, 58.1227188}, cst{30.77362375, 58.1046753}, cst{30.795625416667, 59.1046104}, cst{38.802355416667, 59.0511551}, cst{38.762337083333, 57.5513000}, cst{48.90093, 57.4684982}, cst{49.91349625, 57.4593849}, cst{49.8540225, 55.4596519}, cst{52.381900416667, 55.4362831}, cst{52.31308625, 52.9366074}, cst{72.840285, 52.7196465}, cst{72.45734375, 36.2218513}, cst{69.57384125, 36.2547150}, cst{69.4869375, 30.9218750}, cst{52.426667916667, 31.1003609}}
|
|
||||||
cstLists["PHE"] = []cst{cst{351.69270458333, -39.3127594}, cst{351.76852208333, -56.3126869}, cst{351.77839375, -57.8126793}, cst{21.20622875, -57.8484154}, cst{21.2732625, -52.8485603}, cst{24.967354583333, -52.8658562}, cst{24.9938475, -50.8659210}, cst{28.693257083333, -50.8859215}, cst{28.738322916667, -47.5527229}, cst{36.1529475, -47.6004944}, cst{36.26401375, -39.4342155}, cst{26.350727916667, -39.3726234}}
|
|
||||||
cstLists["PIC"] = []cst{cst{90.951777083333, -43.0057793}, cst{75.97444375, -42.8255501}, cst{73.482370416667, -42.7963676}, cst{73.402082916667, -46.2959023}, cst{68.42409625, -46.2387962}, cst{68.362247916667, -48.7384491}, cst{68.217745416667, -53.7376366}, cst{75.6770175, -53.8238029}, cst{75.547742916667, -57.3230400}, cst{83.01880375, -57.4122620}, cst{82.85761375, -60.9112892}, cst{90.34506125, -61.0020981}, cst{90.173642916667, -64.0010529}, cst{98.93724875, -64.1070251}, cst{102.70331375, -64.1518784}, cst{103.01111708333, -58.1537018}, cst{97.995077916667, -58.0938416}, cst{98.114275416667, -55.0945587}, cst{93.1074, -55.0340500}, cst{93.19435375, -52.5345764}, cst{90.693705, -52.5042114}, cst{90.748902083333, -50.7545471}}
|
|
||||||
cstLists["PSC"] = []cst{cst{342.8497125, 0.6622211}, cst{342.84221708333, 2.6622071}, cst{342.82141625, 8.1621685}, cst{359.09803375, 8.1957970}, cst{359.09711875, 10.6957970}, cst{1.6027304166667, 10.6960354}, cst{1.6031745833333, 13.1960354}, cst{3.7341179166667, 13.1951942}, cst{3.73992125, 21.6951923}, cst{14.414815416667, 21.6766376}, cst{14.424064583333, 24.4266243}, cst{12.41349125, 24.4319324}, cst{12.44306375, 33.6818962}, cst{22.89742625, 33.6453705}, cst{22.86642, 28.6454391}, cst{26.76471, 28.6262817}, cst{26.744674583333, 25.6263351}, cst{26.65573375, 10.5432396}, cst{31.6652475, 10.5143948}, cst{31.61526625, 2.5978806}, cst{6.6037875, 2.6925383}, cst{6.60132875, 0.6925398}, cst{6.5927025, -6.3074551}, cst{359.10329875, -6.3042021}, cst{359.10221125, -3.3042023}, cst{342.86470375, -3.3377509}}
|
|
||||||
cstLists["PSA"] = []cst{cst{346.68096625, -24.8250446}, cst{329.77028875, -24.9040413}, cst{321.80777541667, -24.9597607}, cst{321.83163625, -27.4596672}, cst{321.92805291667, -36.4592972}, cst{346.72751375, -36.3249741}}
|
|
||||||
cstLists["PUP"] = []cst{cst{111.97339958333, -11.2521448}, cst{111.67719875, -33.2504692}, cst{99.903859583333, -33.1128159}, cst{99.70891625, -43.1116486}, cst{90.951777083333, -43.0057793}, cst{90.748902083333, -50.7545471}, cst{120.8616975, -51.1025848}, cst{121.03827875, -43.3535042}, cst{126.57231291667, -43.4095192}, cst{126.67779875, -37.1600380}, cst{126.92709458333, -17.4112568}, cst{126.98977958333, -11.4115648}, cst{122.73417875, -11.3687992}}
|
|
||||||
cstLists["PYX"] = []cst{cst{126.92709458333, -17.4112568}, cst{130.18434, -17.4424706}, cst{130.1635125, -19.4423733}, cst{137.68497, -19.5088310}, cst{137.63677625, -24.5086308}, cst{141.904335, -24.5425186}, cst{141.77159875, -37.2920151}, cst{126.67779875, -37.1600380}}
|
|
||||||
cstLists["RET"] = []cst{cst{48.362689583333, -67.0358200}, cst{68.79401875, -67.2479248}, cst{69.274534583333, -58.7506638}, cst{65.55459, -58.7088547}, cst{65.6504625, -56.2093849}, cst{60.69291875, -56.1555862}, cst{60.79789625, -52.8228111}, cst{58.318787916667, -52.7968445}, cst{53.365002083333, -52.7470779}, cst{53.23681625, -57.0797844}, cst{48.79113125, -57.0377846}}
|
|
||||||
cstLists["SGE"] = []cst{cst{284.37363625, 18.6647091}, cst{284.33913291667, 21.2478352}, cst{290.09631125, 21.3148155}, cst{290.12128791667, 19.3982983}, cst{298.88568875, 19.4955387}, cst{298.860255, 21.5787334}, cst{305.12540875, 21.6436558}, cst{305.13404875, 20.8936996}, cst{305.18694875, 16.1439629}, cst{303.5589975, 16.1275158}, cst{298.926, 16.0790844}, cst{298.92116541667, 16.4957294}, cst{286.4054775, 16.3550682}, cst{286.37549375, 18.6882229}}
|
|
||||||
cstLists["SGR"] = []cst{cst{284.74405375, -11.8664360}, cst{284.79372, -15.8328123}, cst{275.54952625, -15.9435720}, cst{265.80019041667, -16.0618820}, cst{266.00183541667, -30.0606632}, cst{269.50281125, -30.0182076}, cst{269.62546375, -37.0174599}, cst{289.59631958333, -36.7785645}, cst{289.76964, -45.2775650}, cst{307.169295, -45.0900002}, cst{306.89795541667, -27.5913391}, cst{301.91596958333, -27.6419144}, cst{301.72636958333, -11.6762342}}
|
|
||||||
cstLists["SCO"] = []cst{cst{240.43727875, -8.3523235}, cst{245.69120375, -8.2958899}, cst{245.81068041667, -18.5452347}, cst{247.43823, -18.5272255}, cst{247.45067541667, -19.5271549}, cst{245.82298375, -19.5451660}, cst{245.89144625, -24.8781185}, cst{253.15567041667, -24.7960968}, cst{253.23534875, -30.2123089}, cst{266.00183541667, -30.0606632}, cst{269.50281125, -30.0182076}, cst{269.62546375, -37.0174599}, cst{269.80928375, -45.5163460}, cst{248.57062375, -45.7670517}, cst{248.4947775, -42.2674789}, cst{242.15280625, -42.3366776}, cst{241.94769875, -29.8377628}, cst{236.92998, -29.8896160}, cst{236.81306375, -20.3902016}, cst{240.57177625, -20.3516178}}
|
|
||||||
cstLists["SCL"] = []cst{cst{346.68096625, -24.8250446}, cst{359.11056458333, -24.8042011}, cst{26.45888875, -24.8729095}, cst{26.350727916667, -39.3726234}, cst{351.69270458333, -39.3127594}, cst{351.6833775, -36.3127670}, cst{346.72751375, -36.3249741}}
|
|
||||||
cstLists["SCT"] = []cst{cst{275.54952625, -15.9435720}, cst{284.79372, -15.8328123}, cst{284.74405375, -11.8664360}, cst{284.64729291667, -3.8336766}, cst{280.3981875, -3.8842230}, cst{275.3991225, -3.9444826}}
|
|
||||||
cstLists["SER1"] = []cst{cst{227.85301208333, -0.4742887}, cst{227.78148625, 7.5253930}, cst{227.60549125, 25.5246105}, cst{229.09951625, 25.5380573}, cst{241.80573458333, 25.6641407}, cst{241.85657541667, 21.6644115}, cst{240.1110075, 21.6459675}, cst{240.18107375, 15.6463346}, cst{242.67663, 15.6728001}, cst{242.80966791667, 3.6735139}, cst{245.558595, 3.7033811}, cst{245.6026275, -0.2963768}, cst{245.63838875, -3.5461800}, cst{240.38695375, -3.6025870}, cst{227.88195125, -3.7241600}}
|
|
||||||
cstLists["SER2"] = []cst{cst{275.35059875, 0.0552235}, cst{275.31423625, 3.0550034}, cst{277.93922375, 3.0867271}, cst{277.92105625, 4.5866175}, cst{275.29601125, 4.5548930}, cst{275.27461041667, 6.3047633}, cst{281.45856875, 6.3791943}, cst{284.525985, 6.4156075}, cst{284.57642541667, 2.1659052}, cst{280.3262325, 2.1153460}, cst{280.35020875, 0.1154895}, cst{280.3981875, -3.8842230}, cst{275.3991225, -3.9444826}, cst{275.54952625, -15.9435720}, cst{265.80019041667, -16.0618820}, cst{259.29742791667, -16.1399899}, cst{259.22206958333, -10.1404381}, cst{265.47349041667, -10.0653696}, cst{265.49440375, -11.7319136}, cst{266.7447, -11.7167768}, cst{266.72375708333, -10.0502338}, cst{271.22371625, -9.9956055}, cst{271.14967375, -3.9960551}, cst{269.14970375, -4.0203514}, cst{269.10103791667, -0.0206471}}
|
|
||||||
cstLists["SEX"] = []cst{cst{145.34890625, -0.5670585}, cst{145.39841708333, 6.4327669}, cst{162.87601958333, 6.3377299}, cst{162.8497125, -0.6622211}, cst{162.82713875, -6.6621790}, cst{162.80791375, -11.6621428}, cst{145.27027208333, -11.5667810}}
|
|
||||||
cstLists["TAU"] = []cst{cst{50.836682916667, -1.3029516}, cst{50.85298375, 0.4469725}, cst{50.94641125, 10.3632069}, cst{51.037234583333, 19.4461136}, cst{52.2906225, 19.4343338}, cst{52.426667916667, 31.1003609}, cst{69.4869375, 30.9218750}, cst{69.47678875, 30.2552605}, cst{73.235342916667, 30.2123089}, cst{73.212475416667, 28.7124405}, cst{90.228902916667, 28.5092430}, cst{90.22107125, 28.0092907}, cst{90.1440375, 22.8430862}, cst{87.39379375, 22.8764725}, cst{87.326982083333, 18.0435486}, cst{88.327167083333, 18.0314159}, cst{88.255369583333, 12.5318508}, cst{85.755057083333, 12.5621548}, cst{85.79364625, 15.5619202}, cst{81.7922325, 15.6101446}, cst{81.7987125, 16.1101055}, cst{76.29527875, 16.1754990}, cst{76.28892625, 15.6755352}, cst{71.03402875, 15.7364635}, cst{70.852360416667, 0.2375014}, cst{55.352905416667, 0.4037257}, cst{55.335582083333, -1.3461887}}
|
|
||||||
cstLists["TEL"] = []cst{cst{307.45880125, -56.5885773}, cst{307.169295, -45.0900002}, cst{289.76964, -45.2775650}, cst{272.3090175, -45.4859734}, cst{272.67225291667, -56.9837723}}
|
|
||||||
cstLists["TRI"] = []cst{cst{26.744674583333, 25.6263351}, cst{26.76471, 28.6262817}, cst{22.86642, 28.6454391}, cst{22.89742625, 33.6453705}, cst{22.910835, 35.6453362}, cst{31.854250416667, 35.5971375}, cst{31.87109125, 37.3470840}, cst{39.67934125, 37.2931557}, cst{40.43465875, 37.2873878}, cst{40.402382916667, 34.5375137}, cst{42.666467916667, 34.5196762}, cst{42.62838, 31.1865025}, cst{38.10319375, 31.2213154}, cst{38.07014875, 27.8047638}, cst{30.53061625, 27.8550186}, cst{30.51371125, 25.6050701}}
|
|
||||||
cstLists["TRA"] = []cst{cst{224.16644125, -70.5115433}, cst{224.00363375, -68.0122070}, cst{226.55712625, -67.9909286}, cst{226.35353541667, -64.0751266}, cst{230.16657875, -64.0415649}, cst{230.05456958333, -61.4587479}, cst{232.58976458333, -61.4353065}, cst{232.5498675, -60.4354935}, cst{249.03468125, -60.2644577}, cst{249.08163, -61.2641945}, cst{251.53784708333, -61.2364578}, cst{251.67632125, -63.8189964}, cst{254.1951375, -63.7900925}, cst{254.28351458333, -65.2062531}, cst{255.5423925, -65.1916428}, cst{255.72498291667, -67.6905823}, cst{258.2424825, -67.6610870}, cst{258.47067875, -70.1597443}}
|
|
||||||
cstLists["TUC"] = []cst{cst{351.99783291667, -74.3124619}, cst{1.5662970833333, -74.3039627}, cst{12.3324375, -74.3185730}, cst{12.295414583333, -75.3185272}, cst{20.654050416667, -75.3472214}, cst{21.20622875, -57.8484154}, cst{351.77839375, -57.8126793}, cst{351.76852208333, -56.3126869}, cst{332.113695, -56.3908348}, cst{332.3985675, -66.8899918}, cst{351.86139125, -66.8125992}}
|
|
||||||
cstLists["UMA"] = []cst{cst{145.70923791667, 41.4316750}, cst{139.51249041667, 41.4785957}, cst{139.59071125, 46.4782791}, cst{128.44010375, 46.5777283}, cst{128.79913375, 59.5759888}, cst{122.12910125, 59.6433983}, cst{123.08622875, 73.1383743}, cst{140.61547375, 72.9741364}, cst{171.96136958333, 72.8125000}, cst{171.84934625, 65.8126068}, cst{181.57925541667, 65.8039627}, cst{181.58155958333, 63.3039627}, cst{203.55053875, 63.3593445}, cst{203.57364125, 62.3593979}, cst{217.04525375, 62.4414825}, cst{217.25124875, 54.9422379}, cst{211.58439125, 54.9035759}, cst{211.69873208333, 47.9039383}, cst{203.79511375, 47.8599281}, cst{203.74239875, 52.3598061}, cst{182.8185225, 52.3043365}, cst{182.82643375, 44.3043365}, cst{181.59141625, 44.3039627}, cst{181.59450625, 33.3039627}, cst{181.59566375, 28.3039627}, cst{179.60894125, 28.3040466}, cst{166.69399791667, 28.3250256}, cst{166.71422541667, 33.3249931}, cst{163.48940875, 33.3356781}, cst{163.52316875, 39.3356133}, cst{154.3594125, 39.3774109}, cst{154.37822375, 41.3773613}}
|
|
||||||
cstLists["UMI"] = []cst{cst{195.8206125, 76.3289108}, cst{196.09747375, 69.3293610}, cst{210.65081125, 69.3991165}, cst{210.82055541667, 65.3996506}, cst{235.32956541667, 65.6023483}, cst{235.05063, 69.6009445}, cst{247.8410625, 69.7383041}, cst{247.2207075, 74.7347870}, cst{261.53663708333, 74.9033127}, cst{260.21790458333, 79.8953476}, cst{267.65602041667, 79.9857483}, cst{261.72223041667, 85.9495697}, cst{308.72097, 86.4656219}, cst{308.33135541667, 86.6306305}, cst{343.51066625, 86.8368912}, cst{339.26098791667, 88.6638870}, cst{135.83247125, 87.5689163}, cst{130.40275041667, 86.0975418}, cst{213.0229575, 85.9308090}, cst{216.78285625, 79.4449844}, cst{203.80918958333, 79.3629303}, cst{204.15701875, 76.3638153}}
|
|
||||||
cstLists["VEL"] = []cst{cst{166.33725625, -57.1744423}, cst{166.45650458333, -40.4246216}, cst{141.73406125, -40.2918739}, cst{141.77159875, -37.2920151}, cst{126.67779875, -37.1600380}, cst{126.57231291667, -43.4095192}, cst{121.03827875, -43.3535042}, cst{120.8616975, -51.1025848}, cst{123.38112875, -51.1285286}, cst{123.32011625, -53.3782196}, cst{127.60929125, -53.4206772}, cst{127.56711875, -54.9204712}, cst{133.38017375, -54.9742203}, cst{133.32365541667, -56.9739723}}
|
|
||||||
cstLists["VIR"] = []cst{cst{174.35052458333, -0.6916979}, cst{174.36568791667, 10.3082914}, cst{179.60373458333, 10.3040485}, cst{179.60453541667, 13.3040485}, cst{194.0620275, 13.3225126}, cst{194.05906625, 14.3225088}, cst{204.02893041667, 14.3604937}, cst{204.06384875, 7.3605771}, cst{227.78148625, 7.5253930}, cst{227.85301208333, -0.4742887}, cst{221.6030925, -0.5269387}, cst{221.66710791667, -8.5266848}, cst{215.40850625, -8.5731344}, cst{215.51309125, -22.5727749}, cst{194.16687, -22.6773415}, cst{194.1330525, -11.6773882}, cst{179.09676, -11.6957970}, cst{179.09860625, -6.6957974}, cst{174.34229875, -6.6916924}}
|
|
||||||
cstLists["VOL"] = []cst{cst{98.93724875, -64.1070251}, cst{98.454422916667, -70.1041336}, cst{97.770709583333, -75.1000366}, cst{114.21470375, -75.2899170}, cst{135.24368708333, -75.4954681}, cst{136.09472708333, -64.4990387}, cst{102.70331375, -64.1518784}}
|
|
||||||
cstLists["VUL"] = []cst{cst{284.33913291667, 21.2478352}, cst{284.27716208333, 25.6641407}, cst{290.16131375, 25.7325745}, cst{290.13264625, 27.7324085}, cst{296.27220125, 27.8011742}, cst{296.25094375, 29.3010578}, cst{315.07258375, 29.4871387}, cst{315.08391458333, 28.4871883}, cst{322.62016375, 28.5480537}, cst{322.66201958333, 24.0482101}, cst{320.15170041667, 24.0289364}, cst{320.18840875, 20.0290813}, cst{317.17878291667, 20.0046406}, cst{309.907665, 19.9399967}, cst{309.89693708333, 20.9399471}, cst{305.13404875, 20.8936996}, cst{305.12540875, 21.6436558}, cst{298.860255, 21.5787334}, cst{298.88568875, 19.4955387}, cst{290.12128791667, 19.3982983}, cst{290.09631125, 21.3148155}}
|
|
||||||
change := []string{"PSC", "TUC", "PHE", "SCL", "CET", "PEG", "AND", "CAS", "CEP"}
|
|
||||||
for _, v := range change {
|
|
||||||
for k, v2 := range cstLists[v] {
|
|
||||||
if v2.RA < 270 {
|
|
||||||
cstLists[v][k].RA = v2.RA + 360
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//选定 RA=277.5 DEC=-40
|
|
||||||
|
|
||||||
func isCross(a, b, c, d cst) bool {
|
|
||||||
var ac, bc, ad, bd, ca, cb, da, db cst
|
|
||||||
var r1, r2 float64
|
|
||||||
ac.RA = a.RA - c.RA
|
|
||||||
ac.DEC = a.DEC - c.DEC
|
|
||||||
ad.RA = a.RA - d.RA
|
|
||||||
ad.DEC = a.DEC - d.DEC
|
|
||||||
r1 = ac.RA*ad.DEC - ad.RA*ac.DEC
|
|
||||||
bc.RA = b.RA - c.RA
|
|
||||||
bc.DEC = b.DEC - c.DEC
|
|
||||||
bd.RA = b.RA - d.RA
|
|
||||||
bd.DEC = b.DEC - d.DEC
|
|
||||||
r2 = bc.RA*bd.DEC - bd.RA*bc.DEC
|
|
||||||
//echo r1.' '.r2;
|
|
||||||
if r1*r2 > 0 {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
ca.RA = c.RA - a.RA
|
|
||||||
ca.DEC = c.DEC - a.DEC
|
|
||||||
cb.RA = c.RA - b.RA
|
|
||||||
cb.DEC = c.DEC - b.DEC
|
|
||||||
r1 = ca.RA*cb.DEC - cb.RA*ca.DEC
|
|
||||||
da.RA = d.RA - a.RA
|
|
||||||
da.DEC = d.DEC - a.DEC
|
|
||||||
db.RA = d.RA - b.RA
|
|
||||||
db.DEC = d.DEC - b.DEC
|
|
||||||
r2 = da.RA*db.DEC - db.RA*da.DEC
|
|
||||||
if r1*r2 > 0 {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func IsXZ(ra, dec, jde float64) string {
|
|
||||||
var nra, ndec float64
|
|
||||||
if cstLists == nil || len(cstLists) == 0 {
|
|
||||||
initCstData()
|
|
||||||
}
|
|
||||||
nra = ra
|
|
||||||
if ra >= 360 {
|
|
||||||
nra -= 360
|
|
||||||
}
|
|
||||||
nra, ndec = ZuoBiaoSuiCha(nra, dec, jde, 2451545.0)
|
|
||||||
if ra >= 360 && nra < 270 {
|
|
||||||
nra += 360
|
|
||||||
}
|
|
||||||
for k, v := range cstLists {
|
|
||||||
var count int = 0
|
|
||||||
for i := 0; i < len(v)-1; i++ {
|
|
||||||
if k == "UMI" || k == "OCT" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if i == 0 {
|
|
||||||
if isCross(cst{277.5, -100}, cst{nra, ndec}, v[len(v)-1], v[0]) {
|
|
||||||
count++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if isCross(cst{277.5, -100}, cst{nra, ndec}, v[i], v[i+1]) {
|
|
||||||
count++
|
|
||||||
}
|
|
||||||
if FR((nra-277.5)*(v[i].DEC+100)) == FR((v[i].RA-277.5)*(ndec+100)) {
|
|
||||||
count++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if count%2 == 1 {
|
|
||||||
return k
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if nra <= 270 {
|
|
||||||
ra = ra + 360
|
|
||||||
return IsXZ(ra, dec, jde)
|
|
||||||
}
|
|
||||||
if ndec > 50 {
|
|
||||||
return "UMI"
|
|
||||||
} else if ndec < -50 {
|
|
||||||
return "OCT"
|
|
||||||
}
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
func WhichCst(ra, dec, jde float64) string {
|
|
||||||
cst := make(map[string]string, 88)
|
|
||||||
cst["AND"] = "仙女座"
|
|
||||||
cst["ANT"] = "唧筒座"
|
|
||||||
cst["APS"] = "天燕座"
|
|
||||||
cst["AQR"] = "宝瓶座"
|
|
||||||
cst["AQL"] = "天鹰座"
|
|
||||||
cst["ARA"] = "天坛座"
|
|
||||||
cst["ARI"] = "白羊座"
|
|
||||||
cst["AUR"] = "御夫座"
|
|
||||||
cst["BOO"] = "牧夫座"
|
|
||||||
cst["CAE"] = "雕具座"
|
|
||||||
cst["CAM"] = "鹿豹座"
|
|
||||||
cst["CNC"] = "巨蟹座"
|
|
||||||
cst["CVN"] = "猎犬座"
|
|
||||||
cst["CMA"] = "大犬座"
|
|
||||||
cst["CMI"] = "小犬座"
|
|
||||||
cst["CAP"] = "摩羯座"
|
|
||||||
cst["CAR"] = "船底座"
|
|
||||||
cst["CAS"] = "仙后座"
|
|
||||||
cst["CEN"] = "半人马座"
|
|
||||||
cst["CEP"] = "仙王座"
|
|
||||||
cst["CET"] = "鲸鱼座"
|
|
||||||
cst["CHA"] = "蝘蜓座"
|
|
||||||
cst["CIR"] = "圆规座"
|
|
||||||
cst["COL"] = "天鸽座"
|
|
||||||
cst["COM"] = "后发座"
|
|
||||||
cst["CRA"] = "南冕座"
|
|
||||||
cst["CRB"] = "北冕座"
|
|
||||||
cst["CRV"] = "乌鸦座"
|
|
||||||
cst["CRT"] = "巨爵座"
|
|
||||||
cst["CRU"] = "南十字座"
|
|
||||||
cst["CYG"] = "天鹅座"
|
|
||||||
cst["DEL"] = "海豚座"
|
|
||||||
cst["DOR"] = "剑鱼座"
|
|
||||||
cst["DRA"] = "天龙座"
|
|
||||||
cst["EQU"] = "小马座"
|
|
||||||
cst["ERI"] = "波江座"
|
|
||||||
cst["FOR"] = "天炉座"
|
|
||||||
cst["GEM"] = "双子座"
|
|
||||||
cst["GRU"] = "天鹤座"
|
|
||||||
cst["HER"] = "武仙座"
|
|
||||||
cst["HOR"] = "时钟座"
|
|
||||||
cst["HYA"] = "长蛇座"
|
|
||||||
cst["HYI"] = "水蛇座"
|
|
||||||
cst["IND"] = "印第安座"
|
|
||||||
cst["LAC"] = "蝎虎座"
|
|
||||||
cst["LEO"] = "狮子座"
|
|
||||||
cst["LMI"] = "小狮座"
|
|
||||||
cst["LEP"] = "天兔座"
|
|
||||||
cst["LIB"] = "天秤座"
|
|
||||||
cst["LUP"] = "豺狼座"
|
|
||||||
cst["LYN"] = "天猫座"
|
|
||||||
cst["LYR"] = "天琴座"
|
|
||||||
cst["MEN"] = "山案座"
|
|
||||||
cst["MIC"] = "显微镜座"
|
|
||||||
cst["MON"] = "麒麟座"
|
|
||||||
cst["MUS"] = "苍蝇座"
|
|
||||||
cst["NOR"] = "矩尺座"
|
|
||||||
cst["OCT"] = "南极座"
|
|
||||||
cst["OPH"] = "蛇夫座"
|
|
||||||
cst["ORI"] = "猎户座"
|
|
||||||
cst["PAV"] = "孔雀座"
|
|
||||||
cst["PEG"] = "飞马座"
|
|
||||||
cst["PER"] = "英仙座"
|
|
||||||
cst["PHE"] = "凤凰座"
|
|
||||||
cst["PIC"] = "绘架座"
|
|
||||||
cst["PSC"] = "双鱼座"
|
|
||||||
cst["PSA"] = "南鱼座"
|
|
||||||
cst["PUP"] = "船尾座"
|
|
||||||
cst["PYX"] = "罗盘座"
|
|
||||||
cst["RET"] = "网罟座"
|
|
||||||
cst["SGE"] = "天箭座"
|
|
||||||
cst["SGR"] = "人马座"
|
|
||||||
cst["SCO"] = "天蝎座"
|
|
||||||
cst["SCL"] = "玉夫座"
|
|
||||||
cst["SCT"] = "盾牌座"
|
|
||||||
cst["SER1"] = "巨蛇座"
|
|
||||||
cst["SER2"] = "巨蛇座"
|
|
||||||
cst["SEX"] = "六分仪座"
|
|
||||||
cst["TAU"] = "金牛座"
|
|
||||||
cst["TEL"] = "望远镜座"
|
|
||||||
cst["TRI"] = "三角座"
|
|
||||||
cst["TRA"] = "南三角座"
|
|
||||||
cst["TUC"] = "杜鹃座"
|
|
||||||
cst["UMA"] = "大熊座"
|
|
||||||
cst["UMI"] = "小熊座"
|
|
||||||
cst["VEL"] = "船帆座"
|
|
||||||
cst["VIR"] = "室女座"
|
|
||||||
cst["VOL"] = "飞鱼座"
|
|
||||||
cst["VUL"] = "狐狸座"
|
|
||||||
mystar := IsXZ(ra, dec, jde)
|
|
||||||
return cst[mystar]
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,115 @@
|
|||||||
|
package basic
|
||||||
|
|
||||||
|
import "math"
|
||||||
|
|
||||||
|
var defDeltaTFn = DefaultDeltaTv2
|
||||||
|
|
||||||
|
func DeltaT(date float64, isJDE bool) float64 {
|
||||||
|
return defDeltaTFn(date, isJDE)
|
||||||
|
}
|
||||||
|
|
||||||
|
func SetDeltaTFn(fn func(float64, bool) float64) {
|
||||||
|
if fn != nil {
|
||||||
|
defDeltaTFn = fn
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetDeltaTFn() func(float64, bool) float64 {
|
||||||
|
return defDeltaTFn
|
||||||
|
}
|
||||||
|
|
||||||
|
func DefaultDeltaTv2(date float64, isJd bool) float64 { //传入年或儒略日,传出为秒
|
||||||
|
if !isJd {
|
||||||
|
date = JDECalc(int(date), int((date-math.Floor(date))*12)+1, (date-math.Floor(date))*365.25+1)
|
||||||
|
}
|
||||||
|
return DeltaTv2(date)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 使用Stephenson等人(2016)和Morrison等人(2021)的拟合和外推公式计算Delta T
|
||||||
|
// http://astro.ukho.gov.uk/nao/lvm/
|
||||||
|
// 2010年后的系数已修改以包含2019年后的数据
|
||||||
|
// 返回Delta T,单位为秒
|
||||||
|
func DeltaTSplineY(y float64) float64 {
|
||||||
|
// 积分lod(平均太阳日偏离86400秒的偏差)方程:
|
||||||
|
// 来自 http://astro.ukho.gov.uk/nao/lvm/:
|
||||||
|
// lod = 1.72 t − 3.5 sin(2*pi*(t+0.75)/14) 单位ms/day,其中 t = (y - 1825)/100
|
||||||
|
// 是从1825年开始的世纪数
|
||||||
|
// 使用 1ms = 1e-3s 和 1儒略年 = 365.25天,
|
||||||
|
// lod = 6.2823e-3 * Delta y - 1.278375*sin(2*pi/14*(Delta y /100 + 0.75) 单位s/year
|
||||||
|
// 其中 Delta y = y - 1825。积分该方程得到
|
||||||
|
// Integrate[lod, y] = 3.14115e-3*(Delta y)^2 + 894.8625/pi*cos(2*pi/14*(Delta y /100 + 0.75)
|
||||||
|
// 单位为秒。积分常数设为0。
|
||||||
|
integratedLod := func(x float64) float64 {
|
||||||
|
u := x - 1825
|
||||||
|
return 3.14115e-3*u*u + 284.8435805251424*math.Cos(0.4487989505128276*(0.01*u+0.75))
|
||||||
|
}
|
||||||
|
|
||||||
|
if y < -720 {
|
||||||
|
// 使用积分lod + 常数
|
||||||
|
const c = 1.007739546148514
|
||||||
|
return integratedLod(y) + c
|
||||||
|
}
|
||||||
|
if y > 2025 {
|
||||||
|
// 使用积分lod + 常数
|
||||||
|
const c = -150.56787057979514
|
||||||
|
return integratedLod(y) + c
|
||||||
|
}
|
||||||
|
|
||||||
|
// 使用三次样条拟合
|
||||||
|
y0 := []float64{-720, -100, 400, 1000, 1150, 1300, 1500, 1600, 1650, 1720, 1800, 1810, 1820, 1830, 1840, 1850, 1855, 1860, 1865, 1870, 1875, 1880, 1885, 1890, 1895, 1900, 1905, 1910, 1915, 1920, 1925, 1930, 1935, 1940, 1945, 1950, 1953, 1956, 1959, 1962, 1965, 1968, 1971, 1974, 1977, 1980, 1983, 1986, 1989, 1992, 1995, 1998, 2001, 2004, 2007, 2010, 2013, 2016, 2019, 2022}
|
||||||
|
y1 := []float64{-100, 400, 1000, 1150, 1300, 1500, 1600, 1650, 1720, 1800, 1810, 1820, 1830, 1840, 1850, 1855, 1860, 1865, 1870, 1875, 1880, 1885, 1890, 1895, 1900, 1905, 1910, 1915, 1920, 1925, 1930, 1935, 1940, 1945, 1950, 1953, 1956, 1959, 1962, 1965, 1968, 1971, 1974, 1977, 1980, 1983, 1986, 1989, 1992, 1995, 1998, 2001, 2004, 2007, 2010, 2013, 2016, 2019, 2022, 2025}
|
||||||
|
a0 := []float64{20371.848, 11557.668, 6535.116, 1650.393, 1056.647, 681.149, 292.343, 109.127, 43.952, 12.068, 18.367, 15.678, 16.516, 10.804, 7.634, 9.338, 10.357, 9.04, 8.255, 2.371, -1.126, -3.21, -4.388, -3.884, -5.017, -1.977, 4.923, 11.142, 17.479, 21.617, 23.789, 24.418, 24.164, 24.426, 27.05, 28.932, 30.002, 30.76, 32.652, 33.621, 35.093, 37.956, 40.951, 44.244, 47.291, 50.361, 52.936, 54.984, 56.373, 58.453, 60.678, 62.898, 64.083, 64.553, 65.197, 66.061, 66.919, 68.130, 69.250, 69.296}
|
||||||
|
a1 := []float64{-9999.586, -5822.27, -5671.519, -753.21, -459.628, -421.345, -192.841, -78.697, -68.089, 2.507, -3.481, 0.021, -2.157, -6.018, -0.416, 1.642, -0.486, -0.591, -3.456, -5.593, -2.314, -1.893, 0.101, -0.531, 0.134, 5.715, 6.828, 6.33, 5.518, 3.02, 1.333, 0.052, -0.419, 1.645, 2.499, 1.127, 0.737, 1.409, 1.577, 0.868, 2.275, 3.035, 3.157, 3.199, 3.069, 2.878, 2.354, 1.577, 1.648, 2.235, 2.324, 1.804, 0.674, 0.466, 0.804, 0.839, 1.005, 1.348, 0.594, -0.227}
|
||||||
|
a2 := []float64{776.247, 1303.151, -298.291, 184.811, 108.771, 61.953, -6.572, 10.505, 38.333, 41.731, -1.126, 4.629, -6.806, 2.944, 2.658, 0.261, -2.389, 2.284, -5.148, 3.011, 0.269, 0.152, 1.842, -2.474, 3.138, 2.443, -1.329, 0.831, -1.643, -0.856, -0.831, -0.449, -0.022, 2.086, -1.232, 0.22, -0.61, 1.282, -1.115, 0.406, 1.002, -0.242, 0.364, -0.323, 0.193, -0.384, -0.14, -0.637, 0.708, -0.121, 0.21, -0.729, -0.402, 0.194, 0.144, -0.109, 0.275, 0.068, -0.822, 0.001}
|
||||||
|
a3 := []float64{409.16, -503.433, 1085.087, -25.346, -24.641, -29.414, 16.197, 3.018, -2.127, -37.939, 1.918, -3.812, 3.25, -0.096, -0.539, -0.883, 1.558, -2.477, 2.72, -0.914, -0.039, 0.563, -1.438, 1.871, -0.232, -1.257, 0.72, -0.825, 0.262, 0.008, 0.127, 0.142, 0.702, -1.106, 0.614, -0.277, 0.631, -0.799, 0.507, 0.199, -0.414, 0.202, -0.229, 0.172, -0.192, 0.081, -0.165, 0.448, -0.276, 0.11, -0.313, 0.109, 0.199, -0.017, -0.084, 0.128, -0.069, -0.297, 0.274, 0.086}
|
||||||
|
|
||||||
|
n := len(y0)
|
||||||
|
var i int
|
||||||
|
for i = n - 1; i >= 0; i-- {
|
||||||
|
if y >= y0[i] {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
t := (y - y0[i]) / (y1[i] - y0[i])
|
||||||
|
dT := a0[i] + t*(a1[i]+t*(a2[i]+t*a3[i]))
|
||||||
|
return dT
|
||||||
|
}
|
||||||
|
|
||||||
|
func DeltaTv2(jd float64) float64 {
|
||||||
|
if jd > 2461041.5 || jd < 2441317.5 {
|
||||||
|
var y float64
|
||||||
|
if jd >= 2299160.5 {
|
||||||
|
y = (jd-2451544.5)/365.2425 + 2000
|
||||||
|
} else {
|
||||||
|
y = (jd+0.5)/365.25 - 4712
|
||||||
|
}
|
||||||
|
return DeltaTSplineY(y)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 闰秒JD值
|
||||||
|
jdLeaps := []float64{2457754.5, 2457204.5, 2456109.5, 2454832.5,
|
||||||
|
2453736.5, 2451179.5, 2450630.5, 2450083.5,
|
||||||
|
2449534.5, 2449169.5, 2448804.5, 2448257.5,
|
||||||
|
2447892.5, 2447161.5, 2446247.5, 2445516.5,
|
||||||
|
2445151.5, 2444786.5, 2444239.5, 2443874.5,
|
||||||
|
2443509.5, 2443144.5, 2442778.5, 2442413.5,
|
||||||
|
2442048.5, 2441683.5, 2441499.5, 2441133.5}
|
||||||
|
n := len(jdLeaps)
|
||||||
|
deltaTSeconds := 42.184
|
||||||
|
for i := 0; i < n; i++ {
|
||||||
|
if jd > jdLeaps[i] {
|
||||||
|
deltaTSeconds += float64(n - i - 1)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return deltaTSeconds
|
||||||
|
}
|
||||||
|
|
||||||
|
func TD2UT(jde float64, utToTD bool) float64 { // true 世界时转力学时CC,false 力学时转世界时VV
|
||||||
|
deltaTSeconds := DeltaT(jde, true)
|
||||||
|
if utToTD {
|
||||||
|
return jde + deltaTSeconds/3600/24
|
||||||
|
} else {
|
||||||
|
return jde - deltaTSeconds/3600/24
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,206 @@
|
|||||||
|
package basic
|
||||||
|
|
||||||
|
import "math"
|
||||||
|
|
||||||
|
const (
|
||||||
|
angularDiameterAstronomicalUnitKM = 149597870.7
|
||||||
|
angularDiameterArcsecPerRadian = 180.0 * 3600.0 / math.Pi
|
||||||
|
|
||||||
|
sunEquatorialRadiusKM = 695700.0
|
||||||
|
moonEquatorialRadiusKM = 1737.4
|
||||||
|
mercuryEquatorialRadiusKM = 2440.53
|
||||||
|
venusEquatorialRadiusKM = 6051.8
|
||||||
|
marsEquatorialRadiusKM = 3396.19
|
||||||
|
jupiterEquatorialRadiusKM = 71492.0
|
||||||
|
saturnEquatorialRadiusKM = 60268.0
|
||||||
|
uranusEquatorialRadiusKM = 25559.0
|
||||||
|
neptuneEquatorialRadiusKM = 24764.0
|
||||||
|
)
|
||||||
|
|
||||||
|
func angularSemidiameterArcsec(radiusKM, distanceKM float64) float64 {
|
||||||
|
return math.Asin(radiusKM/distanceKM) * angularDiameterArcsecPerRadian
|
||||||
|
}
|
||||||
|
|
||||||
|
func angularSemidiameterFromAU(radiusKM, distanceAU float64) float64 {
|
||||||
|
return angularSemidiameterArcsec(radiusKM, distanceAU*angularDiameterAstronomicalUnitKM)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SunSemidiameter 太阳视半径,单位角秒 / apparent solar semidiameter in arcseconds.
|
||||||
|
func SunSemidiameter(jd float64) float64 {
|
||||||
|
return SunSemidiameterN(jd, -1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SunSemidiameterN 太阳视半径(截断版),单位角秒 / truncated apparent solar semidiameter in arcseconds.
|
||||||
|
func SunSemidiameterN(jd float64, n int) float64 {
|
||||||
|
return angularSemidiameterFromAU(sunEquatorialRadiusKM, EarthAwayN(jd, n))
|
||||||
|
}
|
||||||
|
|
||||||
|
// SunDiameter 太阳视直径,单位角秒 / apparent solar diameter in arcseconds.
|
||||||
|
func SunDiameter(jd float64) float64 {
|
||||||
|
return SunDiameterN(jd, -1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SunDiameterN 太阳视直径(截断版),单位角秒 / truncated apparent solar diameter in arcseconds.
|
||||||
|
func SunDiameterN(jd float64, n int) float64 {
|
||||||
|
return 2 * SunSemidiameterN(jd, n)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MoonSemidiameter 月亮视半径,单位角秒 / apparent lunar semidiameter in arcseconds.
|
||||||
|
func MoonSemidiameter(jd float64) float64 {
|
||||||
|
return MoonSemidiameterN(jd, -1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MoonSemidiameterN 月亮视半径(截断版),单位角秒 / truncated apparent lunar semidiameter in arcseconds.
|
||||||
|
func MoonSemidiameterN(jd float64, n int) float64 {
|
||||||
|
return angularSemidiameterArcsec(moonEquatorialRadiusKM, HMoonAwayN(jd, n))
|
||||||
|
}
|
||||||
|
|
||||||
|
// MoonDiameter 月亮视直径,单位角秒 / apparent lunar diameter in arcseconds.
|
||||||
|
func MoonDiameter(jd float64) float64 {
|
||||||
|
return MoonDiameterN(jd, -1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MoonDiameterN 月亮视直径(截断版),单位角秒 / truncated apparent lunar diameter in arcseconds.
|
||||||
|
func MoonDiameterN(jd float64, n int) float64 {
|
||||||
|
return 2 * MoonSemidiameterN(jd, n)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MercurySemidiameter 水星视半径,单位角秒 / apparent Mercury semidiameter in arcseconds.
|
||||||
|
func MercurySemidiameter(jd float64) float64 {
|
||||||
|
return MercurySemidiameterN(jd, -1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MercurySemidiameterN 水星视半径(截断版),单位角秒 / truncated apparent Mercury semidiameter in arcseconds.
|
||||||
|
func MercurySemidiameterN(jd float64, n int) float64 {
|
||||||
|
return angularSemidiameterFromAU(mercuryEquatorialRadiusKM, EarthMercuryAwayN(jd, n))
|
||||||
|
}
|
||||||
|
|
||||||
|
// MercuryDiameter 水星视直径,单位角秒 / apparent Mercury diameter in arcseconds.
|
||||||
|
func MercuryDiameter(jd float64) float64 {
|
||||||
|
return MercuryDiameterN(jd, -1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MercuryDiameterN 水星视直径(截断版),单位角秒 / truncated apparent Mercury diameter in arcseconds.
|
||||||
|
func MercuryDiameterN(jd float64, n int) float64 {
|
||||||
|
return 2 * MercurySemidiameterN(jd, n)
|
||||||
|
}
|
||||||
|
|
||||||
|
// VenusSemidiameter 金星视半径,单位角秒 / apparent Venus semidiameter in arcseconds.
|
||||||
|
func VenusSemidiameter(jd float64) float64 {
|
||||||
|
return VenusSemidiameterN(jd, -1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// VenusSemidiameterN 金星视半径(截断版),单位角秒 / truncated apparent Venus semidiameter in arcseconds.
|
||||||
|
func VenusSemidiameterN(jd float64, n int) float64 {
|
||||||
|
return angularSemidiameterFromAU(venusEquatorialRadiusKM, EarthVenusAwayN(jd, n))
|
||||||
|
}
|
||||||
|
|
||||||
|
// VenusDiameter 金星视直径,单位角秒 / apparent Venus diameter in arcseconds.
|
||||||
|
func VenusDiameter(jd float64) float64 {
|
||||||
|
return VenusDiameterN(jd, -1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// VenusDiameterN 金星视直径(截断版),单位角秒 / truncated apparent Venus diameter in arcseconds.
|
||||||
|
func VenusDiameterN(jd float64, n int) float64 {
|
||||||
|
return 2 * VenusSemidiameterN(jd, n)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarsSemidiameter 火星视半径,单位角秒 / apparent Mars semidiameter in arcseconds.
|
||||||
|
func MarsSemidiameter(jd float64) float64 {
|
||||||
|
return MarsSemidiameterN(jd, -1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarsSemidiameterN 火星视半径(截断版),单位角秒 / truncated apparent Mars semidiameter in arcseconds.
|
||||||
|
func MarsSemidiameterN(jd float64, n int) float64 {
|
||||||
|
return angularSemidiameterFromAU(marsEquatorialRadiusKM, EarthMarsAwayN(jd, n))
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarsDiameter 火星视直径,单位角秒 / apparent Mars diameter in arcseconds.
|
||||||
|
func MarsDiameter(jd float64) float64 {
|
||||||
|
return MarsDiameterN(jd, -1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarsDiameterN 火星视直径(截断版),单位角秒 / truncated apparent Mars diameter in arcseconds.
|
||||||
|
func MarsDiameterN(jd float64, n int) float64 {
|
||||||
|
return 2 * MarsSemidiameterN(jd, n)
|
||||||
|
}
|
||||||
|
|
||||||
|
// JupiterSemidiameter 木星视半径,单位角秒 / apparent Jupiter semidiameter in arcseconds.
|
||||||
|
func JupiterSemidiameter(jd float64) float64 {
|
||||||
|
return JupiterSemidiameterN(jd, -1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// JupiterSemidiameterN 木星视半径(截断版),单位角秒 / truncated apparent Jupiter semidiameter in arcseconds.
|
||||||
|
func JupiterSemidiameterN(jd float64, n int) float64 {
|
||||||
|
return angularSemidiameterFromAU(jupiterEquatorialRadiusKM, EarthJupiterAwayN(jd, n))
|
||||||
|
}
|
||||||
|
|
||||||
|
// JupiterDiameter 木星视直径,单位角秒 / apparent Jupiter diameter in arcseconds.
|
||||||
|
func JupiterDiameter(jd float64) float64 {
|
||||||
|
return JupiterDiameterN(jd, -1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// JupiterDiameterN 木星视直径(截断版),单位角秒 / truncated apparent Jupiter diameter in arcseconds.
|
||||||
|
func JupiterDiameterN(jd float64, n int) float64 {
|
||||||
|
return 2 * JupiterSemidiameterN(jd, n)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SaturnSemidiameter 土星视半径,单位角秒 / apparent Saturn semidiameter in arcseconds.
|
||||||
|
func SaturnSemidiameter(jd float64) float64 {
|
||||||
|
return SaturnSemidiameterN(jd, -1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SaturnSemidiameterN 土星视半径(截断版),单位角秒 / truncated apparent Saturn semidiameter in arcseconds.
|
||||||
|
func SaturnSemidiameterN(jd float64, n int) float64 {
|
||||||
|
return angularSemidiameterFromAU(saturnEquatorialRadiusKM, EarthSaturnAwayN(jd, n))
|
||||||
|
}
|
||||||
|
|
||||||
|
// SaturnDiameter 土星视直径,单位角秒 / apparent Saturn diameter in arcseconds.
|
||||||
|
func SaturnDiameter(jd float64) float64 {
|
||||||
|
return SaturnDiameterN(jd, -1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SaturnDiameterN 土星视直径(截断版),单位角秒 / truncated apparent Saturn diameter in arcseconds.
|
||||||
|
func SaturnDiameterN(jd float64, n int) float64 {
|
||||||
|
return 2 * SaturnSemidiameterN(jd, n)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UranusSemidiameter 天王星视半径,单位角秒 / apparent Uranus semidiameter in arcseconds.
|
||||||
|
func UranusSemidiameter(jd float64) float64 {
|
||||||
|
return UranusSemidiameterN(jd, -1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UranusSemidiameterN 天王星视半径(截断版),单位角秒 / truncated apparent Uranus semidiameter in arcseconds.
|
||||||
|
func UranusSemidiameterN(jd float64, n int) float64 {
|
||||||
|
return angularSemidiameterFromAU(uranusEquatorialRadiusKM, EarthUranusAwayN(jd, n))
|
||||||
|
}
|
||||||
|
|
||||||
|
// UranusDiameter 天王星视直径,单位角秒 / apparent Uranus diameter in arcseconds.
|
||||||
|
func UranusDiameter(jd float64) float64 {
|
||||||
|
return UranusDiameterN(jd, -1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UranusDiameterN 天王星视直径(截断版),单位角秒 / truncated apparent Uranus diameter in arcseconds.
|
||||||
|
func UranusDiameterN(jd float64, n int) float64 {
|
||||||
|
return 2 * UranusSemidiameterN(jd, n)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NeptuneSemidiameter 海王星视半径,单位角秒 / apparent Neptune semidiameter in arcseconds.
|
||||||
|
func NeptuneSemidiameter(jd float64) float64 {
|
||||||
|
return NeptuneSemidiameterN(jd, -1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NeptuneSemidiameterN 海王星视半径(截断版),单位角秒 / truncated apparent Neptune semidiameter in arcseconds.
|
||||||
|
func NeptuneSemidiameterN(jd float64, n int) float64 {
|
||||||
|
return angularSemidiameterFromAU(neptuneEquatorialRadiusKM, EarthNeptuneAwayN(jd, n))
|
||||||
|
}
|
||||||
|
|
||||||
|
// NeptuneDiameter 海王星视直径,单位角秒 / apparent Neptune diameter in arcseconds.
|
||||||
|
func NeptuneDiameter(jd float64) float64 {
|
||||||
|
return NeptuneDiameterN(jd, -1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NeptuneDiameterN 海王星视直径(截断版),单位角秒 / truncated apparent Neptune diameter in arcseconds.
|
||||||
|
func NeptuneDiameterN(jd float64, n int) float64 {
|
||||||
|
return 2 * NeptuneSemidiameterN(jd, n)
|
||||||
|
}
|
||||||
@@ -0,0 +1,102 @@
|
|||||||
|
package basic
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"math"
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type angularDiameterSample struct {
|
||||||
|
InputUTC string `json:"input_utc"`
|
||||||
|
Values map[string]float64 `json:"values"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAngularDiametersMatchHorizonsBaseline(t *testing.T) {
|
||||||
|
// Baseline is generated from JPL Horizons by scripts/generate_angular_diameter_baseline.sh.
|
||||||
|
data, err := os.ReadFile("testdata/angular_diameter_baseline.json")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("read baseline: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var samples []angularDiameterSample
|
||||||
|
if err := json.Unmarshal(data, &samples); err != nil {
|
||||||
|
t.Fatalf("decode baseline: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
type diameterCase struct {
|
||||||
|
name string
|
||||||
|
diameter func(float64) float64
|
||||||
|
semidiameter func(float64) float64
|
||||||
|
baselineKey string
|
||||||
|
toleranceArcsec float64
|
||||||
|
}
|
||||||
|
|
||||||
|
cases := []diameterCase{
|
||||||
|
{name: "Sun", diameter: SunDiameter, semidiameter: SunSemidiameter, baselineKey: "sun", toleranceArcsec: 0.01},
|
||||||
|
{name: "Moon", diameter: MoonDiameter, semidiameter: MoonSemidiameter, baselineKey: "moon", toleranceArcsec: 0.2},
|
||||||
|
{name: "Mercury", diameter: MercuryDiameter, semidiameter: MercurySemidiameter, baselineKey: "mercury", toleranceArcsec: 0.01},
|
||||||
|
{name: "Venus", diameter: VenusDiameter, semidiameter: VenusSemidiameter, baselineKey: "venus", toleranceArcsec: 0.01},
|
||||||
|
{name: "Mars", diameter: MarsDiameter, semidiameter: MarsSemidiameter, baselineKey: "mars", toleranceArcsec: 0.01},
|
||||||
|
{name: "Jupiter", diameter: JupiterDiameter, semidiameter: JupiterSemidiameter, baselineKey: "jupiter", toleranceArcsec: 0.01},
|
||||||
|
{name: "Saturn", diameter: SaturnDiameter, semidiameter: SaturnSemidiameter, baselineKey: "saturn", toleranceArcsec: 0.01},
|
||||||
|
{name: "Uranus", diameter: UranusDiameter, semidiameter: UranusSemidiameter, baselineKey: "uranus", toleranceArcsec: 0.01},
|
||||||
|
{name: "Neptune", diameter: NeptuneDiameter, semidiameter: NeptuneSemidiameter, baselineKey: "neptune", toleranceArcsec: 0.01},
|
||||||
|
}
|
||||||
|
|
||||||
|
maxDiffs := make(map[string]float64, len(cases))
|
||||||
|
for _, sample := range samples {
|
||||||
|
date, err := time.Parse(time.RFC3339, sample.InputUTC)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("parse sample time %q: %v", sample.InputUTC, err)
|
||||||
|
}
|
||||||
|
jd := TD2UT(Date2JDE(date.UTC()), true)
|
||||||
|
for _, tc := range cases {
|
||||||
|
want := sample.Values[tc.baselineKey]
|
||||||
|
got := tc.diameter(jd)
|
||||||
|
diff := math.Abs(got - want)
|
||||||
|
if diff > maxDiffs[tc.name] {
|
||||||
|
maxDiffs[tc.name] = diff
|
||||||
|
}
|
||||||
|
if math.Abs(got-want) > tc.toleranceArcsec {
|
||||||
|
t.Fatalf("%s diameter mismatch at %s: got %.9f want %.9f tolerance %.9f", tc.name, sample.InputUTC, got, want, tc.toleranceArcsec)
|
||||||
|
}
|
||||||
|
semi := tc.semidiameter(jd)
|
||||||
|
if math.Abs(got-2*semi) > 1e-12 {
|
||||||
|
t.Fatalf("%s diameter/semidiameter mismatch at %s: diameter %.12f semidiameter %.12f", tc.name, sample.InputUTC, got, semi)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range cases {
|
||||||
|
t.Logf("%s diameter max diff: %.6f arcsec", tc.name, maxDiffs[tc.name])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAngularDiameterNFullMatchesDefault(t *testing.T) {
|
||||||
|
jd := TD2UT(Date2JDE(time.Date(2026, 4, 28, 9, 30, 45, 0, time.UTC)), true)
|
||||||
|
|
||||||
|
cases := []struct {
|
||||||
|
name string
|
||||||
|
diameter func(float64) float64
|
||||||
|
diameterN func(float64, int) float64
|
||||||
|
semidiameter func(float64) float64
|
||||||
|
semidiameterN func(float64, int) float64
|
||||||
|
}{
|
||||||
|
{"Sun", SunDiameter, SunDiameterN, SunSemidiameter, SunSemidiameterN},
|
||||||
|
{"Moon", MoonDiameter, MoonDiameterN, MoonSemidiameter, MoonSemidiameterN},
|
||||||
|
{"Mercury", MercuryDiameter, MercuryDiameterN, MercurySemidiameter, MercurySemidiameterN},
|
||||||
|
{"Venus", VenusDiameter, VenusDiameterN, VenusSemidiameter, VenusSemidiameterN},
|
||||||
|
{"Mars", MarsDiameter, MarsDiameterN, MarsSemidiameter, MarsSemidiameterN},
|
||||||
|
{"Jupiter", JupiterDiameter, JupiterDiameterN, JupiterSemidiameter, JupiterSemidiameterN},
|
||||||
|
{"Saturn", SaturnDiameter, SaturnDiameterN, SaturnSemidiameter, SaturnSemidiameterN},
|
||||||
|
{"Uranus", UranusDiameter, UranusDiameterN, UranusSemidiameter, UranusSemidiameterN},
|
||||||
|
{"Neptune", NeptuneDiameter, NeptuneDiameterN, NeptuneSemidiameter, NeptuneSemidiameterN},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range cases {
|
||||||
|
assertSameFloat(t, tc.name+".Diameter", tc.diameter(jd), tc.diameterN(jd, -1))
|
||||||
|
assertSameFloat(t, tc.name+".Semidiameter", tc.semidiameter(jd), tc.semidiameterN(jd, -1))
|
||||||
|
}
|
||||||
|
}
|
||||||
+7
-6
@@ -1,8 +1,9 @@
|
|||||||
package basic
|
package basic
|
||||||
|
|
||||||
import (
|
import (
|
||||||
. "b612.me/astro/tools"
|
|
||||||
"math"
|
"math"
|
||||||
|
|
||||||
|
. "b612.me/astro/tools"
|
||||||
)
|
)
|
||||||
|
|
||||||
//地球常数
|
//地球常数
|
||||||
@@ -20,19 +21,19 @@ func HeightDistance(height float64) float64 {
|
|||||||
|
|
||||||
// HeightDistance 高度(单位:米)与地平线下角度的关系(单位:度)
|
// HeightDistance 高度(单位:米)与地平线下角度的关系(单位:度)
|
||||||
func HeightDegree(height float64) float64 {
|
func HeightDegree(height float64) float64 {
|
||||||
return math.Acos((EARTH_AVERAGE_RADIUS)/(EARTH_AVERAGE_RADIUS+height)) * 180 / math.Pi / 2
|
return math.Acos((EARTH_AVERAGE_RADIUS)/(EARTH_AVERAGE_RADIUS+height)) * 180 / math.Pi
|
||||||
}
|
}
|
||||||
|
|
||||||
// HeightDistanceByLat 不同纬度下高度与地平线距离的关系(单位:米)
|
// HeightDistanceByLat 不同纬度下高度与地平线距离的关系(单位:米)
|
||||||
func HeightDistanceByLat(height, lat float64) float64 {
|
func HeightDistanceByLat(height, lat float64) float64 {
|
||||||
raduis := GeocentricRadius(lat)
|
radius := GeocentricRadius(lat)
|
||||||
return math.Acos((raduis)/(raduis+height)) * raduis
|
return math.Acos((radius)/(radius+height)) * radius
|
||||||
}
|
}
|
||||||
|
|
||||||
// HeightDegreeByLat 不同纬度下高度(单位:米)与地平线下角度的关系(单位:度)
|
// HeightDegreeByLat 不同纬度下高度(单位:米)与地平线下角度的关系(单位:度)
|
||||||
func HeightDegreeByLat(height, lat float64) float64 {
|
func HeightDegreeByLat(height, lat float64) float64 {
|
||||||
raduis := GeocentricRadius(lat)
|
radius := GeocentricRadius(lat)
|
||||||
return math.Acos((raduis)/(raduis+height)) * 180 / math.Pi / 2
|
return math.Acos((radius)/(radius+height)) * 180 / math.Pi
|
||||||
}
|
}
|
||||||
|
|
||||||
// GeocentricRadius 地心直径与纬度的关系
|
// GeocentricRadius 地心直径与纬度的关系
|
||||||
|
|||||||
+26
-5
@@ -1,13 +1,34 @@
|
|||||||
package basic
|
package basic
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"math"
|
"math"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
func Test_EarthFn(t *testing.T) {
|
func TestHeightDegreeMatchesSphericalArc(t *testing.T) {
|
||||||
fmt.Println(HeightDistance(10000))
|
height := 10000.0
|
||||||
//近似算法,差距在接受范围内?
|
got := HeightDegree(height)
|
||||||
fmt.Println(math.Sqrt(((EARTH_AVERAGE_RADIUS)*2 + 10000) * 10000))
|
want := HeightDistance(height) / EARTH_AVERAGE_RADIUS * 180 / math.Pi
|
||||||
|
if math.Abs(got-want) > 1e-12 {
|
||||||
|
t.Fatalf("HeightDegree mismatch: got %.15f want %.15f", got, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHeightDegreeByLatMatchesSphericalArc(t *testing.T) {
|
||||||
|
height := 10000.0
|
||||||
|
lat := 45.0
|
||||||
|
radius := GeocentricRadius(lat)
|
||||||
|
got := HeightDegreeByLat(height, lat)
|
||||||
|
want := HeightDistanceByLat(height, lat) / radius * 180 / math.Pi
|
||||||
|
if math.Abs(got-want) > 1e-12 {
|
||||||
|
t.Fatalf("HeightDegreeByLat mismatch: got %.15f want %.15f", got, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHeightDegreeReferenceValue(t *testing.T) {
|
||||||
|
got := HeightDegree(10000)
|
||||||
|
want := 3.20801665537668
|
||||||
|
if math.Abs(got-want) > 1e-12 {
|
||||||
|
t.Fatalf("HeightDegree(10000) = %.15f want %.15f", got, want)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,151 @@
|
|||||||
|
package basic
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestLunarEclipseDiagramIncludesContacts(t *testing.T) {
|
||||||
|
diagram := LunarEclipseDiagram(JDECalc(2026, 3, 3), LunarEclipseDiagramOptions{StepDays: 10.0 / 1440.0})
|
||||||
|
if diagram.Eclipse.Type != LunarEclipseTotal {
|
||||||
|
t.Fatalf("unexpected eclipse type: got %s want %s", diagram.Eclipse.Type, LunarEclipseTotal)
|
||||||
|
}
|
||||||
|
if diagram.MoonRadius != 1 {
|
||||||
|
t.Fatalf("moon radius mismatch: got %.9f want 1", diagram.MoonRadius)
|
||||||
|
}
|
||||||
|
if !(diagram.PenumbraRadius > diagram.UmbraRadius && diagram.UmbraRadius > diagram.MoonRadius) {
|
||||||
|
t.Fatalf(
|
||||||
|
"unexpected radii: penumbra=%.9f umbra=%.9f moon=%.9f",
|
||||||
|
diagram.PenumbraRadius,
|
||||||
|
diagram.UmbraRadius,
|
||||||
|
diagram.MoonRadius,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
labels := map[string]bool{}
|
||||||
|
for _, point := range diagram.Points {
|
||||||
|
if point.Label != "" {
|
||||||
|
labels[point.Label] = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, label := range []string{"P1", "U1", "U2", "Greatest", "U3", "U4", "P4"} {
|
||||||
|
if !labels[label] {
|
||||||
|
t.Fatalf("missing label %s in diagram points", label)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLocalSolarEclipseDiagramIncludesContacts(t *testing.T) {
|
||||||
|
diagram := LocalSolarEclipseDiagram(
|
||||||
|
TD2UT(Date2JDE(time.Date(2024, 4, 8, 12, 0, 0, 0, time.UTC)), true),
|
||||||
|
-96.7970,
|
||||||
|
32.7767,
|
||||||
|
0,
|
||||||
|
LocalSolarEclipseDiagramOptions{StepDays: 5.0 / 1440.0},
|
||||||
|
)
|
||||||
|
if diagram.Eclipse.Type == SolarEclipseNone {
|
||||||
|
t.Fatalf("expected local solar eclipse")
|
||||||
|
}
|
||||||
|
if len(diagram.Frames) == 0 {
|
||||||
|
t.Fatalf("expected diagram frames")
|
||||||
|
}
|
||||||
|
|
||||||
|
labels := map[string]bool{}
|
||||||
|
for _, frame := range diagram.Frames {
|
||||||
|
if frame.SunRadius != 1 {
|
||||||
|
t.Fatalf("sun radius mismatch: got %.9f want 1", frame.SunRadius)
|
||||||
|
}
|
||||||
|
if !(frame.MoonRadius > 0) {
|
||||||
|
t.Fatalf("invalid moon radius: %.9f", frame.MoonRadius)
|
||||||
|
}
|
||||||
|
if math.IsNaN(frame.MoonX) || math.IsNaN(frame.MoonY) {
|
||||||
|
t.Fatalf("invalid moon position: x=%f y=%f", frame.MoonX, frame.MoonY)
|
||||||
|
}
|
||||||
|
if frame.Label != "" {
|
||||||
|
labels[frame.Label] = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, label := range []string{"C1", "Greatest", "C4"} {
|
||||||
|
if !labels[label] {
|
||||||
|
t.Fatalf("missing label %s in diagram frames", label)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLocalSolarEclipseDiagramTimesKeepCoincidentLabels(t *testing.T) {
|
||||||
|
eclipse := LocalSolarEclipseResult{
|
||||||
|
HasPartial: true,
|
||||||
|
HasCentral: true,
|
||||||
|
PartialStart: 1,
|
||||||
|
GreatestEclipse: 2,
|
||||||
|
CentralStart: 2,
|
||||||
|
CentralEnd: 2,
|
||||||
|
PartialEnd: 3,
|
||||||
|
}
|
||||||
|
times, _ := localSolarEclipseDiagramTimes(eclipse, 0.5)
|
||||||
|
var coincident localSolarEclipseDiagramTime
|
||||||
|
found := false
|
||||||
|
for _, item := range times {
|
||||||
|
if item.jde == 2 {
|
||||||
|
coincident = item
|
||||||
|
found = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !found {
|
||||||
|
t.Fatalf("missing coincident event time")
|
||||||
|
}
|
||||||
|
want := []string{"C2", "Greatest", "C3"}
|
||||||
|
if len(coincident.labels) != len(want) {
|
||||||
|
t.Fatalf("coincident labels = %v, want %v", coincident.labels, want)
|
||||||
|
}
|
||||||
|
for i, label := range want {
|
||||||
|
if coincident.labels[i] != label {
|
||||||
|
t.Fatalf("coincident labels = %v, want %v", coincident.labels, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if got := localSolarEclipseDiagramPrimaryLabel(coincident.labels); got != "Greatest" {
|
||||||
|
t.Fatalf("primary label = %q, want %q", got, "Greatest")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLunarEclipseDiagramTimesKeepCoincidentLabels(t *testing.T) {
|
||||||
|
eclipse := LunarEclipseResult{
|
||||||
|
HasPenumbral: true,
|
||||||
|
HasPartial: true,
|
||||||
|
HasTotal: true,
|
||||||
|
PenumbralStart: 1,
|
||||||
|
PartialStart: 1.5,
|
||||||
|
TotalStart: 2,
|
||||||
|
Maximum: 2,
|
||||||
|
TotalEnd: 2,
|
||||||
|
PartialEnd: 2.5,
|
||||||
|
PenumbralEnd: 3,
|
||||||
|
}
|
||||||
|
times, _ := lunarEclipseDiagramTimes(eclipse, 0.5)
|
||||||
|
var coincident lunarEclipseDiagramTime
|
||||||
|
found := false
|
||||||
|
for _, item := range times {
|
||||||
|
if item.jde == 2 {
|
||||||
|
coincident = item
|
||||||
|
found = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !found {
|
||||||
|
t.Fatalf("missing coincident event time")
|
||||||
|
}
|
||||||
|
want := []string{"U2", "Greatest", "U3"}
|
||||||
|
if len(coincident.labels) != len(want) {
|
||||||
|
t.Fatalf("coincident labels = %v, want %v", coincident.labels, want)
|
||||||
|
}
|
||||||
|
for i, label := range want {
|
||||||
|
if coincident.labels[i] != label {
|
||||||
|
t.Fatalf("coincident labels = %v, want %v", coincident.labels, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if got := lunarEclipseDiagramPrimaryLabel(coincident.labels); got != "Greatest" {
|
||||||
|
t.Fatalf("primary label = %q, want %q", got, "Greatest")
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,79 @@
|
|||||||
|
package basic
|
||||||
|
|
||||||
|
import "math"
|
||||||
|
|
||||||
|
const (
|
||||||
|
exactEventTolerance = 2.0 / 86400.0
|
||||||
|
exactQueryTTToleranceUT = 0.1 / 86400.0
|
||||||
|
)
|
||||||
|
|
||||||
|
func sameEventJD(a, b float64) bool {
|
||||||
|
return math.Abs(a-b) <= exactEventTolerance
|
||||||
|
}
|
||||||
|
|
||||||
|
func sameEventUTQueryTT(eventUT, queryTT float64) bool {
|
||||||
|
return math.Abs(eventUTQueryTTDelta(eventUT, queryTT)) <= exactQueryTTToleranceUT
|
||||||
|
}
|
||||||
|
|
||||||
|
func closestEventUTToQueryTT(queryTT, best float64, candidates ...float64) float64 {
|
||||||
|
bestAbs := math.Abs(eventUTQueryTTDelta(best, queryTT))
|
||||||
|
for _, candidate := range candidates {
|
||||||
|
candidateAbs := math.Abs(eventUTQueryTTDelta(candidate, queryTT))
|
||||||
|
if candidateAbs < bestAbs {
|
||||||
|
best = candidate
|
||||||
|
bestAbs = candidateAbs
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return best
|
||||||
|
}
|
||||||
|
|
||||||
|
type phaseEventSearchFunc func(jde, degree float64, next uint8) float64
|
||||||
|
type simpleEventSearchFunc func(jde float64) float64
|
||||||
|
|
||||||
|
func inclusiveLastPhaseEvent(jde, degree float64, fn phaseEventSearchFunc) float64 {
|
||||||
|
last := fn(jde, degree, 0)
|
||||||
|
next := fn(jde, degree, 1)
|
||||||
|
if eventUTQueryBeforeOrEqual(next, jde) && eventUTQueryAfterOrEqual(next, jde) {
|
||||||
|
return next
|
||||||
|
}
|
||||||
|
if eventUTQueryBeforeOrEqual(last, jde) {
|
||||||
|
return last
|
||||||
|
}
|
||||||
|
return last
|
||||||
|
}
|
||||||
|
|
||||||
|
func inclusiveNextPhaseEvent(jde, degree float64, fn phaseEventSearchFunc) float64 {
|
||||||
|
last := fn(jde, degree, 0)
|
||||||
|
if eventUTQueryBeforeOrEqual(last, jde) && eventUTQueryAfterOrEqual(last, jde) {
|
||||||
|
return last
|
||||||
|
}
|
||||||
|
next := fn(jde, degree, 1)
|
||||||
|
if eventUTQueryAfterOrEqual(next, jde) {
|
||||||
|
return next
|
||||||
|
}
|
||||||
|
return next
|
||||||
|
}
|
||||||
|
|
||||||
|
func inclusiveLastSimpleEvent(jde float64, lastFn, nextFn simpleEventSearchFunc) float64 {
|
||||||
|
last := lastFn(jde)
|
||||||
|
next := nextFn(jde)
|
||||||
|
if eventUTQueryBeforeOrEqual(next, jde) && eventUTQueryAfterOrEqual(next, jde) {
|
||||||
|
return next
|
||||||
|
}
|
||||||
|
if eventUTQueryBeforeOrEqual(last, jde) {
|
||||||
|
return last
|
||||||
|
}
|
||||||
|
return last
|
||||||
|
}
|
||||||
|
|
||||||
|
func inclusiveNextSimpleEvent(jde float64, lastFn, nextFn simpleEventSearchFunc) float64 {
|
||||||
|
last := lastFn(jde)
|
||||||
|
if eventUTQueryBeforeOrEqual(last, jde) && eventUTQueryAfterOrEqual(last, jde) {
|
||||||
|
return last
|
||||||
|
}
|
||||||
|
next := nextFn(jde)
|
||||||
|
if eventUTQueryAfterOrEqual(next, jde) {
|
||||||
|
return next
|
||||||
|
}
|
||||||
|
return next
|
||||||
|
}
|
||||||
@@ -0,0 +1,97 @@
|
|||||||
|
package basic
|
||||||
|
|
||||||
|
import "math"
|
||||||
|
|
||||||
|
func eventFixedScanRefine(seed, halfWindow, step float64, fn func(float64) float64) float64 {
|
||||||
|
start := seed - halfWindow
|
||||||
|
bestJD := start
|
||||||
|
bestAbs := math.Abs(fn(start))
|
||||||
|
samples := int(math.Round((2 * halfWindow) / step))
|
||||||
|
for i := 1; i < samples; i++ {
|
||||||
|
candidateJD := start + float64(i)*step
|
||||||
|
candidateAbs := math.Abs(fn(candidateJD))
|
||||||
|
if candidateAbs < bestAbs {
|
||||||
|
bestAbs = candidateAbs
|
||||||
|
bestJD = candidateJD
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return bestJD
|
||||||
|
}
|
||||||
|
|
||||||
|
func eventZeroBracket(leftJD, leftVal, centerJD, centerVal, rightJD, rightVal float64) (float64, float64, float64, float64, bool) {
|
||||||
|
if leftVal == 0 {
|
||||||
|
return leftJD, leftJD, leftVal, leftVal, true
|
||||||
|
}
|
||||||
|
if centerVal == 0 {
|
||||||
|
return centerJD, centerJD, centerVal, centerVal, true
|
||||||
|
}
|
||||||
|
if rightVal == 0 {
|
||||||
|
return rightJD, rightJD, rightVal, rightVal, true
|
||||||
|
}
|
||||||
|
if leftVal*centerVal < 0 {
|
||||||
|
return leftJD, centerJD, leftVal, centerVal, true
|
||||||
|
}
|
||||||
|
if centerVal*rightVal < 0 {
|
||||||
|
return centerJD, rightJD, centerVal, rightVal, true
|
||||||
|
}
|
||||||
|
if leftVal*rightVal < 0 {
|
||||||
|
return leftJD, rightJD, leftVal, rightVal, true
|
||||||
|
}
|
||||||
|
return 0, 0, 0, 0, false
|
||||||
|
}
|
||||||
|
|
||||||
|
// eventZeroRefine 细化 seed 附近的零点;无可用括号区间时退回固定步长扫描。
|
||||||
|
func eventZeroRefine(seed, halfWindow, step float64, fn func(float64) float64) float64 {
|
||||||
|
leftJD := seed - halfWindow
|
||||||
|
centerJD := seed
|
||||||
|
rightJD := seed + halfWindow
|
||||||
|
leftVal := fn(leftJD)
|
||||||
|
centerVal := fn(centerJD)
|
||||||
|
rightVal := fn(rightJD)
|
||||||
|
|
||||||
|
bestJD := centerJD
|
||||||
|
bestAbs := math.Abs(centerVal)
|
||||||
|
if candidateAbs := math.Abs(leftVal); candidateAbs < bestAbs {
|
||||||
|
bestAbs = candidateAbs
|
||||||
|
bestJD = leftJD
|
||||||
|
}
|
||||||
|
if candidateAbs := math.Abs(rightVal); candidateAbs < bestAbs {
|
||||||
|
bestAbs = candidateAbs
|
||||||
|
bestJD = rightJD
|
||||||
|
}
|
||||||
|
|
||||||
|
bracketLeftJD, bracketRightJD, bracketLeftVal, bracketRightVal, ok := eventZeroBracket(leftJD, leftVal, centerJD, centerVal, rightJD, rightVal)
|
||||||
|
if !ok {
|
||||||
|
return eventFixedScanRefine(seed, halfWindow, step, fn)
|
||||||
|
}
|
||||||
|
if bracketLeftJD == bracketRightJD {
|
||||||
|
return bracketLeftJD
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i < 8; i++ {
|
||||||
|
candidateJD := (bracketLeftJD + bracketRightJD) / 2
|
||||||
|
if bracketRightVal != bracketLeftVal {
|
||||||
|
secantJD := bracketRightJD - bracketRightVal*(bracketRightJD-bracketLeftJD)/(bracketRightVal-bracketLeftVal)
|
||||||
|
if secantJD > bracketLeftJD && secantJD < bracketRightJD {
|
||||||
|
candidateJD = secantJD
|
||||||
|
}
|
||||||
|
}
|
||||||
|
candidateVal := fn(candidateJD)
|
||||||
|
candidateAbs := math.Abs(candidateVal)
|
||||||
|
if candidateAbs < bestAbs {
|
||||||
|
bestAbs = candidateAbs
|
||||||
|
bestJD = candidateJD
|
||||||
|
}
|
||||||
|
if candidateVal == 0 || math.Abs(bracketRightJD-bracketLeftJD) <= step {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if bracketLeftVal*candidateVal < 0 {
|
||||||
|
bracketRightJD = candidateJD
|
||||||
|
bracketRightVal = candidateVal
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
bracketLeftJD = candidateJD
|
||||||
|
bracketLeftVal = candidateVal
|
||||||
|
}
|
||||||
|
return bestJD
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
package basic
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestEventZeroRefineFindsNearbyRoot(t *testing.T) {
|
||||||
|
root := 0.123456789
|
||||||
|
got := eventZeroRefine(root+0.00002, 0.0003, 1e-7, func(x float64) float64 {
|
||||||
|
return x - root
|
||||||
|
})
|
||||||
|
if math.Abs(got-root) > 1e-12 {
|
||||||
|
t.Fatalf("got %.15f want %.15f", got, root)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEventZeroRefineFallsBackToFixedScan(t *testing.T) {
|
||||||
|
got := eventZeroRefine(0.1, 1, 0.1, func(x float64) float64 {
|
||||||
|
return x*x + 1
|
||||||
|
})
|
||||||
|
if math.Abs(got) > 1e-12 {
|
||||||
|
t.Fatalf("got %.15f want 0", got)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,187 @@
|
|||||||
|
package basic
|
||||||
|
|
||||||
|
import "math"
|
||||||
|
|
||||||
|
const (
|
||||||
|
innerEventEpsilon = 0.1 / 86400.0
|
||||||
|
innerEventWindowPadding = 4.0 / 86400.0
|
||||||
|
innerEventMaximizeEpsilon = 4.0 / 86400.0
|
||||||
|
)
|
||||||
|
|
||||||
|
func eventQueryTTAsUT(queryTT float64) float64 {
|
||||||
|
return TD2UT(queryTT, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
func eventUTQueryTTDelta(eventUT, queryTT float64) float64 {
|
||||||
|
return eventUT - eventQueryTTAsUT(queryTT)
|
||||||
|
}
|
||||||
|
|
||||||
|
func eventUTQueryBeforeOrEqual(eventUT, queryTT float64) bool {
|
||||||
|
return eventUTQueryTTDelta(eventUT, queryTT) <= innerEventEpsilon
|
||||||
|
}
|
||||||
|
|
||||||
|
func eventUTQueryAfterOrEqual(eventUT, queryTT float64) bool {
|
||||||
|
return eventUTQueryTTDelta(eventUT, queryTT) >= -innerEventEpsilon
|
||||||
|
}
|
||||||
|
|
||||||
|
func eventUTNextQueryTT(eventUT float64) float64 {
|
||||||
|
return TD2UT(eventUT, true) + 1.0
|
||||||
|
}
|
||||||
|
|
||||||
|
func eventUTLastQueryTT(eventUT float64) float64 {
|
||||||
|
return TD2UT(eventUT, true) - 1.0
|
||||||
|
}
|
||||||
|
|
||||||
|
func innerNextCycleOffset(delta, period float64) float64 {
|
||||||
|
if delta <= 0 {
|
||||||
|
return -delta * period / 360.0
|
||||||
|
}
|
||||||
|
return (360.0 - delta) * period / 360.0
|
||||||
|
}
|
||||||
|
|
||||||
|
func innerLastCycleOffset(delta, period float64) float64 {
|
||||||
|
if delta >= 0 {
|
||||||
|
return delta * period / 360.0
|
||||||
|
}
|
||||||
|
return (360.0 + delta) * period / 360.0
|
||||||
|
}
|
||||||
|
|
||||||
|
func clampFloat64(v, min, max float64) float64 {
|
||||||
|
if v < min {
|
||||||
|
return min
|
||||||
|
}
|
||||||
|
if v > max {
|
||||||
|
return max
|
||||||
|
}
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
func scanWindowForMinAbs(start, end, step float64, fn func(float64) float64) float64 {
|
||||||
|
if end < start {
|
||||||
|
start, end = end, start
|
||||||
|
}
|
||||||
|
if step <= 0 || end == start {
|
||||||
|
return start
|
||||||
|
}
|
||||||
|
bestJD := start
|
||||||
|
bestAbs := math.Abs(fn(start))
|
||||||
|
for jd := start + step; jd < end; jd += step {
|
||||||
|
candidateAbs := math.Abs(fn(jd))
|
||||||
|
if candidateAbs < bestAbs {
|
||||||
|
bestAbs = candidateAbs
|
||||||
|
bestJD = jd
|
||||||
|
}
|
||||||
|
}
|
||||||
|
endAbs := math.Abs(fn(end))
|
||||||
|
if endAbs < bestAbs {
|
||||||
|
return end
|
||||||
|
}
|
||||||
|
return bestJD
|
||||||
|
}
|
||||||
|
|
||||||
|
func scanWindowForMax(start, end, step float64, fn func(float64) float64) float64 {
|
||||||
|
if end < start {
|
||||||
|
start, end = end, start
|
||||||
|
}
|
||||||
|
if step <= 0 || end == start {
|
||||||
|
return start
|
||||||
|
}
|
||||||
|
bestJD := start
|
||||||
|
bestVal := fn(start)
|
||||||
|
for jd := start + step; jd < end; jd += step {
|
||||||
|
candidateVal := fn(jd)
|
||||||
|
if candidateVal > bestVal {
|
||||||
|
bestVal = candidateVal
|
||||||
|
bestJD = jd
|
||||||
|
}
|
||||||
|
}
|
||||||
|
endVal := fn(end)
|
||||||
|
if endVal > bestVal {
|
||||||
|
return end
|
||||||
|
}
|
||||||
|
return bestJD
|
||||||
|
}
|
||||||
|
|
||||||
|
func boundedEventZeroRefine(seed, start, end, halfWindow, step float64, fn func(float64) float64) float64 {
|
||||||
|
if end < start {
|
||||||
|
start, end = end, start
|
||||||
|
}
|
||||||
|
if end <= start {
|
||||||
|
return start
|
||||||
|
}
|
||||||
|
maxHalfWindow := (end - start) / 2
|
||||||
|
if halfWindow > maxHalfWindow {
|
||||||
|
halfWindow = maxHalfWindow
|
||||||
|
}
|
||||||
|
if halfWindow <= 0 {
|
||||||
|
return clampFloat64(seed, start, end)
|
||||||
|
}
|
||||||
|
seed = clampFloat64(seed, start+halfWindow, end-halfWindow)
|
||||||
|
return eventZeroRefine(seed, halfWindow, step, fn)
|
||||||
|
}
|
||||||
|
|
||||||
|
func zeroEventInWindow(start, end, coarseStep, halfWindow, refineStep float64, coarseFn, exactFn func(float64) float64) float64 {
|
||||||
|
if end < start {
|
||||||
|
start, end = end, start
|
||||||
|
}
|
||||||
|
if end <= start {
|
||||||
|
return start
|
||||||
|
}
|
||||||
|
rangeDays := end - start
|
||||||
|
if coarseStep <= 0 || coarseStep > rangeDays {
|
||||||
|
coarseStep = rangeDays / 6.0
|
||||||
|
}
|
||||||
|
if coarseStep < 0.5 {
|
||||||
|
coarseStep = 0.5
|
||||||
|
}
|
||||||
|
if refineStep <= 0 {
|
||||||
|
refineStep = 0.5 / 86400.0
|
||||||
|
}
|
||||||
|
if halfWindow <= 0 {
|
||||||
|
halfWindow = coarseStep
|
||||||
|
}
|
||||||
|
guess := scanWindowForMinAbs(start, end, coarseStep, coarseFn)
|
||||||
|
return boundedEventZeroRefine(guess, start, end, halfWindow, refineStep, exactFn)
|
||||||
|
}
|
||||||
|
|
||||||
|
func maximizeInWindow(start, end, coarseStep float64, coarseFn, exactFn func(float64) float64) float64 {
|
||||||
|
if end < start {
|
||||||
|
start, end = end, start
|
||||||
|
}
|
||||||
|
if end <= start {
|
||||||
|
return start
|
||||||
|
}
|
||||||
|
rangeDays := end - start
|
||||||
|
if coarseStep <= 0 || coarseStep > rangeDays {
|
||||||
|
coarseStep = rangeDays / 6.0
|
||||||
|
}
|
||||||
|
if coarseStep < 0.5 {
|
||||||
|
coarseStep = 0.5
|
||||||
|
}
|
||||||
|
guess := scanWindowForMax(start, end, coarseStep, coarseFn)
|
||||||
|
left := clampFloat64(guess-coarseStep, start, end)
|
||||||
|
right := clampFloat64(guess+coarseStep, start, end)
|
||||||
|
if right-left <= innerEventMaximizeEpsilon {
|
||||||
|
return guess
|
||||||
|
}
|
||||||
|
for i := 0; i < 20; i++ {
|
||||||
|
third := (right - left) / 3.0
|
||||||
|
leftThird := left + third
|
||||||
|
rightThird := right - third
|
||||||
|
if exactFn(leftThird) <= exactFn(rightThird) {
|
||||||
|
left = leftThird
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
right = rightThird
|
||||||
|
}
|
||||||
|
bestJD := guess
|
||||||
|
bestVal := exactFn(bestJD)
|
||||||
|
for _, jd := range []float64{left, (left + right) / 2.0, right} {
|
||||||
|
candidateVal := exactFn(jd)
|
||||||
|
if candidateVal > bestVal {
|
||||||
|
bestVal = candidateVal
|
||||||
|
bestJD = jd
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return bestJD
|
||||||
|
}
|
||||||
@@ -0,0 +1,105 @@
|
|||||||
|
package basic
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestInnerPlanetExactEventBoundaryIncludesCurrent(t *testing.T) {
|
||||||
|
cases := []struct {
|
||||||
|
name string
|
||||||
|
seed float64
|
||||||
|
lastFn func(float64) float64
|
||||||
|
nextFn func(float64) float64
|
||||||
|
}{
|
||||||
|
{name: "MercuryConjunction", seed: NextMercuryConjunction(ttjdUTC(2026, 1, 1, 0, 0, 0)), lastFn: LastMercuryConjunction, nextFn: NextMercuryConjunction},
|
||||||
|
{name: "MercuryInferior", seed: NextMercuryInferiorConjunctionInclusive(ttjdUTC(2026, 1, 1, 0, 0, 0)), lastFn: LastMercuryInferiorConjunctionInclusive, nextFn: NextMercuryInferiorConjunctionInclusive},
|
||||||
|
{name: "MercurySuperior", seed: NextMercurySuperiorConjunctionInclusive(ttjdUTC(2026, 1, 1, 0, 0, 0)), lastFn: LastMercurySuperiorConjunctionInclusive, nextFn: NextMercurySuperiorConjunctionInclusive},
|
||||||
|
{name: "MercuryRetrograde", seed: NextMercuryRetrogradeInclusive(ttjdUTC(2026, 1, 1, 0, 0, 0)), lastFn: LastMercuryRetrogradeInclusive, nextFn: NextMercuryRetrogradeInclusive},
|
||||||
|
{name: "MercuryP2R", seed: NextMercuryProgradeToRetrogradeInclusive(ttjdUTC(2026, 1, 1, 0, 0, 0)), lastFn: LastMercuryProgradeToRetrogradeInclusive, nextFn: NextMercuryProgradeToRetrogradeInclusive},
|
||||||
|
{name: "MercuryR2P", seed: NextMercuryRetrogradeToProgradeInclusive(ttjdUTC(2026, 1, 1, 0, 0, 0)), lastFn: LastMercuryRetrogradeToProgradeInclusive, nextFn: NextMercuryRetrogradeToProgradeInclusive},
|
||||||
|
{name: "MercuryGreatestElongation", seed: NextMercuryGreatestElongationInclusive(ttjdUTC(2026, 1, 1, 0, 0, 0)), lastFn: LastMercuryGreatestElongationInclusive, nextFn: NextMercuryGreatestElongationInclusive},
|
||||||
|
{name: "MercuryEastElongation", seed: NextMercuryGreatestElongationEastInclusive(ttjdUTC(2026, 1, 1, 0, 0, 0)), lastFn: LastMercuryGreatestElongationEastInclusive, nextFn: NextMercuryGreatestElongationEastInclusive},
|
||||||
|
{name: "MercuryWestElongation", seed: NextMercuryGreatestElongationWestInclusive(ttjdUTC(2026, 1, 1, 0, 0, 0)), lastFn: LastMercuryGreatestElongationWestInclusive, nextFn: NextMercuryGreatestElongationWestInclusive},
|
||||||
|
{name: "VenusConjunction", seed: NextVenusConjunction(ttjdUTC(2026, 1, 1, 0, 0, 0)), lastFn: LastVenusConjunction, nextFn: NextVenusConjunction},
|
||||||
|
{name: "VenusInferior", seed: NextVenusInferiorConjunctionInclusive(ttjdUTC(2026, 1, 1, 0, 0, 0)), lastFn: LastVenusInferiorConjunctionInclusive, nextFn: NextVenusInferiorConjunctionInclusive},
|
||||||
|
{name: "VenusSuperior", seed: NextVenusSuperiorConjunctionInclusive(ttjdUTC(2026, 1, 1, 0, 0, 0)), lastFn: LastVenusSuperiorConjunctionInclusive, nextFn: NextVenusSuperiorConjunctionInclusive},
|
||||||
|
{name: "VenusRetrograde", seed: NextVenusRetrogradeInclusive(ttjdUTC(2026, 1, 1, 0, 0, 0)), lastFn: LastVenusRetrogradeInclusive, nextFn: NextVenusRetrogradeInclusive},
|
||||||
|
{name: "VenusP2R", seed: NextVenusProgradeToRetrogradeInclusive(ttjdUTC(2026, 1, 1, 0, 0, 0)), lastFn: LastVenusProgradeToRetrogradeInclusive, nextFn: NextVenusProgradeToRetrogradeInclusive},
|
||||||
|
{name: "VenusR2P", seed: NextVenusRetrogradeToProgradeInclusive(ttjdUTC(2026, 1, 1, 0, 0, 0)), lastFn: LastVenusRetrogradeToProgradeInclusive, nextFn: NextVenusRetrogradeToProgradeInclusive},
|
||||||
|
{name: "VenusGreatestElongation", seed: NextVenusGreatestElongationInclusive(ttjdUTC(2026, 1, 1, 0, 0, 0)), lastFn: LastVenusGreatestElongationInclusive, nextFn: NextVenusGreatestElongationInclusive},
|
||||||
|
{name: "VenusEastElongation", seed: NextVenusGreatestElongationEastInclusive(ttjdUTC(2026, 1, 1, 0, 0, 0)), lastFn: LastVenusGreatestElongationEastInclusive, nextFn: NextVenusGreatestElongationEastInclusive},
|
||||||
|
{name: "VenusWestElongation", seed: NextVenusGreatestElongationWestInclusive(ttjdUTC(2026, 1, 1, 0, 0, 0)), lastFn: LastVenusGreatestElongationWestInclusive, nextFn: NextVenusGreatestElongationWestInclusive},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range cases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
queryTT := TD2UT(tc.seed, true)
|
||||||
|
last := tc.lastFn(queryTT)
|
||||||
|
next := tc.nextFn(queryTT)
|
||||||
|
if !sameEventJD(last, tc.seed) {
|
||||||
|
t.Fatalf("last exact boundary mismatch: got %.12f want %.12f", last, tc.seed)
|
||||||
|
}
|
||||||
|
if !sameEventJD(next, tc.seed) {
|
||||||
|
t.Fatalf("next exact boundary mismatch: got %.12f want %.12f", next, tc.seed)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestInnerPlanetNextEventAdvancesPastReturnedEvent(t *testing.T) {
|
||||||
|
cases := []struct {
|
||||||
|
name string
|
||||||
|
seed float64
|
||||||
|
next func(float64) float64
|
||||||
|
}{
|
||||||
|
{name: "MercuryConjunction", seed: ttjdUTC(2026, 5, 1, 0, 0, 0), next: NextMercuryConjunction},
|
||||||
|
{name: "MercuryInferior", seed: ttjdUTC(2026, 5, 1, 0, 0, 0), next: NextMercuryInferiorConjunction},
|
||||||
|
{name: "MercuryP2R", seed: ttjdUTC(2026, 5, 1, 0, 0, 0), next: NextMercuryProgradeToRetrograde},
|
||||||
|
{name: "MercuryEastElongation", seed: ttjdUTC(2026, 5, 1, 0, 0, 0), next: NextMercuryGreatestElongationEast},
|
||||||
|
{name: "VenusConjunction", seed: ttjdUTC(2026, 5, 1, 0, 0, 0), next: NextVenusConjunction},
|
||||||
|
{name: "VenusWestElongation", seed: ttjdUTC(2026, 5, 1, 0, 0, 0), next: NextVenusGreatestElongationWest},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range cases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
first := tc.next(tc.seed)
|
||||||
|
query := TD2UT(Date2JDE(JDE2DateByZone(first, time.UTC, false).Add(time.Second)), true)
|
||||||
|
next := tc.next(query)
|
||||||
|
if !eventUTQueryAfterOrEqual(next, query) {
|
||||||
|
t.Fatalf("next should be after query: first=%.12f query=%.12f next=%.12f", first, query, next)
|
||||||
|
}
|
||||||
|
if sameEventJD(next, first) {
|
||||||
|
t.Fatalf("next should advance past first event: first=%.12f next=%.12f", first, next)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestInnerPlanetTypedConjunctionExactBoundaryIncludesCurrent(t *testing.T) {
|
||||||
|
cases := []struct {
|
||||||
|
name string
|
||||||
|
seed float64
|
||||||
|
next func(float64) float64
|
||||||
|
last func(float64) float64
|
||||||
|
}{
|
||||||
|
{name: "MercuryInferior", seed: NextMercuryInferiorConjunction(ttjdUTC(2026, 1, 1, 0, 0, 0)), next: NextMercuryInferiorConjunction, last: LastMercuryInferiorConjunction},
|
||||||
|
{name: "MercurySuperior", seed: NextMercurySuperiorConjunction(ttjdUTC(2026, 1, 1, 0, 0, 0)), next: NextMercurySuperiorConjunction, last: LastMercurySuperiorConjunction},
|
||||||
|
{name: "VenusInferior", seed: NextVenusInferiorConjunction(ttjdUTC(2026, 1, 1, 0, 0, 0)), next: NextVenusInferiorConjunction, last: LastVenusInferiorConjunction},
|
||||||
|
{name: "VenusSuperior", seed: NextVenusSuperiorConjunction(ttjdUTC(2026, 1, 1, 0, 0, 0)), next: NextVenusSuperiorConjunction, last: LastVenusSuperiorConjunction},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range cases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
queryTT := TD2UT(tc.seed, true)
|
||||||
|
last := tc.last(queryTT)
|
||||||
|
next := tc.next(queryTT)
|
||||||
|
if !sameEventJD(last, tc.seed) {
|
||||||
|
t.Fatalf("last exact boundary mismatch: got %.12f want %.12f", last, tc.seed)
|
||||||
|
}
|
||||||
|
if !sameEventJD(next, tc.seed) {
|
||||||
|
t.Fatalf("next exact boundary mismatch: got %.12f want %.12f", next, tc.seed)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,165 @@
|
|||||||
|
package basic
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type innerBaselineFile struct {
|
||||||
|
Events []innerBaselineEvent `json:"events"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type innerBaselineEvent struct {
|
||||||
|
Planet string `json:"planet"`
|
||||||
|
Kind string `json:"kind"`
|
||||||
|
NAOJHintJST string `json:"naoj_hint_jst"`
|
||||||
|
Precision string `json:"precision"`
|
||||||
|
CandidateJST string `json:"candidate_jst"`
|
||||||
|
VerifiedJST string `json:"verified_jst"`
|
||||||
|
CandidateSource string `json:"candidate_source"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadInnerBaseline(t *testing.T) innerBaselineFile {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
paths := [][]string{
|
||||||
|
{
|
||||||
|
"testdata/jpl_inner_event_baseline.json",
|
||||||
|
"basic/testdata/jpl_inner_event_baseline.json",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"testdata/jpl_inner_event_baseline_21c_sample.json",
|
||||||
|
"basic/testdata/jpl_inner_event_baseline_21c_sample.json",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"testdata/jpl_inner_event_baseline_20c_sample.json",
|
||||||
|
"basic/testdata/jpl_inner_event_baseline_20c_sample.json",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"testdata/jpl_inner_event_baseline_22c_sample.json",
|
||||||
|
"basic/testdata/jpl_inner_event_baseline_22c_sample.json",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
var merged innerBaselineFile
|
||||||
|
for index, candidates := range paths {
|
||||||
|
var (
|
||||||
|
data []byte
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
for _, path := range candidates {
|
||||||
|
data, err = os.ReadFile(path)
|
||||||
|
if err == nil {
|
||||||
|
var baseline innerBaselineFile
|
||||||
|
if err := json.Unmarshal(data, &baseline); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
merged.Events = append(merged.Events, baseline.Events...)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err != nil && index == 0 {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(merged.Events) == 0 {
|
||||||
|
t.Fatal("empty inner baseline file")
|
||||||
|
}
|
||||||
|
return merged
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseInnerBaselineTime(t *testing.T, value string) time.Time {
|
||||||
|
t.Helper()
|
||||||
|
loc := time.FixedZone("JST", 9*3600)
|
||||||
|
layouts := []string{
|
||||||
|
"2006-01-02 15:04:05 MST",
|
||||||
|
"2006-01-02 15:04 MST",
|
||||||
|
"2006-01-02 15:04:05",
|
||||||
|
"2006-01-02 15:04",
|
||||||
|
}
|
||||||
|
var err error
|
||||||
|
for _, layout := range layouts {
|
||||||
|
when, parseErr := time.ParseInLocation(layout, value, loc)
|
||||||
|
if parseErr == nil {
|
||||||
|
return when
|
||||||
|
}
|
||||||
|
err = parseErr
|
||||||
|
}
|
||||||
|
t.Fatalf("parse baseline time %q: %v", value, err)
|
||||||
|
return time.Time{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func innerBaselineTolerance(event innerBaselineEvent) time.Duration {
|
||||||
|
switch event.Kind {
|
||||||
|
case "IC", "SC", "P2R", "R2P":
|
||||||
|
return 2 * time.Minute
|
||||||
|
case "GEE", "GEW":
|
||||||
|
return 90 * time.Minute
|
||||||
|
default:
|
||||||
|
return 2 * time.Minute
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func innerEventFuncs(t *testing.T, event innerBaselineEvent) (func(float64) float64, func(float64) float64) {
|
||||||
|
t.Helper()
|
||||||
|
switch event.Planet + ":" + event.Kind {
|
||||||
|
case "Mercury:IC":
|
||||||
|
return LastMercuryInferiorConjunctionInclusive, NextMercuryInferiorConjunctionInclusive
|
||||||
|
case "Mercury:SC":
|
||||||
|
return LastMercurySuperiorConjunctionInclusive, NextMercurySuperiorConjunctionInclusive
|
||||||
|
case "Mercury:P2R":
|
||||||
|
return LastMercuryProgradeToRetrogradeInclusive, NextMercuryProgradeToRetrogradeInclusive
|
||||||
|
case "Mercury:R2P":
|
||||||
|
return LastMercuryRetrogradeToProgradeInclusive, NextMercuryRetrogradeToProgradeInclusive
|
||||||
|
case "Mercury:GEE":
|
||||||
|
return LastMercuryGreatestElongationEastInclusive, NextMercuryGreatestElongationEastInclusive
|
||||||
|
case "Mercury:GEW":
|
||||||
|
return LastMercuryGreatestElongationWestInclusive, NextMercuryGreatestElongationWestInclusive
|
||||||
|
case "Venus:IC":
|
||||||
|
return LastVenusInferiorConjunctionInclusive, NextVenusInferiorConjunctionInclusive
|
||||||
|
case "Venus:SC":
|
||||||
|
return LastVenusSuperiorConjunctionInclusive, NextVenusSuperiorConjunctionInclusive
|
||||||
|
case "Venus:P2R":
|
||||||
|
return LastVenusProgradeToRetrogradeInclusive, NextVenusProgradeToRetrogradeInclusive
|
||||||
|
case "Venus:R2P":
|
||||||
|
return LastVenusRetrogradeToProgradeInclusive, NextVenusRetrogradeToProgradeInclusive
|
||||||
|
case "Venus:GEE":
|
||||||
|
return LastVenusGreatestElongationEastInclusive, NextVenusGreatestElongationEastInclusive
|
||||||
|
case "Venus:GEW":
|
||||||
|
return LastVenusGreatestElongationWestInclusive, NextVenusGreatestElongationWestInclusive
|
||||||
|
default:
|
||||||
|
t.Fatalf("unsupported event %s:%s", event.Planet, event.Kind)
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func assertInnerBaselineEvent(t *testing.T, event innerBaselineEvent, lastFn, nextFn func(float64) float64) {
|
||||||
|
t.Helper()
|
||||||
|
when := parseInnerBaselineTime(t, event.VerifiedJST)
|
||||||
|
before := when.Add(-24 * time.Hour)
|
||||||
|
after := when.Add(24 * time.Hour)
|
||||||
|
next := JDE2DateByZone(nextFn(toUTJD(before)), when.Location(), false)
|
||||||
|
last := JDE2DateByZone(lastFn(toUTJD(after)), when.Location(), false)
|
||||||
|
tolerance := innerBaselineTolerance(event)
|
||||||
|
|
||||||
|
if diff := next.Sub(when); diff < -tolerance || diff > tolerance {
|
||||||
|
t.Fatalf("%s %s next mismatch: got %s want %s tol=%s hint=%s candidate=%s via=%s", event.Planet, event.Kind, next, when, tolerance, event.NAOJHintJST, event.CandidateJST, event.CandidateSource)
|
||||||
|
}
|
||||||
|
if diff := last.Sub(when); diff < -tolerance || diff > tolerance {
|
||||||
|
t.Fatalf("%s %s last mismatch: got %s want %s tol=%s hint=%s candidate=%s via=%s", event.Planet, event.Kind, last, when, tolerance, event.NAOJHintJST, event.CandidateJST, event.CandidateSource)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestInnerPlanetTruthAgainstJPL(t *testing.T) {
|
||||||
|
baseline := loadInnerBaseline(t)
|
||||||
|
for _, event := range baseline.Events {
|
||||||
|
event := event
|
||||||
|
name := strings.Join([]string{event.Planet, event.Kind, event.VerifiedJST}, "_")
|
||||||
|
t.Run(name, func(t *testing.T) {
|
||||||
|
lastFn, nextFn := innerEventFuncs(t, event)
|
||||||
|
assertInnerBaselineEvent(t, event, lastFn, nextFn)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
+181
@@ -0,0 +1,181 @@
|
|||||||
|
package basic
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"math"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
var ErrInvalidCivilDate = errors.New("invalid civil date")
|
||||||
|
var timeNow = time.Now
|
||||||
|
|
||||||
|
// Date2JDE 日期转儒略日
|
||||||
|
func Date2JDE(date time.Time) float64 {
|
||||||
|
day := float64(date.Day()) + float64(date.Hour())/24.0 + float64(date.Minute())/24.0/60.0 + float64(date.Second())/24.0/3600.0 + float64(date.Nanosecond())/1000000000.0/3600.0/24.0
|
||||||
|
return JDECalc(date.Year(), int(date.Month()), day)
|
||||||
|
}
|
||||||
|
|
||||||
|
func ValidateCivilDate(year, month int, day float64) error {
|
||||||
|
if math.IsNaN(day) || math.IsInf(day, 0) {
|
||||||
|
return ErrInvalidCivilDate
|
||||||
|
}
|
||||||
|
if month < 1 || month > 12 {
|
||||||
|
return ErrInvalidCivilDate
|
||||||
|
}
|
||||||
|
if day < 1 {
|
||||||
|
return ErrInvalidCivilDate
|
||||||
|
}
|
||||||
|
dayInt := int(math.Floor(day))
|
||||||
|
if dayInt < 1 || dayInt > daysInCivilMonth(year, month) {
|
||||||
|
return ErrInvalidCivilDate
|
||||||
|
}
|
||||||
|
if isGregorianReformGap(year, month, day) {
|
||||||
|
return ErrInvalidCivilDate
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func isGregorianReformGap(year, month int, day float64) bool {
|
||||||
|
return year == 1582 && month == 10 && day >= 5 && day < 15
|
||||||
|
}
|
||||||
|
|
||||||
|
func daysInCivilMonth(year, month int) int {
|
||||||
|
switch month {
|
||||||
|
case 1, 3, 5, 7, 8, 10, 12:
|
||||||
|
return 31
|
||||||
|
case 4, 6, 9, 11:
|
||||||
|
return 30
|
||||||
|
case 2:
|
||||||
|
if isCivilLeapYear(year, month, 1) {
|
||||||
|
return 29
|
||||||
|
}
|
||||||
|
return 28
|
||||||
|
default:
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func isCivilLeapYear(year, month int, day float64) bool {
|
||||||
|
if year < 1582 || (year == 1582 && (month < 10 || (month == 10 && day <= 4))) {
|
||||||
|
return year%4 == 0
|
||||||
|
}
|
||||||
|
if year%400 == 0 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if year%100 == 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return year%4 == 0
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
@name: 儒略日计算
|
||||||
|
@dec: 计算给定时间的儒略日,1582年改力后为格里高利历,之前为儒略历
|
||||||
|
@ 请注意,传入的时间在天文计算中一般为力学时,应当注意和世界时的转化
|
||||||
|
*/
|
||||||
|
func JDECalc(year, month int, day float64) float64 {
|
||||||
|
if err := ValidateCivilDate(year, month, day); err != nil {
|
||||||
|
return math.NaN()
|
||||||
|
}
|
||||||
|
effectiveYear, effectiveMonth, effectiveDay := year, month, int(math.Floor(day))
|
||||||
|
if month == 1 || month == 2 {
|
||||||
|
year--
|
||||||
|
month += 12
|
||||||
|
}
|
||||||
|
var gregorianCorrection int
|
||||||
|
if effectiveYear < 1582 || (effectiveYear == 1582 && (effectiveMonth < 10 || (effectiveMonth == 10 && effectiveDay <= 4))) {
|
||||||
|
gregorianCorrection = 0
|
||||||
|
} else {
|
||||||
|
century := int(year / 100)
|
||||||
|
gregorianCorrection = 2 - century + int(century/4)
|
||||||
|
}
|
||||||
|
return (math.Floor(365.25*(float64(year)+4716.0)) + math.Floor(30.6001*float64(month+1)) + day + float64(gregorianCorrection) - 1524.5)
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
@name: 获得当前儒略日时间:当地世界时,非格林尼治时间
|
||||||
|
*/
|
||||||
|
func GetNowJDE() (nowJDE float64) {
|
||||||
|
now := timeNow()
|
||||||
|
dayFraction := float64(now.Second())/3600.0/24.0 + float64(now.Minute())/60.0/24.0 + float64(now.Hour())/24.0
|
||||||
|
nowJDE = JDECalc(now.Year(), int(now.Month()), float64(now.Day())+dayFraction)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func JDE2Date(jd float64) time.Time {
|
||||||
|
jd = jd + 0.5
|
||||||
|
z := float64(int(jd))
|
||||||
|
f := jd - z
|
||||||
|
var a, b, years, months, days float64
|
||||||
|
if z < 2299161.0 {
|
||||||
|
a = z
|
||||||
|
} else {
|
||||||
|
alpha := math.Floor((z - 1867216.25) / 36524.25)
|
||||||
|
a = z + 1 + alpha - math.Floor(alpha/4)
|
||||||
|
}
|
||||||
|
b = a + 1524
|
||||||
|
c := math.Floor((b - 122.1) / 365.25)
|
||||||
|
d := math.Floor(365.25 * c)
|
||||||
|
e := math.Floor((b - d) / 30.6001)
|
||||||
|
days = b - d - math.Floor(30.6001*e) + f
|
||||||
|
if e < 14 {
|
||||||
|
months = e - 1
|
||||||
|
}
|
||||||
|
if e == 14 || e == 15 {
|
||||||
|
months = e - 13
|
||||||
|
}
|
||||||
|
if months > 2 {
|
||||||
|
years = c - 4716
|
||||||
|
}
|
||||||
|
if months == 1 || months == 2 {
|
||||||
|
years = c - 4715
|
||||||
|
}
|
||||||
|
tms := (days - math.Floor(days)) * 24 * 3600
|
||||||
|
days = math.Floor(days)
|
||||||
|
tz, _ := time.LoadLocation("Local")
|
||||||
|
dates := time.Date(int(years), time.Month(int(months)), int(days), 0, 0, 0, 0, tz)
|
||||||
|
return time.Unix(dates.Unix()+int64(tms), int64((tms-math.Floor(tms))*1000000000))
|
||||||
|
}
|
||||||
|
|
||||||
|
// JDE2DateByZone JDE(儒略日)转日期
|
||||||
|
// jd: 儒略日
|
||||||
|
// tz: 目标时区
|
||||||
|
// byZone: (true: 传入的儒略日视为目标时区当地时间的儒略日,false: 传入的儒略日视为UTC时间的儒略日)
|
||||||
|
// 回参:转换后的日期,时区始终为目标时区
|
||||||
|
func JDE2DateByZone(jd float64, tz *time.Location, byZone bool) time.Time {
|
||||||
|
jd = jd + 0.5
|
||||||
|
z := float64(int(jd))
|
||||||
|
f := jd - z
|
||||||
|
var a, b, years, months, days float64
|
||||||
|
if z < 2299161.0 {
|
||||||
|
a = z
|
||||||
|
} else {
|
||||||
|
alpha := math.Floor((z - 1867216.25) / 36524.25)
|
||||||
|
a = z + 1 + alpha - math.Floor(alpha/4)
|
||||||
|
}
|
||||||
|
b = a + 1524
|
||||||
|
c := math.Floor((b - 122.1) / 365.25)
|
||||||
|
d := math.Floor(365.25 * c)
|
||||||
|
e := math.Floor((b - d) / 30.6001)
|
||||||
|
days = b - d - math.Floor(30.6001*e) + f
|
||||||
|
if e < 14 {
|
||||||
|
months = e - 1
|
||||||
|
}
|
||||||
|
if e == 14 || e == 15 {
|
||||||
|
months = e - 13
|
||||||
|
}
|
||||||
|
if months > 2 {
|
||||||
|
years = c - 4716
|
||||||
|
}
|
||||||
|
if months == 1 || months == 2 {
|
||||||
|
years = c - 4715
|
||||||
|
}
|
||||||
|
tms := (days - math.Floor(days)) * 24 * 3600
|
||||||
|
days = math.Floor(days)
|
||||||
|
var transTz = tz
|
||||||
|
if !byZone {
|
||||||
|
transTz = time.UTC
|
||||||
|
}
|
||||||
|
return time.Date(int(years), time.Month(int(months)), int(days), 0, 0, 0, 0, transTz).
|
||||||
|
Add(time.Duration(int64(1000000000 * tms))).In(tz)
|
||||||
|
}
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
package basic
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestGetNowJDEUsesSingleTimestamp(t *testing.T) {
|
||||||
|
oldTimeNow := timeNow
|
||||||
|
defer func() {
|
||||||
|
timeNow = oldTimeNow
|
||||||
|
}()
|
||||||
|
|
||||||
|
calls := 0
|
||||||
|
first := time.Date(2026, 4, 29, 23, 59, 59, 0, time.FixedZone("CST", 8*3600))
|
||||||
|
second := first.Add(2 * time.Second)
|
||||||
|
timeNow = func() time.Time {
|
||||||
|
calls++
|
||||||
|
if calls == 1 {
|
||||||
|
return first
|
||||||
|
}
|
||||||
|
return second
|
||||||
|
}
|
||||||
|
|
||||||
|
got := GetNowJDE()
|
||||||
|
want := Date2JDE(first)
|
||||||
|
if calls != 1 {
|
||||||
|
t.Fatalf("GetNowJDE should read current time once, got %d calls", calls)
|
||||||
|
}
|
||||||
|
if math.Float64bits(got) != math.Float64bits(want) {
|
||||||
|
t.Fatalf("GetNowJDE mismatch: got %.15f want %.15f", got, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
+102
-341
@@ -7,143 +7,112 @@ import (
|
|||||||
. "b612.me/astro/tools"
|
. "b612.me/astro/tools"
|
||||||
)
|
)
|
||||||
|
|
||||||
func JupiterL(JD float64) float64 {
|
func JupiterL(jd float64) float64 {
|
||||||
return planet.WherePlanet(4, 0, JD)
|
return planet.WherePlanet(4, 0, jd)
|
||||||
}
|
}
|
||||||
|
|
||||||
func JupiterB(JD float64) float64 {
|
func JupiterB(jd float64) float64 {
|
||||||
return planet.WherePlanet(4, 1, JD)
|
return planet.WherePlanet(4, 1, jd)
|
||||||
}
|
}
|
||||||
func JupiterR(JD float64) float64 {
|
func JupiterR(jd float64) float64 {
|
||||||
return planet.WherePlanet(4, 2, JD)
|
return planet.WherePlanet(4, 2, jd)
|
||||||
}
|
}
|
||||||
func AJupiterX(JD float64) float64 {
|
func AJupiterX(jd float64) float64 {
|
||||||
l := JupiterL(JD)
|
l := JupiterL(jd)
|
||||||
b := JupiterB(JD)
|
b := JupiterB(jd)
|
||||||
r := JupiterR(JD)
|
r := JupiterR(jd)
|
||||||
el := planet.WherePlanet(-1, 0, JD)
|
el := planet.WherePlanet(-1, 0, jd)
|
||||||
eb := planet.WherePlanet(-1, 1, JD)
|
eb := planet.WherePlanet(-1, 1, jd)
|
||||||
er := planet.WherePlanet(-1, 2, JD)
|
er := planet.WherePlanet(-1, 2, jd)
|
||||||
x := r*Cos(b)*Cos(l) - er*Cos(eb)*Cos(el)
|
x := r*Cos(b)*Cos(l) - er*Cos(eb)*Cos(el)
|
||||||
return x
|
return x
|
||||||
}
|
}
|
||||||
|
|
||||||
func AJupiterY(JD float64) float64 {
|
func AJupiterY(jd float64) float64 {
|
||||||
|
|
||||||
l := JupiterL(JD)
|
l := JupiterL(jd)
|
||||||
b := JupiterB(JD)
|
b := JupiterB(jd)
|
||||||
r := JupiterR(JD)
|
r := JupiterR(jd)
|
||||||
el := planet.WherePlanet(-1, 0, JD)
|
el := planet.WherePlanet(-1, 0, jd)
|
||||||
eb := planet.WherePlanet(-1, 1, JD)
|
eb := planet.WherePlanet(-1, 1, jd)
|
||||||
er := planet.WherePlanet(-1, 2, JD)
|
er := planet.WherePlanet(-1, 2, jd)
|
||||||
y := r*Cos(b)*Sin(l) - er*Cos(eb)*Sin(el)
|
y := r*Cos(b)*Sin(l) - er*Cos(eb)*Sin(el)
|
||||||
return y
|
return y
|
||||||
}
|
}
|
||||||
func AJupiterZ(JD float64) float64 {
|
func AJupiterZ(jd float64) float64 {
|
||||||
//l := JupiterL(JD)
|
//l := JupiterL(jd)
|
||||||
b := JupiterB(JD)
|
b := JupiterB(jd)
|
||||||
r := JupiterR(JD)
|
r := JupiterR(jd)
|
||||||
// el := planet.WherePlanet(-1, 0, JD)
|
// el := planet.WherePlanet(-1, 0, jd)
|
||||||
eb := planet.WherePlanet(-1, 1, JD)
|
eb := planet.WherePlanet(-1, 1, jd)
|
||||||
er := planet.WherePlanet(-1, 2, JD)
|
er := planet.WherePlanet(-1, 2, jd)
|
||||||
z := r*Sin(b) - er*Sin(eb)
|
z := r*Sin(b) - er*Sin(eb)
|
||||||
return z
|
return z
|
||||||
}
|
}
|
||||||
|
|
||||||
func AJupiterXYZ(JD float64) (float64, float64, float64) {
|
func AJupiterXYZ(jd float64) (float64, float64, float64) {
|
||||||
l := JupiterL(JD)
|
l := JupiterL(jd)
|
||||||
b := JupiterB(JD)
|
b := JupiterB(jd)
|
||||||
r := JupiterR(JD)
|
r := JupiterR(jd)
|
||||||
el := planet.WherePlanet(-1, 0, JD)
|
el := planet.WherePlanet(-1, 0, jd)
|
||||||
eb := planet.WherePlanet(-1, 1, JD)
|
eb := planet.WherePlanet(-1, 1, jd)
|
||||||
er := planet.WherePlanet(-1, 2, JD)
|
er := planet.WherePlanet(-1, 2, jd)
|
||||||
x := r*Cos(b)*Cos(l) - er*Cos(eb)*Cos(el)
|
x := r*Cos(b)*Cos(l) - er*Cos(eb)*Cos(el)
|
||||||
y := r*Cos(b)*Sin(l) - er*Cos(eb)*Sin(el)
|
y := r*Cos(b)*Sin(l) - er*Cos(eb)*Sin(el)
|
||||||
z := r*Sin(b) - er*Sin(eb)
|
z := r*Sin(b) - er*Sin(eb)
|
||||||
return x, y, z
|
return x, y, z
|
||||||
}
|
}
|
||||||
|
|
||||||
func JupiterApparentRa(JD float64) float64 {
|
func JupiterApparentRa(jd float64) float64 {
|
||||||
lo, bo := JupiterApparentLoBo(JD)
|
lo, bo := JupiterApparentLoBo(jd)
|
||||||
sita := Sita(JD)
|
eps := TrueObliquity(jd)
|
||||||
ra := math.Atan2((Sin(lo)*Cos(sita) - Tan(bo)*Sin(sita)), Cos(lo))
|
ra := math.Atan2((Sin(lo)*Cos(eps) - Tan(bo)*Sin(eps)), Cos(lo))
|
||||||
ra = ra * 180 / math.Pi
|
ra = ra * 180 / math.Pi
|
||||||
return Limit360(ra)
|
return Limit360(ra)
|
||||||
}
|
}
|
||||||
func JupiterApparentDec(JD float64) float64 {
|
func JupiterApparentDec(jd float64) float64 {
|
||||||
lo, bo := JupiterApparentLoBo(JD)
|
lo, bo := JupiterApparentLoBo(jd)
|
||||||
sita := Sita(JD)
|
eps := TrueObliquity(jd)
|
||||||
dec := ArcSin(Sin(bo)*Cos(sita) + Cos(bo)*Sin(sita)*Sin(lo))
|
dec := ArcSin(Sin(bo)*Cos(eps) + Cos(bo)*Sin(eps)*Sin(lo))
|
||||||
return dec
|
return dec
|
||||||
}
|
}
|
||||||
|
|
||||||
func JupiterApparentRaDec(JD float64) (float64, float64) {
|
func JupiterApparentRaDec(jd float64) (float64, float64) {
|
||||||
lo, bo := JupiterApparentLoBo(JD)
|
lo, bo := JupiterApparentLoBo(jd)
|
||||||
sita := Sita(JD)
|
eps := TrueObliquity(jd)
|
||||||
ra := math.Atan2((Sin(lo)*Cos(sita) - Tan(bo)*Sin(sita)), Cos(lo))
|
ra := math.Atan2((Sin(lo)*Cos(eps) - Tan(bo)*Sin(eps)), Cos(lo))
|
||||||
ra = ra * 180 / math.Pi
|
ra = ra * 180 / math.Pi
|
||||||
dec := ArcSin(Sin(bo)*Cos(sita) + Cos(bo)*Sin(sita)*Sin(lo))
|
dec := ArcSin(Sin(bo)*Cos(eps) + Cos(bo)*Sin(eps)*Sin(lo))
|
||||||
return Limit360(ra), dec
|
return Limit360(ra), dec
|
||||||
}
|
}
|
||||||
|
|
||||||
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 += HJZD(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+=HJZD(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 += HJZD(JD)
|
|
||||||
return lo, bo
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func JupiterMag(JD float64) float64 {
|
func JupiterMag(jd float64) float64 {
|
||||||
AwaySun := JupiterR(JD)
|
sunDistance := JupiterR(jd)
|
||||||
AwayEarth := EarthJupiterAway(JD)
|
earthDistance := EarthJupiterAway(jd)
|
||||||
Away := planet.WherePlanet(-1, 2, JD)
|
earthSunDistance := planet.WherePlanet(-1, 2, jd)
|
||||||
i := (AwaySun*AwaySun + AwayEarth*AwayEarth - Away*Away) / (2 * AwaySun * AwayEarth)
|
i := (sunDistance*sunDistance + earthDistance*earthDistance - earthSunDistance*earthSunDistance) / (2 * sunDistance * earthDistance)
|
||||||
i = ArcCos(i)
|
i = ArcCos(i)
|
||||||
Mag := -9.40 + 5*math.Log10(AwaySun*AwayEarth) + 0.0005*i
|
mag := -9.40 + 5*math.Log10(sunDistance*earthDistance) + 0.0005*i
|
||||||
return FloatRound(Mag, 2)
|
return FloatRound(mag, 2)
|
||||||
}
|
}
|
||||||
|
|
||||||
func JupiterHeight(jde, lon, lat, timezone float64) float64 {
|
func JupiterHeight(jde, lon, lat, timezone float64) float64 {
|
||||||
@@ -153,10 +122,10 @@ func JupiterHeight(jde, lon, lat, timezone float64) float64 {
|
|||||||
ra, dec := JupiterApparentRaDec(TD2UT(utcJde, true))
|
ra, dec := JupiterApparentRaDec(TD2UT(utcJde, true))
|
||||||
st := Limit360(ApparentSiderealTime(utcJde)*15 + lon)
|
st := Limit360(ApparentSiderealTime(utcJde)*15 + lon)
|
||||||
// 计算时角
|
// 计算时角
|
||||||
H := Limit360(st - ra)
|
hourAngle := Limit360(st - ra)
|
||||||
// 高度角、时角与天球座标三角转换公式
|
// 高度角、时角与天球座标三角转换公式
|
||||||
// sin(h)=sin(lat)*sin(dec)+cos(dec)*cos(lat)*cos(H)
|
// sin(h)=sin(lat)*sin(dec)+cos(dec)*cos(lat)*cos(hourAngle)
|
||||||
sinHeight := Sin(lat)*Sin(dec) + Cos(dec)*Cos(lat)*Cos(H)
|
sinHeight := Sin(lat)*Sin(dec) + Cos(dec)*Cos(lat)*Cos(hourAngle)
|
||||||
return ArcSin(sinHeight)
|
return ArcSin(sinHeight)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -167,271 +136,63 @@ func JupiterAzimuth(jde, lon, lat, timezone float64) float64 {
|
|||||||
ra, dec := JupiterApparentRaDec(TD2UT(utcJde, true))
|
ra, dec := JupiterApparentRaDec(TD2UT(utcJde, true))
|
||||||
st := Limit360(ApparentSiderealTime(utcJde)*15 + lon)
|
st := Limit360(ApparentSiderealTime(utcJde)*15 + lon)
|
||||||
// 计算时角
|
// 计算时角
|
||||||
H := Limit360(st - ra)
|
hourAngle := Limit360(st - ra)
|
||||||
// 三角转换公式
|
// 三角转换公式
|
||||||
tanAzimuth := Sin(H) / (Cos(H)*Sin(lat) - Tan(dec)*Cos(lat))
|
tanAzimuth := Sin(hourAngle) / (Cos(hourAngle)*Sin(lat) - Tan(dec)*Cos(lat))
|
||||||
Azimuth := ArcTan(tanAzimuth)
|
azimuth := ArcTan(tanAzimuth)
|
||||||
if Azimuth < 0 {
|
if azimuth < 0 {
|
||||||
if H/15 < 12 {
|
if hourAngle/15 < 12 {
|
||||||
return Azimuth + 360
|
return azimuth + 360
|
||||||
}
|
}
|
||||||
return Azimuth + 180
|
return azimuth + 180
|
||||||
}
|
}
|
||||||
if H/15 < 12 {
|
if hourAngle/15 < 12 {
|
||||||
return Azimuth + 180
|
return azimuth + 180
|
||||||
}
|
}
|
||||||
return Azimuth
|
return azimuth
|
||||||
}
|
}
|
||||||
|
|
||||||
func JupiterHourAngle(JD, Lon, TZ float64) float64 {
|
func JupiterHourAngle(jd, lon, timezone float64) float64 {
|
||||||
startime := Limit360(ApparentSiderealTime(JD-TZ/24)*15 + Lon)
|
siderealLongitude := Limit360(ApparentSiderealTime(jd-timezone/24)*15 + lon)
|
||||||
timeangle := startime - JupiterApparentRa(TD2UT(JD-TZ/24.0, true))
|
hourAngle := siderealLongitude - JupiterApparentRa(TD2UT(jd-timezone/24.0, true))
|
||||||
if timeangle < 0 {
|
if hourAngle < 0 {
|
||||||
timeangle += 360
|
hourAngle += 360
|
||||||
}
|
}
|
||||||
return timeangle
|
return hourAngle
|
||||||
}
|
}
|
||||||
|
|
||||||
func JupiterCulminationTime(jde, lon, timezone float64) float64 {
|
func JupiterCulminationTime(jde, lon, timezone float64) float64 {
|
||||||
//jde 世界时,非力学时,当地时区 0时,无需转换力学时
|
//jde 世界时,非力学时,当地时区 0时,无需转换力学时
|
||||||
//ra,dec 瞬时天球座标,非J2000等时间天球坐标
|
//ra,dec 瞬时天球座标,非J2000等时间天球坐标
|
||||||
jde = math.Floor(jde) + 0.5
|
jde = math.Floor(jde) + 0.5
|
||||||
JD1 := jde + Limit360(360-JupiterHourAngle(jde, lon, timezone))/15.0/24.0*0.99726851851851851851
|
estimateJD := jde + Limit360(360-JupiterHourAngle(jde, lon, timezone))/15.0/24.0*0.99726851851851851851
|
||||||
limitHA := func(jde, lon, timezone float64) float64 {
|
normalizedHourAngle := func(jde, lon, timezone float64) float64 {
|
||||||
ha := JupiterHourAngle(jde, lon, timezone)
|
currentHourAngle := JupiterHourAngle(jde, lon, timezone)
|
||||||
if ha < 180 {
|
if currentHourAngle < 180 {
|
||||||
ha += 360
|
currentHourAngle += 360
|
||||||
}
|
}
|
||||||
return ha
|
return currentHourAngle
|
||||||
}
|
}
|
||||||
for {
|
for {
|
||||||
JD0 := JD1
|
prevJD := estimateJD
|
||||||
stDegree := limitHA(JD0, lon, timezone) - 360
|
hourAngleDelta := normalizedHourAngle(prevJD, lon, timezone) - 360
|
||||||
stDegreep := (limitHA(JD0+0.000005, lon, timezone) - limitHA(JD0-0.000005, lon, timezone)) / 0.00001
|
hourAngleSlope := (normalizedHourAngle(prevJD+0.000005, lon, timezone) - normalizedHourAngle(prevJD-0.000005, lon, timezone)) / 0.00001
|
||||||
JD1 = JD0 - stDegree/stDegreep
|
estimateJD = prevJD - hourAngleDelta/hourAngleSlope
|
||||||
if math.Abs(JD1-JD0) <= 0.00001 {
|
if math.Abs(estimateJD-prevJD) <= 0.00001 {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return JD1
|
return estimateJD
|
||||||
}
|
}
|
||||||
|
|
||||||
func JupiterRiseTime(JD, Lon, Lat, TZ, ZS, HEI float64) float64 {
|
func JupiterRiseTime(jd, lon, lat, timezone, aeroCorrection, observerHeight float64) (float64, error) {
|
||||||
return jupiterRiseDown(JD, Lon, Lat, TZ, ZS, HEI, true)
|
return jupiterRiseDown(jd, lon, lat, timezone, aeroCorrection, observerHeight, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
func JupiterDownTime(JD, Lon, Lat, TZ, ZS, HEI float64) float64 {
|
func JupiterSetTime(jd, lon, lat, timezone, aeroCorrection, observerHeight float64) (float64, error) {
|
||||||
return jupiterRiseDown(JD, Lon, Lat, TZ, ZS, HEI, false)
|
return jupiterRiseDown(jd, lon, lat, timezone, aeroCorrection, observerHeight, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
func jupiterRiseDown(JD, Lon, Lat, TZ, ZS, HEI float64, isRise bool) float64 {
|
func jupiterRiseDown(jd, lon, lat, timezone, aeroCorrection, observerHeight float64, isRise bool) (float64, error) {
|
||||||
var An float64
|
return planetRiseDown(jd, lon, lat, timezone, aeroCorrection, observerHeight, isRise, JupiterCulminationTime, JupiterHeight, JupiterApparentDec)
|
||||||
JD = math.Floor(JD) + 0.5
|
|
||||||
ntz := math.Round(Lon / 15)
|
|
||||||
if ZS != 0 {
|
|
||||||
An = -0.8333
|
|
||||||
}
|
|
||||||
An = An - HeightDegreeByLat(HEI, Lat)
|
|
||||||
tztime := JupiterCulminationTime(JD, Lon, ntz)
|
|
||||||
if JupiterHeight(tztime, Lon, Lat, ntz) < An {
|
|
||||||
return -2 //极夜
|
|
||||||
}
|
|
||||||
if JupiterHeight(tztime-0.5, Lon, Lat, ntz) > An {
|
|
||||||
return -1 //极昼
|
|
||||||
}
|
|
||||||
dec := HSunApparentDec(TD2UT(tztime-ntz/24, true))
|
|
||||||
//(sin(ho)-sin(φ)*sin(δ2))/(cos(φ)*cos(δ2))
|
|
||||||
tmp := (Sin(An) - Sin(dec)*Sin(Lat)) / (Cos(dec) * Cos(Lat))
|
|
||||||
var rise float64
|
|
||||||
if math.Abs(tmp) <= 1 {
|
|
||||||
rzsc := ArcCos(tmp) / 15
|
|
||||||
if isRise {
|
|
||||||
rise = tztime - rzsc/24 - 25.0/24.0/60.0
|
|
||||||
} else {
|
|
||||||
rise = tztime + rzsc/24 - 25.0/24.0/60.0
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
rise = tztime
|
|
||||||
i := 0
|
|
||||||
//TODO:使用二分法计算
|
|
||||||
for JupiterHeight(rise, Lon, Lat, ntz) > An {
|
|
||||||
i++
|
|
||||||
if isRise {
|
|
||||||
rise -= 15.0 / 60.0 / 24.0
|
|
||||||
} else {
|
|
||||||
rise += 15.0 / 60.0 / 24.0
|
|
||||||
}
|
|
||||||
if i > 48 {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
JD1 := rise
|
|
||||||
for {
|
|
||||||
JD0 := JD1
|
|
||||||
stDegree := JupiterHeight(JD0, Lon, Lat, ntz) - An
|
|
||||||
stDegreep := (JupiterHeight(JD0+0.000005, Lon, Lat, ntz) - JupiterHeight(JD0-0.000005, Lon, Lat, ntz)) / 0.00001
|
|
||||||
JD1 = JD0 - stDegree/stDegreep
|
|
||||||
if math.Abs(JD1-JD0) <= 0.00001 {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return JD1 - ntz/24 + TZ/24
|
|
||||||
}
|
|
||||||
|
|
||||||
// Pos
|
|
||||||
|
|
||||||
const JUPITER_S_PERIOD = 1 / ((1 / 365.256363004) - (1 / 4332.59))
|
|
||||||
|
|
||||||
func jupiterConjunction(jde, degree float64, next uint8) float64 {
|
|
||||||
//0=last 1=next
|
|
||||||
decSub := func(jde float64, degree float64, filter bool) float64 {
|
|
||||||
sub := Limit360(Limit360(JupiterApparentLo(jde)-HSunApparentLo(jde)) - degree)
|
|
||||||
if filter {
|
|
||||||
if sub > 180 {
|
|
||||||
sub -= 360
|
|
||||||
}
|
|
||||||
if sub < -180 {
|
|
||||||
sub += 360
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return sub
|
|
||||||
}
|
|
||||||
dayCost := JUPITER_S_PERIOD / 360
|
|
||||||
nowSub := decSub(jde, degree, false)
|
|
||||||
if next == 0 {
|
|
||||||
jde -= (360 - nowSub) * dayCost
|
|
||||||
} else {
|
|
||||||
jde += dayCost * nowSub
|
|
||||||
}
|
|
||||||
JD1 := jde
|
|
||||||
for {
|
|
||||||
JD0 := JD1
|
|
||||||
stDegree := decSub(JD0, degree, true)
|
|
||||||
stDegreep := (decSub(JD0+0.000005, degree, true) - decSub(JD0-0.000005, degree, true)) / 0.00001
|
|
||||||
JD1 = JD0 - stDegree/stDegreep
|
|
||||||
if math.Abs(JD1-JD0) <= 0.00001 {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return TD2UT(JD1, false)
|
|
||||||
}
|
|
||||||
|
|
||||||
func LastJupiterConjunction(jde float64) float64 {
|
|
||||||
return jupiterConjunction(jde, 0, 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
func NextJupiterConjunction(jde float64) float64 {
|
|
||||||
return jupiterConjunction(jde, 0, 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
func LastJupiterOpposition(jde float64) float64 {
|
|
||||||
return jupiterConjunction(jde, 180, 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
func NextJupiterOpposition(jde float64) float64 {
|
|
||||||
return jupiterConjunction(jde, 180, 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
func NextJupiterEasternQuadrature(jde float64) float64 {
|
|
||||||
return jupiterConjunction(jde, 90, 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
func LastJupiterEasternQuadrature(jde float64) float64 {
|
|
||||||
return jupiterConjunction(jde, 90, 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
func NextJupiterWesternQuadrature(jde float64) float64 {
|
|
||||||
return jupiterConjunction(jde, 270, 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
func LastJupiterWesternQuadrature(jde float64) float64 {
|
|
||||||
return jupiterConjunction(jde, 270, 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
func jupiterRetrograde(jde float64, isLeft bool) float64 {
|
|
||||||
//0=last 1=next
|
|
||||||
decSub := func(jde float64, val float64) float64 {
|
|
||||||
sub := JupiterApparentRa(jde+val) - JupiterApparentRa(jde-val)
|
|
||||||
if sub > 180 {
|
|
||||||
sub -= 360
|
|
||||||
}
|
|
||||||
if sub < -180 {
|
|
||||||
sub += 360
|
|
||||||
}
|
|
||||||
return sub / (2 * val)
|
|
||||||
}
|
|
||||||
jde = NextJupiterOpposition(jde)
|
|
||||||
if isLeft {
|
|
||||||
jde -= 60
|
|
||||||
} else {
|
|
||||||
jde += 60
|
|
||||||
}
|
|
||||||
for {
|
|
||||||
nowSub := decSub(jde, 1.0/86400.0)
|
|
||||||
if math.Abs(nowSub) > 0.55 {
|
|
||||||
jde += 2
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
break
|
|
||||||
}
|
|
||||||
JD1 := jde
|
|
||||||
for {
|
|
||||||
JD0 := JD1
|
|
||||||
stDegree := decSub(JD0, 2.0/86400.0)
|
|
||||||
stDegreep := (decSub(JD0+15.0/86400.0, 2.0/86400.0) - decSub(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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
JD1 = JD1 - 15.0/86400.0
|
|
||||||
min := JD1
|
|
||||||
minRa := 100.0
|
|
||||||
for i := 0.0; i < 60.0; i++ {
|
|
||||||
tmp := decSub(JD1+i*0.5/86400.0, 0.5/86400.0)
|
|
||||||
if math.Abs(tmp) < math.Abs(minRa) {
|
|
||||||
minRa = tmp
|
|
||||||
min = JD1 + i*0.5/86400.0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return TD2UT(min, false)
|
|
||||||
}
|
|
||||||
|
|
||||||
func NextJupiterRetrogradeToPrograde(jde float64) float64 {
|
|
||||||
date := jupiterRetrograde(jde, false)
|
|
||||||
if date < jde {
|
|
||||||
op := NextJupiterOpposition(jde)
|
|
||||||
return jupiterRetrograde(op+10, false)
|
|
||||||
}
|
|
||||||
return date
|
|
||||||
}
|
|
||||||
|
|
||||||
func LastJupiterRetrogradeToPrograde(jde float64) float64 {
|
|
||||||
jde = LastJupiterOpposition(jde) - 10
|
|
||||||
date := jupiterRetrograde(jde, false)
|
|
||||||
if date > jde {
|
|
||||||
op := LastJupiterOpposition(jde)
|
|
||||||
return jupiterRetrograde(op-10, false)
|
|
||||||
}
|
|
||||||
return date
|
|
||||||
}
|
|
||||||
|
|
||||||
func NextJupiterProgradeToRetrograde(jde float64) float64 {
|
|
||||||
date := jupiterRetrograde(jde, true)
|
|
||||||
if date < jde {
|
|
||||||
op := NextJupiterOpposition(jde)
|
|
||||||
return jupiterRetrograde(op+10, true)
|
|
||||||
}
|
|
||||||
return date
|
|
||||||
}
|
|
||||||
|
|
||||||
func LastJupiterProgradeToRetrograde(jde float64) float64 {
|
|
||||||
jde = LastJupiterOpposition(jde) - 10
|
|
||||||
date := jupiterRetrograde(jde, true)
|
|
||||||
if date > jde {
|
|
||||||
op := LastJupiterOpposition(jde)
|
|
||||||
return jupiterRetrograde(op-10, true)
|
|
||||||
}
|
|
||||||
return date
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,207 @@
|
|||||||
|
package basic
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math"
|
||||||
|
|
||||||
|
. "b612.me/astro/tools"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Pos
|
||||||
|
|
||||||
|
const (
|
||||||
|
JUPITER_S_PERIOD = 1 / ((1 / 365.256363004) - (1 / 4332.59))
|
||||||
|
jupiterEventSearchN = 16
|
||||||
|
jupiterPhaseCoarseTolerance = 30.0 / 86400.0
|
||||||
|
)
|
||||||
|
|
||||||
|
func jupiterSunLongitudeDelta(jde, degree float64, filter bool) float64 {
|
||||||
|
sub := Limit360(Limit360(JupiterApparentLo(jde)-HSunApparentLo(jde)) - degree)
|
||||||
|
if filter {
|
||||||
|
if sub > 180 {
|
||||||
|
sub -= 360
|
||||||
|
}
|
||||||
|
if sub < -180 {
|
||||||
|
sub += 360
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return sub
|
||||||
|
}
|
||||||
|
|
||||||
|
func jupiterSunLongitudeDeltaN(jde, degree float64, filter bool, n int) float64 {
|
||||||
|
sub := Limit360(Limit360(JupiterApparentLoN(jde, n)-HSunApparentLoN(jde, n)) - degree)
|
||||||
|
if filter {
|
||||||
|
if sub > 180 {
|
||||||
|
sub -= 360
|
||||||
|
}
|
||||||
|
if sub < -180 {
|
||||||
|
sub += 360
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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 {
|
||||||
|
//0=last 1=next
|
||||||
|
daysPerDegree := JUPITER_S_PERIOD / 360
|
||||||
|
currentDelta := jupiterSunLongitudeDelta(jde, degree, false)
|
||||||
|
if next == 0 {
|
||||||
|
jde -= (360 - currentDelta) * daysPerDegree
|
||||||
|
} else {
|
||||||
|
jde += daysPerDegree * currentDelta
|
||||||
|
}
|
||||||
|
estimateJD := jde
|
||||||
|
for {
|
||||||
|
prevJD := estimateJD
|
||||||
|
longitudeDelta := jupiterSunLongitudeDelta(prevJD, degree, true)
|
||||||
|
longitudeSlope := (jupiterSunLongitudeDelta(prevJD+0.000005, degree, true) - jupiterSunLongitudeDelta(prevJD-0.000005, degree, true)) / 0.00001
|
||||||
|
estimateJD = prevJD - longitudeDelta/longitudeSlope
|
||||||
|
if math.Abs(estimateJD-prevJD) <= 0.00001 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return TD2UT(estimateJD, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
func jupiterConjunction(jde, degree float64, next uint8) float64 {
|
||||||
|
//0=last 1=next
|
||||||
|
daysPerDegree := JUPITER_S_PERIOD / 360
|
||||||
|
currentDelta := jupiterSunLongitudeDelta(jde, degree, false)
|
||||||
|
if next == 0 {
|
||||||
|
jde -= (360 - currentDelta) * daysPerDegree
|
||||||
|
} else {
|
||||||
|
jde += daysPerDegree * currentDelta
|
||||||
|
}
|
||||||
|
estimateJD := jde
|
||||||
|
for {
|
||||||
|
prevJD := estimateJD
|
||||||
|
longitudeDelta := jupiterSunLongitudeDeltaN(prevJD, degree, true, jupiterEventSearchN)
|
||||||
|
longitudeSlope := (jupiterSunLongitudeDeltaN(prevJD+0.000005, degree, true, jupiterEventSearchN) - jupiterSunLongitudeDeltaN(prevJD-0.000005, degree, true, jupiterEventSearchN)) / 0.00001
|
||||||
|
estimateJD = prevJD - longitudeDelta/longitudeSlope
|
||||||
|
if math.Abs(estimateJD-prevJD) <= jupiterPhaseCoarseTolerance {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for {
|
||||||
|
prevJD := estimateJD
|
||||||
|
longitudeDelta := jupiterSunLongitudeDelta(prevJD, degree, true)
|
||||||
|
longitudeSlope := (jupiterSunLongitudeDelta(prevJD+0.000005, degree, true) - jupiterSunLongitudeDelta(prevJD-0.000005, degree, true)) / 0.00001
|
||||||
|
estimateJD = prevJD - longitudeDelta/longitudeSlope
|
||||||
|
if math.Abs(estimateJD-prevJD) <= 0.00001 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return TD2UT(estimateJD, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
func LastJupiterConjunction(jde float64) float64 {
|
||||||
|
return inclusiveLastPhaseEvent(jde, 0, jupiterConjunction)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NextJupiterConjunction(jde float64) float64 {
|
||||||
|
return inclusiveNextPhaseEvent(jde, 0, jupiterConjunction)
|
||||||
|
}
|
||||||
|
|
||||||
|
func LastJupiterOpposition(jde float64) float64 {
|
||||||
|
return inclusiveLastPhaseEvent(jde, 180, jupiterConjunction)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NextJupiterOpposition(jde float64) float64 {
|
||||||
|
return inclusiveNextPhaseEvent(jde, 180, jupiterConjunction)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NextJupiterEasternQuadrature(jde float64) float64 {
|
||||||
|
return inclusiveNextPhaseEvent(jde, 90, jupiterConjunction)
|
||||||
|
}
|
||||||
|
|
||||||
|
func LastJupiterEasternQuadrature(jde float64) float64 {
|
||||||
|
return inclusiveLastPhaseEvent(jde, 90, jupiterConjunction)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NextJupiterWesternQuadrature(jde float64) float64 {
|
||||||
|
return inclusiveNextPhaseEvent(jde, 270, jupiterConjunction)
|
||||||
|
}
|
||||||
|
|
||||||
|
func LastJupiterWesternQuadrature(jde float64) float64 {
|
||||||
|
return inclusiveLastPhaseEvent(jde, 270, jupiterConjunction)
|
||||||
|
}
|
||||||
|
|
||||||
|
func jupiterRetrogradeAroundOpposition(oppositionJD float64, searchBeforeOpposition bool) float64 {
|
||||||
|
oppositionTT := TD2UT(oppositionJD, true)
|
||||||
|
startTT := oppositionTT
|
||||||
|
endTT := oppositionTT
|
||||||
|
if searchBeforeOpposition {
|
||||||
|
easternQuadratureUT := jupiterConjunction(oppositionTT, 90, 0)
|
||||||
|
startTT = TD2UT(easternQuadratureUT, true)
|
||||||
|
} else {
|
||||||
|
westernQuadratureUT := jupiterConjunction(oppositionTT, 270, 1)
|
||||||
|
endTT = TD2UT(westernQuadratureUT, true)
|
||||||
|
}
|
||||||
|
bestJD := zeroEventInWindow(startTT, endTT, 2.0, 2.0, 30.0/86400.0, func(jd float64) float64 {
|
||||||
|
return jupiterRADerivativeN(jd, 1.0/86400.0, jupiterEventSearchN)
|
||||||
|
}, func(jd float64) float64 {
|
||||||
|
return jupiterRADerivative(jd, 0.5/86400.0)
|
||||||
|
})
|
||||||
|
return TD2UT(bestJD, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NextJupiterRetrogradeToPrograde(jde float64) float64 {
|
||||||
|
lastOppositionJD := jupiterConjunctionFull(jde, 180, 0)
|
||||||
|
date := jupiterRetrogradeAroundOpposition(lastOppositionJD, false)
|
||||||
|
if sameEventUTQueryTT(date, jde) || eventUTQueryAfterOrEqual(date, jde) {
|
||||||
|
return date
|
||||||
|
}
|
||||||
|
nextOppositionJD := jupiterConjunctionFull(jde, 180, 1)
|
||||||
|
return jupiterRetrogradeAroundOpposition(nextOppositionJD, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
func LastJupiterRetrogradeToPrograde(jde float64) float64 {
|
||||||
|
lastOppositionJD := jupiterConjunctionFull(jde, 180, 0)
|
||||||
|
date := jupiterRetrogradeAroundOpposition(lastOppositionJD, false)
|
||||||
|
if sameEventUTQueryTT(date, jde) || eventUTQueryBeforeOrEqual(date, jde) {
|
||||||
|
return date
|
||||||
|
}
|
||||||
|
previousOppositionJD := jupiterConjunctionFull(eventUTLastQueryTT(lastOppositionJD), 180, 0)
|
||||||
|
return jupiterRetrogradeAroundOpposition(previousOppositionJD, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NextJupiterProgradeToRetrograde(jde float64) float64 {
|
||||||
|
nextOppositionJD := jupiterConjunctionFull(jde, 180, 1)
|
||||||
|
date := jupiterRetrogradeAroundOpposition(nextOppositionJD, true)
|
||||||
|
if sameEventUTQueryTT(date, jde) || eventUTQueryAfterOrEqual(date, jde) {
|
||||||
|
return date
|
||||||
|
}
|
||||||
|
followingOppositionJD := jupiterConjunctionFull(eventUTNextQueryTT(nextOppositionJD), 180, 1)
|
||||||
|
return jupiterRetrogradeAroundOpposition(followingOppositionJD, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
func LastJupiterProgradeToRetrograde(jde float64) float64 {
|
||||||
|
nextOppositionJD := jupiterConjunctionFull(jde, 180, 1)
|
||||||
|
date := jupiterRetrogradeAroundOpposition(nextOppositionJD, true)
|
||||||
|
if sameEventUTQueryTT(date, jde) || eventUTQueryBeforeOrEqual(date, jde) {
|
||||||
|
return date
|
||||||
|
}
|
||||||
|
lastOppositionJD := jupiterConjunctionFull(jde, 180, 0)
|
||||||
|
return jupiterRetrogradeAroundOpposition(lastOppositionJD, true)
|
||||||
|
}
|
||||||
@@ -0,0 +1,120 @@
|
|||||||
|
package basic
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math"
|
||||||
|
|
||||||
|
"b612.me/astro/planet"
|
||||||
|
. "b612.me/astro/tools"
|
||||||
|
)
|
||||||
|
|
||||||
|
type jupiterPhysicalObservationInfo struct {
|
||||||
|
DS float64
|
||||||
|
DE float64
|
||||||
|
SystemI float64
|
||||||
|
SystemII float64
|
||||||
|
}
|
||||||
|
|
||||||
|
// JupiterCentralMeridianInfo 木星中央经线 / Jupiter central meridians.
|
||||||
|
type JupiterCentralMeridianInfo struct {
|
||||||
|
// SystemI 木星 System I 照亮盘中央经线,单位度,西经为正。
|
||||||
|
SystemI float64
|
||||||
|
// SystemII 木星 System II 照亮盘中央经线,单位度,西经为正。
|
||||||
|
SystemII float64
|
||||||
|
// SystemIII 木星 System III 盘面中央经线,单位度,西经为正。
|
||||||
|
SystemIII float64
|
||||||
|
}
|
||||||
|
|
||||||
|
// JupiterCentralMeridians 木星 System I/II/III 中央经线 / Jupiter System I/II/III central meridians.
|
||||||
|
func JupiterCentralMeridians(jd float64) JupiterCentralMeridianInfo {
|
||||||
|
return JupiterCentralMeridiansN(jd, -1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// JupiterCentralMeridiansN 木星 System I/II/III 中央经线(截断版) / truncated Jupiter System I/II/III central meridians.
|
||||||
|
func JupiterCentralMeridiansN(jd float64, n int) JupiterCentralMeridianInfo {
|
||||||
|
observations := jupiterPhysicalObservationsN(jd, n)
|
||||||
|
physical := JupiterPhysicalN(jd, n)
|
||||||
|
return JupiterCentralMeridianInfo{
|
||||||
|
SystemI: observations.SystemI,
|
||||||
|
SystemII: observations.SystemII,
|
||||||
|
SystemIII: physical.SubEarthLongitude,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// JupiterDSDE 木星 DS/DE 行星中心赤纬 / Jupiter planetocentric declinations of Sun and Earth.
|
||||||
|
func JupiterDSDE(jd float64) (ds, de float64) {
|
||||||
|
return JupiterDSDEN(jd, -1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// JupiterDSDEN 木星 DS/DE 行星中心赤纬(截断版) / truncated Jupiter planetocentric declinations of Sun and Earth.
|
||||||
|
func JupiterDSDEN(jd float64, n int) (ds, de float64) {
|
||||||
|
observations := jupiterPhysicalObservationsN(jd, n)
|
||||||
|
return observations.DS, observations.DE
|
||||||
|
}
|
||||||
|
|
||||||
|
func jupiterPhysicalObservationsN(jd float64, n int) jupiterPhysicalObservationInfo {
|
||||||
|
days := jd - 2433282.5
|
||||||
|
julianCentury := days / 36525.0
|
||||||
|
|
||||||
|
poleRA := (268.0 + 0.1061*julianCentury) * rad
|
||||||
|
poleDec := (64.5 - 0.0164*julianCentury) * rad
|
||||||
|
w1 := (17.71 + 877.90003539*days) * rad
|
||||||
|
w2 := (16.838 + 870.27003539*days) * rad
|
||||||
|
|
||||||
|
earthLon := planet.WherePlanetN(-1, 0, jd, n)
|
||||||
|
earthLat := planet.WherePlanetN(-1, 1, jd, n)
|
||||||
|
earthRadius := planet.WherePlanetN(-1, 2, jd, n)
|
||||||
|
|
||||||
|
delta := 4.0
|
||||||
|
var jupiterLon float64
|
||||||
|
var jupiterLat float64
|
||||||
|
var jupiterRadius float64
|
||||||
|
var x float64
|
||||||
|
var y float64
|
||||||
|
var z float64
|
||||||
|
for i := 0; i < 2; i++ {
|
||||||
|
lightTimeDays := astronomicalUnitLightTimeDays * delta
|
||||||
|
jupiterLon = planet.WherePlanetN(4, 0, jd-lightTimeDays, n)
|
||||||
|
jupiterLat = planet.WherePlanetN(4, 1, jd-lightTimeDays, n)
|
||||||
|
jupiterRadius = planet.WherePlanetN(4, 2, jd-lightTimeDays, n)
|
||||||
|
x = jupiterRadius*Cos(jupiterLat)*Cos(jupiterLon) - earthRadius*Cos(earthLat)*Cos(earthLon)
|
||||||
|
y = jupiterRadius*Cos(jupiterLat)*Sin(jupiterLon) - earthRadius*Cos(earthLat)*Sin(earthLon)
|
||||||
|
z = jupiterRadius*Sin(jupiterLat) - earthRadius*Sin(earthLat)
|
||||||
|
delta = math.Sqrt(x*x + y*y + z*z)
|
||||||
|
}
|
||||||
|
|
||||||
|
meanObliquity := EclipticObliquity(jd, false)
|
||||||
|
sinMeanObliquity, cosMeanObliquity := math.Sincos(meanObliquity)
|
||||||
|
sinJupiterLat, cosJupiterLat := math.Sincos(jupiterLat * rad)
|
||||||
|
sinJupiterLon, cosJupiterLon := math.Sincos(jupiterLon * rad)
|
||||||
|
alphaSun := math.Atan2(cosMeanObliquity*sinJupiterLon-sinMeanObliquity*sinJupiterLat/cosJupiterLat, cosJupiterLon)
|
||||||
|
deltaSun := math.Asin(cosMeanObliquity*sinJupiterLat + sinMeanObliquity*cosJupiterLat*sinJupiterLon)
|
||||||
|
u := y*Cos(meanObliquity) - z*Sin(meanObliquity)
|
||||||
|
v := y*Sin(meanObliquity) + z*Cos(meanObliquity)
|
||||||
|
alpha := math.Atan2(u, x)
|
||||||
|
deltaEarth := math.Atan2(v, math.Hypot(x, u))
|
||||||
|
|
||||||
|
sinPoleDec, cosPoleDec := math.Sincos(poleDec)
|
||||||
|
sinDeltaSun, cosDeltaSun := math.Sincos(deltaSun)
|
||||||
|
ds := math.Asin(-sinPoleDec*sinDeltaSun-cosPoleDec*cosDeltaSun*math.Cos(poleRA-alphaSun)) * deg
|
||||||
|
sinDeltaEarth, cosDeltaEarth := math.Sincos(deltaEarth)
|
||||||
|
sinPoleDeltaRA, cosPoleDeltaRA := math.Sincos(poleRA - alpha)
|
||||||
|
zeta := math.Atan2(
|
||||||
|
sinPoleDec*cosDeltaEarth*cosPoleDeltaRA-sinDeltaEarth*cosPoleDec,
|
||||||
|
cosDeltaEarth*sinPoleDeltaRA,
|
||||||
|
)
|
||||||
|
de := math.Asin(-sinPoleDec*sinDeltaEarth-cosPoleDec*cosDeltaEarth*math.Cos(poleRA-alpha)) * deg
|
||||||
|
|
||||||
|
systemI := w1 - zeta - 5.07033*rad*delta
|
||||||
|
systemII := w2 - zeta - 5.02626*rad*delta
|
||||||
|
phaseCorrection := (2*jupiterRadius*delta + earthRadius*earthRadius - jupiterRadius*jupiterRadius - delta*delta) / (4 * jupiterRadius * delta)
|
||||||
|
if Sin(jupiterLon-earthLon) < 0 {
|
||||||
|
phaseCorrection = -phaseCorrection
|
||||||
|
}
|
||||||
|
|
||||||
|
return jupiterPhysicalObservationInfo{
|
||||||
|
DS: ds,
|
||||||
|
DE: de,
|
||||||
|
SystemI: Limit360((systemI + phaseCorrection) * deg),
|
||||||
|
SystemII: Limit360((systemII + phaseCorrection) * deg),
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,92 @@
|
|||||||
|
package basic
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"math"
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestJupiterCentralMeridiansMeeusExample43A(t *testing.T) {
|
||||||
|
got := JupiterCentralMeridians(2448972.50068)
|
||||||
|
ds, de := JupiterDSDE(2448972.50068)
|
||||||
|
|
||||||
|
assertPlanetPhaseClose(t, "Jupiter.CMI", got.SystemI, 268.06, 0.02)
|
||||||
|
assertPlanetPhaseClose(t, "Jupiter.CMII", got.SystemII, 72.74, 0.02)
|
||||||
|
assertPlanetPhaseClose(t, "Jupiter.DS.Exact", ds, -1.733360091891, 1e-9)
|
||||||
|
assertPlanetPhaseClose(t, "Jupiter.DE.Exact", de, -2.484625891057, 1e-9)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestJupiterCentralMeridianSystemIIIMatchesHorizonsBaseline(t *testing.T) {
|
||||||
|
data, err := os.ReadFile("testdata/planet_physical_baseline.json")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("read baseline: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var samples []planetPhysicalSample
|
||||||
|
if err := json.Unmarshal(data, &samples); err != nil {
|
||||||
|
t.Fatalf("decode baseline: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, sample := range samples {
|
||||||
|
if sample.Body != "jupiter" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
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)
|
||||||
|
got := JupiterCentralMeridians(jd)
|
||||||
|
assertPlanetPhaseClose(t, "Jupiter."+sample.InputUTC+".CMIII", got.SystemIII, sample.SubEarthLongitude, 0.02)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestJupiterCentralMeridiansNFullMatchesDefault(t *testing.T) {
|
||||||
|
jd := TD2UT(Date2JDE(time.Date(2026, 4, 28, 9, 30, 45, 0, time.UTC)), true)
|
||||||
|
got := JupiterCentralMeridians(jd)
|
||||||
|
gotN := JupiterCentralMeridiansN(jd, -1)
|
||||||
|
ds, de := JupiterDSDE(jd)
|
||||||
|
dsN, deN := JupiterDSDEN(jd, -1)
|
||||||
|
if math.Float64bits(got.SystemI) != math.Float64bits(gotN.SystemI) {
|
||||||
|
t.Fatalf("SystemI mismatch: got %.18f want %.18f", got.SystemI, gotN.SystemI)
|
||||||
|
}
|
||||||
|
if math.Float64bits(got.SystemII) != math.Float64bits(gotN.SystemII) {
|
||||||
|
t.Fatalf("SystemII mismatch: got %.18f want %.18f", got.SystemII, gotN.SystemII)
|
||||||
|
}
|
||||||
|
if math.Float64bits(got.SystemIII) != math.Float64bits(gotN.SystemIII) {
|
||||||
|
t.Fatalf("SystemIII mismatch: got %.18f want %.18f", got.SystemIII, gotN.SystemIII)
|
||||||
|
}
|
||||||
|
if math.Float64bits(ds) != math.Float64bits(dsN) {
|
||||||
|
t.Fatalf("DS mismatch: got %.18f want %.18f", ds, dsN)
|
||||||
|
}
|
||||||
|
if math.Float64bits(de) != math.Float64bits(deN) {
|
||||||
|
t.Fatalf("DE mismatch: got %.18f want %.18f", de, deN)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestJupiterCentralMeridianAndDSDESampleSweepFiniteAndInRange(t *testing.T) {
|
||||||
|
dates := []time.Time{
|
||||||
|
time.Date(1900, 1, 1, 0, 0, 0, 0, time.UTC),
|
||||||
|
time.Date(1969, 7, 20, 20, 17, 40, 0, time.UTC),
|
||||||
|
time.Date(2000, 1, 1, 12, 0, 0, 0, time.UTC),
|
||||||
|
time.Date(2026, 4, 28, 9, 30, 45, 0, time.UTC),
|
||||||
|
time.Date(2099, 12, 31, 23, 59, 59, 0, time.UTC),
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, date := range dates {
|
||||||
|
jd := TD2UT(Date2JDE(date.UTC()), true)
|
||||||
|
meridians := JupiterCentralMeridians(jd)
|
||||||
|
ds, de := JupiterDSDE(jd)
|
||||||
|
physical := JupiterPhysical(jd)
|
||||||
|
prefix := date.Format(time.RFC3339)
|
||||||
|
|
||||||
|
assertFiniteRange(t, prefix+".SystemI", meridians.SystemI, 0, 360, true)
|
||||||
|
assertFiniteRange(t, prefix+".SystemII", meridians.SystemII, 0, 360, true)
|
||||||
|
assertFiniteRange(t, prefix+".SystemIII", meridians.SystemIII, 0, 360, true)
|
||||||
|
assertFiniteRange(t, prefix+".DS", ds, -90, 90, false)
|
||||||
|
assertFiniteRange(t, prefix+".DE", de, -90, 90, false)
|
||||||
|
assertSameFloat(t, prefix+".CMIII", meridians.SystemIII, physical.SubEarthLongitude)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,827 @@
|
|||||||
|
package basic
|
||||||
|
|
||||||
|
import "math"
|
||||||
|
|
||||||
|
const (
|
||||||
|
jupiterGalileanContactBracketStepDays = 2.0 / 1440.0
|
||||||
|
jupiterGalileanContactBracketSpanDays = 10.0 / 24.0
|
||||||
|
)
|
||||||
|
|
||||||
|
// JupiterGalileanPhenomenonContactPhase 接触阶段 / contact phase.
|
||||||
|
type JupiterGalileanPhenomenonContactPhase string
|
||||||
|
|
||||||
|
const (
|
||||||
|
// JupiterGalileanDisappearanceContact 初亏/初入接触阶段 / disappearance ingress contact.
|
||||||
|
JupiterGalileanDisappearanceContact JupiterGalileanPhenomenonContactPhase = "disappearance"
|
||||||
|
// JupiterGalileanReappearanceContact 复圆/复出接触阶段 / reappearance egress contact.
|
||||||
|
JupiterGalileanReappearanceContact JupiterGalileanPhenomenonContactPhase = "reappearance"
|
||||||
|
)
|
||||||
|
|
||||||
|
// JupiterGalileanPhenomenonContact 伽利略卫星接触窗口 / Galilean-satellite contact window.
|
||||||
|
//
|
||||||
|
// Start/End 表示有限圆盘或有限影斑开始/结束接触的时刻;ModelCrossing 表示这套连续接触模型下,
|
||||||
|
// 零半径参考点穿越边界的时刻。
|
||||||
|
// Start/End mark the beginning/end of the finite-disk or finite-shadow contact interval.
|
||||||
|
// ModelCrossing is the zero-radius boundary crossing in this continuous contact model.
|
||||||
|
type JupiterGalileanPhenomenonContact struct {
|
||||||
|
Valid bool
|
||||||
|
Phase JupiterGalileanPhenomenonContactPhase
|
||||||
|
|
||||||
|
Start float64
|
||||||
|
ModelCrossing float64
|
||||||
|
End float64
|
||||||
|
}
|
||||||
|
|
||||||
|
// JupiterGalileanPhenomenonContactEvent IMCCE 风格的 D/F 接触事件 / IMCCE-style D/F contact event.
|
||||||
|
//
|
||||||
|
// 与 `JupiterGalileanPhenomenonEvent` 不同,这里返回的是有限圆盘/有限影斑的初亏与复圆接触窗口;
|
||||||
|
// 现有整场事件 API 返回的则是零半径几何模型处于 active 状态的整段区间。
|
||||||
|
// 对 `shadow_transit`,这里按 IMCCE 的影凌语义处理:先用半影/本影边界求出部分相持续时间,
|
||||||
|
// 再把这段持续时间中心放在旧 `shadow_transit` API 的影轴过盘时刻上。
|
||||||
|
// Unlike `JupiterGalileanPhenomenonEvent`, this returns the finite-disk / finite-shadow D/F contact windows.
|
||||||
|
// The existing full-event API returns the whole active interval of the zero-radius geometric model.
|
||||||
|
// For `shadow_transit`, the partial-phase duration comes from penumbra/umbra boundaries,
|
||||||
|
// while the reported D/F time is centered on the shadow-axis limb crossing from the existing full-event model.
|
||||||
|
type JupiterGalileanPhenomenonContactEvent struct {
|
||||||
|
Valid bool
|
||||||
|
Satellite int
|
||||||
|
Type JupiterGalileanPhenomenonType
|
||||||
|
|
||||||
|
Disappearance JupiterGalileanPhenomenonContact
|
||||||
|
Greatest float64
|
||||||
|
Reappearance JupiterGalileanPhenomenonContact
|
||||||
|
|
||||||
|
GreatestPhenomenon JupiterGalileanPhenomenon
|
||||||
|
}
|
||||||
|
|
||||||
|
type jupiterGalileanContactGeometry struct {
|
||||||
|
signedDistance float64
|
||||||
|
effectiveRadius float64
|
||||||
|
}
|
||||||
|
|
||||||
|
// LastJupiterGalileanPhenomenonContactEvent 上一次 IMCCE 风格接触事件 / previous IMCCE-style contact event.
|
||||||
|
func LastJupiterGalileanPhenomenonContactEvent(jd float64, satellite int, phenomenonType JupiterGalileanPhenomenonType) JupiterGalileanPhenomenonContactEvent {
|
||||||
|
return jupiterGalileanPhenomenonContactEventFromEvent(
|
||||||
|
LastJupiterGalileanPhenomenonEvent(jd, satellite, phenomenonType),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NextJupiterGalileanPhenomenonContactEvent 下一次 IMCCE 风格接触事件 / next IMCCE-style contact event.
|
||||||
|
func NextJupiterGalileanPhenomenonContactEvent(jd float64, satellite int, phenomenonType JupiterGalileanPhenomenonType) JupiterGalileanPhenomenonContactEvent {
|
||||||
|
return jupiterGalileanPhenomenonContactEventFromEvent(
|
||||||
|
NextJupiterGalileanPhenomenonEvent(jd, satellite, phenomenonType),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClosestJupiterGalileanPhenomenonContactEvent 最近一次 IMCCE 风格接触事件 / closest IMCCE-style contact event.
|
||||||
|
func ClosestJupiterGalileanPhenomenonContactEvent(jd float64, satellite int, phenomenonType JupiterGalileanPhenomenonType) JupiterGalileanPhenomenonContactEvent {
|
||||||
|
return jupiterGalileanPhenomenonContactEventFromEvent(
|
||||||
|
ClosestJupiterGalileanPhenomenonEvent(jd, satellite, phenomenonType),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func jupiterGalileanPhenomenonContactEventFromEvent(event JupiterGalileanPhenomenonEvent) JupiterGalileanPhenomenonContactEvent {
|
||||||
|
if !event.Valid {
|
||||||
|
return invalidJupiterGalileanPhenomenonContactEvent()
|
||||||
|
}
|
||||||
|
var (
|
||||||
|
disappearance JupiterGalileanPhenomenonContact
|
||||||
|
reappearance JupiterGalileanPhenomenonContact
|
||||||
|
ok bool
|
||||||
|
)
|
||||||
|
if event.Type == JupiterGalileanShadowTransit {
|
||||||
|
disappearance, reappearance, ok = refineJupiterGalileanShadowContactPair(event)
|
||||||
|
} else if event.Type == JupiterGalileanEclipse {
|
||||||
|
disappearance, reappearance, ok = refineJupiterGalileanEclipseContactPair(event)
|
||||||
|
} else {
|
||||||
|
disappearance, reappearance, ok = refineJupiterGalileanContactPair(event.Greatest, event.Satellite, event.Type)
|
||||||
|
}
|
||||||
|
if !ok {
|
||||||
|
return invalidJupiterGalileanPhenomenonContactEvent()
|
||||||
|
}
|
||||||
|
return JupiterGalileanPhenomenonContactEvent{
|
||||||
|
Valid: true,
|
||||||
|
Satellite: event.Satellite,
|
||||||
|
Type: event.Type,
|
||||||
|
Disappearance: disappearance,
|
||||||
|
Greatest: event.Greatest,
|
||||||
|
Reappearance: reappearance,
|
||||||
|
GreatestPhenomenon: event.GreatestPhenomenon,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func refineJupiterGalileanContactPair(
|
||||||
|
greatestJD float64,
|
||||||
|
satellite int,
|
||||||
|
phenomenonType JupiterGalileanPhenomenonType,
|
||||||
|
) (JupiterGalileanPhenomenonContact, JupiterGalileanPhenomenonContact, bool) {
|
||||||
|
signedDistance := func(jd float64) float64 {
|
||||||
|
geometry, ok := jupiterGalileanContactGeometryAt(jd, satellite, phenomenonType)
|
||||||
|
if !ok {
|
||||||
|
return math.NaN()
|
||||||
|
}
|
||||||
|
return geometry.signedDistance
|
||||||
|
}
|
||||||
|
disappearanceStartTarget := func(jd float64) float64 {
|
||||||
|
geometry, ok := jupiterGalileanContactGeometryAt(jd, satellite, phenomenonType)
|
||||||
|
if !ok {
|
||||||
|
return math.NaN()
|
||||||
|
}
|
||||||
|
return geometry.signedDistance - geometry.effectiveRadius
|
||||||
|
}
|
||||||
|
insideTarget := func(jd float64) float64 {
|
||||||
|
geometry, ok := jupiterGalileanContactGeometryAt(jd, satellite, phenomenonType)
|
||||||
|
if !ok {
|
||||||
|
return math.NaN()
|
||||||
|
}
|
||||||
|
return geometry.signedDistance + geometry.effectiveRadius
|
||||||
|
}
|
||||||
|
|
||||||
|
disappearanceModel, ok := refineJupiterGalileanContactRoot(greatestJD, -1, signedDistance)
|
||||||
|
if !ok {
|
||||||
|
return JupiterGalileanPhenomenonContact{}, JupiterGalileanPhenomenonContact{}, false
|
||||||
|
}
|
||||||
|
reappearanceModel, ok := refineJupiterGalileanContactRoot(greatestJD, 1, signedDistance)
|
||||||
|
if !ok || reappearanceModel <= disappearanceModel {
|
||||||
|
return JupiterGalileanPhenomenonContact{}, JupiterGalileanPhenomenonContact{}, false
|
||||||
|
}
|
||||||
|
|
||||||
|
disappearanceStart, ok := refineJupiterGalileanContactRoot(disappearanceModel, -1, disappearanceStartTarget)
|
||||||
|
if !ok {
|
||||||
|
return JupiterGalileanPhenomenonContact{}, JupiterGalileanPhenomenonContact{}, false
|
||||||
|
}
|
||||||
|
disappearanceEnd, ok := refineJupiterGalileanContactRoot(disappearanceModel, 1, insideTarget)
|
||||||
|
if !ok || disappearanceEnd <= disappearanceStart {
|
||||||
|
return JupiterGalileanPhenomenonContact{}, JupiterGalileanPhenomenonContact{}, false
|
||||||
|
}
|
||||||
|
reappearanceStart, ok := refineJupiterGalileanContactRoot(reappearanceModel, -1, insideTarget)
|
||||||
|
if !ok {
|
||||||
|
return JupiterGalileanPhenomenonContact{}, JupiterGalileanPhenomenonContact{}, false
|
||||||
|
}
|
||||||
|
reappearanceEnd, ok := refineJupiterGalileanContactRoot(reappearanceModel, 1, disappearanceStartTarget)
|
||||||
|
if !ok || reappearanceEnd <= reappearanceStart {
|
||||||
|
return JupiterGalileanPhenomenonContact{}, JupiterGalileanPhenomenonContact{}, false
|
||||||
|
}
|
||||||
|
|
||||||
|
return JupiterGalileanPhenomenonContact{
|
||||||
|
Valid: true,
|
||||||
|
Phase: JupiterGalileanDisappearanceContact,
|
||||||
|
Start: disappearanceStart,
|
||||||
|
ModelCrossing: disappearanceModel,
|
||||||
|
End: disappearanceEnd,
|
||||||
|
}, JupiterGalileanPhenomenonContact{
|
||||||
|
Valid: true,
|
||||||
|
Phase: JupiterGalileanReappearanceContact,
|
||||||
|
Start: reappearanceStart,
|
||||||
|
ModelCrossing: reappearanceModel,
|
||||||
|
End: reappearanceEnd,
|
||||||
|
}, true
|
||||||
|
}
|
||||||
|
|
||||||
|
func refineJupiterGalileanEclipseContactPair(
|
||||||
|
event JupiterGalileanPhenomenonEvent,
|
||||||
|
) (JupiterGalileanPhenomenonContact, JupiterGalileanPhenomenonContact, bool) {
|
||||||
|
satelliteRadius := jupiterGalileanSatelliteRadiusJupiterRadii(event.Satellite)
|
||||||
|
if !isFinite(satelliteRadius) || satelliteRadius <= 0 {
|
||||||
|
return JupiterGalileanPhenomenonContact{}, JupiterGalileanPhenomenonContact{}, false
|
||||||
|
}
|
||||||
|
// IMCCE 的 EC.D/EC.F 更接近“目视消失/重现”而不是纯几何接触:
|
||||||
|
// D 相用半影入段近似,F 相用本影/半影出段的中点近似。
|
||||||
|
penumbraOuterTarget := func(jd float64) float64 {
|
||||||
|
signedDistance, ok := jupiterGalileanEclipseSignedDistanceAt(jd, event.Satellite, true)
|
||||||
|
if !ok {
|
||||||
|
return math.NaN()
|
||||||
|
}
|
||||||
|
return signedDistance - satelliteRadius
|
||||||
|
}
|
||||||
|
penumbraInnerTarget := func(jd float64) float64 {
|
||||||
|
signedDistance, ok := jupiterGalileanEclipseSignedDistanceAt(jd, event.Satellite, true)
|
||||||
|
if !ok {
|
||||||
|
return math.NaN()
|
||||||
|
}
|
||||||
|
return signedDistance + satelliteRadius
|
||||||
|
}
|
||||||
|
umbraInnerTarget := func(jd float64) float64 {
|
||||||
|
signedDistance, ok := jupiterGalileanEclipseSignedDistanceAt(jd, event.Satellite, false)
|
||||||
|
if !ok {
|
||||||
|
return math.NaN()
|
||||||
|
}
|
||||||
|
return signedDistance + satelliteRadius
|
||||||
|
}
|
||||||
|
umbraOuterTarget := func(jd float64) float64 {
|
||||||
|
signedDistance, ok := jupiterGalileanEclipseSignedDistanceAt(jd, event.Satellite, false)
|
||||||
|
if !ok {
|
||||||
|
return math.NaN()
|
||||||
|
}
|
||||||
|
return signedDistance - satelliteRadius
|
||||||
|
}
|
||||||
|
|
||||||
|
disappearanceStart, ok := refineJupiterGalileanContactRoot(event.Start, -1, penumbraOuterTarget)
|
||||||
|
if !ok {
|
||||||
|
return JupiterGalileanPhenomenonContact{}, JupiterGalileanPhenomenonContact{}, false
|
||||||
|
}
|
||||||
|
disappearanceEnd, ok := refineJupiterGalileanContactRoot(event.Start, 1, penumbraInnerTarget)
|
||||||
|
if !ok || disappearanceEnd <= disappearanceStart {
|
||||||
|
return JupiterGalileanPhenomenonContact{}, JupiterGalileanPhenomenonContact{}, false
|
||||||
|
}
|
||||||
|
reappearanceUmbraStart, ok := refineJupiterGalileanContactRoot(event.End, -1, umbraInnerTarget)
|
||||||
|
if !ok {
|
||||||
|
return JupiterGalileanPhenomenonContact{}, JupiterGalileanPhenomenonContact{}, false
|
||||||
|
}
|
||||||
|
reappearancePenumbraStart, ok := refineJupiterGalileanContactRoot(event.End, -1, penumbraInnerTarget)
|
||||||
|
if !ok {
|
||||||
|
return JupiterGalileanPhenomenonContact{}, JupiterGalileanPhenomenonContact{}, false
|
||||||
|
}
|
||||||
|
reappearanceStart := (reappearanceUmbraStart + reappearancePenumbraStart) / 2
|
||||||
|
reappearanceUmbraEnd, ok := refineJupiterGalileanContactRoot(event.End, 1, umbraOuterTarget)
|
||||||
|
if !ok {
|
||||||
|
return JupiterGalileanPhenomenonContact{}, JupiterGalileanPhenomenonContact{}, false
|
||||||
|
}
|
||||||
|
reappearancePenumbraEnd, ok := refineJupiterGalileanContactRoot(event.End, 1, penumbraOuterTarget)
|
||||||
|
if !ok {
|
||||||
|
return JupiterGalileanPhenomenonContact{}, JupiterGalileanPhenomenonContact{}, false
|
||||||
|
}
|
||||||
|
reappearanceEnd := (reappearanceUmbraEnd + reappearancePenumbraEnd) / 2
|
||||||
|
if !ok || reappearanceEnd <= reappearanceStart {
|
||||||
|
return JupiterGalileanPhenomenonContact{}, JupiterGalileanPhenomenonContact{}, false
|
||||||
|
}
|
||||||
|
|
||||||
|
return JupiterGalileanPhenomenonContact{
|
||||||
|
Valid: true,
|
||||||
|
Phase: JupiterGalileanDisappearanceContact,
|
||||||
|
Start: disappearanceStart,
|
||||||
|
ModelCrossing: event.Start,
|
||||||
|
End: disappearanceEnd,
|
||||||
|
}, JupiterGalileanPhenomenonContact{
|
||||||
|
Valid: true,
|
||||||
|
Phase: JupiterGalileanReappearanceContact,
|
||||||
|
Start: reappearanceStart,
|
||||||
|
ModelCrossing: event.End,
|
||||||
|
End: reappearanceEnd,
|
||||||
|
}, true
|
||||||
|
}
|
||||||
|
|
||||||
|
func refineJupiterGalileanShadowContactPair(
|
||||||
|
event JupiterGalileanPhenomenonEvent,
|
||||||
|
) (JupiterGalileanPhenomenonContact, JupiterGalileanPhenomenonContact, bool) {
|
||||||
|
penumbraMetric := func(jd float64) float64 {
|
||||||
|
value, ok := jupiterGalileanShadowLimbMetricAt(jd, event.Satellite, true)
|
||||||
|
if !ok {
|
||||||
|
return math.NaN()
|
||||||
|
}
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
umbraMetric := func(jd float64) float64 {
|
||||||
|
value, ok := jupiterGalileanShadowLimbMetricAt(jd, event.Satellite, false)
|
||||||
|
if !ok {
|
||||||
|
return math.NaN()
|
||||||
|
}
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
|
||||||
|
penumbraSeedStartJD, penumbraSeedStartValue, ok := findJupiterGalileanNegativeMetricSeed(event.Start, penumbraMetric)
|
||||||
|
if !ok {
|
||||||
|
return JupiterGalileanPhenomenonContact{}, JupiterGalileanPhenomenonContact{}, false
|
||||||
|
}
|
||||||
|
disappearanceStart, ok := refineJupiterGalileanNegativeWindowRoot(penumbraSeedStartJD, penumbraSeedStartValue, -1, penumbraMetric)
|
||||||
|
if !ok {
|
||||||
|
return JupiterGalileanPhenomenonContact{}, JupiterGalileanPhenomenonContact{}, false
|
||||||
|
}
|
||||||
|
umbraSeedStartJD, umbraSeedStartValue, ok := findJupiterGalileanNegativeMetricSeed(event.Start, umbraMetric)
|
||||||
|
if !ok {
|
||||||
|
return JupiterGalileanPhenomenonContact{}, JupiterGalileanPhenomenonContact{}, false
|
||||||
|
}
|
||||||
|
disappearanceUmbraIn, ok := refineJupiterGalileanNegativeWindowRoot(umbraSeedStartJD, umbraSeedStartValue, 1, umbraMetric)
|
||||||
|
if !ok || disappearanceUmbraIn <= disappearanceStart {
|
||||||
|
return JupiterGalileanPhenomenonContact{}, JupiterGalileanPhenomenonContact{}, false
|
||||||
|
}
|
||||||
|
|
||||||
|
umbraSeedEndJD, umbraSeedEndValue, ok := findJupiterGalileanNegativeMetricSeed(event.End, umbraMetric)
|
||||||
|
if !ok {
|
||||||
|
return JupiterGalileanPhenomenonContact{}, JupiterGalileanPhenomenonContact{}, false
|
||||||
|
}
|
||||||
|
reappearanceUmbraOut, ok := refineJupiterGalileanNegativeWindowRoot(umbraSeedEndJD, umbraSeedEndValue, -1, umbraMetric)
|
||||||
|
if !ok {
|
||||||
|
return JupiterGalileanPhenomenonContact{}, JupiterGalileanPhenomenonContact{}, false
|
||||||
|
}
|
||||||
|
penumbraSeedEndJD, penumbraSeedEndValue, ok := findJupiterGalileanNegativeMetricSeed(event.End, penumbraMetric)
|
||||||
|
if !ok {
|
||||||
|
return JupiterGalileanPhenomenonContact{}, JupiterGalileanPhenomenonContact{}, false
|
||||||
|
}
|
||||||
|
reappearancePenumbraOut, ok := refineJupiterGalileanNegativeWindowRoot(penumbraSeedEndJD, penumbraSeedEndValue, 1, penumbraMetric)
|
||||||
|
if !ok || reappearancePenumbraOut <= reappearanceUmbraOut {
|
||||||
|
return JupiterGalileanPhenomenonContact{}, JupiterGalileanPhenomenonContact{}, false
|
||||||
|
}
|
||||||
|
disappearanceDuration := disappearanceUmbraIn - disappearanceStart
|
||||||
|
reappearanceDuration := reappearancePenumbraOut - reappearanceUmbraOut
|
||||||
|
if disappearanceDuration <= 0 || reappearanceDuration <= 0 {
|
||||||
|
return JupiterGalileanPhenomenonContact{}, JupiterGalileanPhenomenonContact{}, false
|
||||||
|
}
|
||||||
|
disappearanceCenteredStart := event.Start - disappearanceDuration/2
|
||||||
|
disappearanceCenteredEnd := event.Start + disappearanceDuration/2
|
||||||
|
reappearanceCenteredStart := event.End - reappearanceDuration/2
|
||||||
|
reappearanceCenteredEnd := event.End + reappearanceDuration/2
|
||||||
|
return JupiterGalileanPhenomenonContact{
|
||||||
|
Valid: true,
|
||||||
|
Phase: JupiterGalileanDisappearanceContact,
|
||||||
|
Start: disappearanceCenteredStart,
|
||||||
|
ModelCrossing: event.Start,
|
||||||
|
End: disappearanceCenteredEnd,
|
||||||
|
}, JupiterGalileanPhenomenonContact{
|
||||||
|
Valid: true,
|
||||||
|
Phase: JupiterGalileanReappearanceContact,
|
||||||
|
Start: reappearanceCenteredStart,
|
||||||
|
ModelCrossing: event.End,
|
||||||
|
End: reappearanceCenteredEnd,
|
||||||
|
}, true
|
||||||
|
}
|
||||||
|
|
||||||
|
func refineJupiterGalileanNegativeMetricWindow(
|
||||||
|
seedJD float64,
|
||||||
|
metric func(jd float64) float64,
|
||||||
|
) (float64, float64, bool) {
|
||||||
|
activeJD, activeValue, ok := findJupiterGalileanNegativeMetricSeed(seedJD, metric)
|
||||||
|
if !ok {
|
||||||
|
return math.NaN(), math.NaN(), false
|
||||||
|
}
|
||||||
|
start, ok := refineJupiterGalileanNegativeWindowRoot(activeJD, activeValue, -1, metric)
|
||||||
|
if !ok {
|
||||||
|
return math.NaN(), math.NaN(), false
|
||||||
|
}
|
||||||
|
end, ok := refineJupiterGalileanNegativeWindowRoot(activeJD, activeValue, 1, metric)
|
||||||
|
if !ok {
|
||||||
|
return math.NaN(), math.NaN(), false
|
||||||
|
}
|
||||||
|
return start, end, true
|
||||||
|
}
|
||||||
|
|
||||||
|
func findJupiterGalileanNegativeMetricSeed(
|
||||||
|
seedJD float64,
|
||||||
|
metric func(jd float64) float64,
|
||||||
|
) (float64, float64, bool) {
|
||||||
|
value := metric(seedJD)
|
||||||
|
if isFinite(value) && value < 0 {
|
||||||
|
return seedJD, value, true
|
||||||
|
}
|
||||||
|
step := jupiterGalileanContactBracketStepDays
|
||||||
|
maxSteps := int(math.Ceil(jupiterGalileanContactBracketSpanDays / step))
|
||||||
|
for i := 1; i <= maxSteps; i++ {
|
||||||
|
for _, direction := range []float64{-1, 1} {
|
||||||
|
candidateJD := seedJD + direction*step*float64(i)
|
||||||
|
candidateValue := metric(candidateJD)
|
||||||
|
if isFinite(candidateValue) && candidateValue < 0 {
|
||||||
|
return candidateJD, candidateValue, true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return math.NaN(), math.NaN(), false
|
||||||
|
}
|
||||||
|
|
||||||
|
func refineJupiterGalileanNegativeWindowRoot(
|
||||||
|
activeJD, activeValue float64,
|
||||||
|
direction int,
|
||||||
|
metric func(jd float64) float64,
|
||||||
|
) (float64, bool) {
|
||||||
|
currentJD := activeJD
|
||||||
|
currentValue := activeValue
|
||||||
|
step := jupiterGalileanContactBracketStepDays
|
||||||
|
maxSteps := int(math.Ceil(jupiterGalileanContactBracketSpanDays / step))
|
||||||
|
for i := 1; i <= maxSteps; i++ {
|
||||||
|
candidateJD := currentJD + float64(direction)*step
|
||||||
|
candidateValue := metric(candidateJD)
|
||||||
|
if !isFinite(candidateValue) {
|
||||||
|
currentJD = candidateJD
|
||||||
|
currentValue = math.Inf(1)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if candidateValue >= 0 {
|
||||||
|
return bisectJupiterGalileanContactRoot(currentJD, currentValue, candidateJD, candidateValue, metric)
|
||||||
|
}
|
||||||
|
currentJD = candidateJD
|
||||||
|
currentValue = candidateValue
|
||||||
|
}
|
||||||
|
return math.NaN(), false
|
||||||
|
}
|
||||||
|
|
||||||
|
func refineJupiterGalileanContactRoot(
|
||||||
|
modelCrossingJD float64,
|
||||||
|
direction int,
|
||||||
|
target func(jd float64) float64,
|
||||||
|
) (float64, bool) {
|
||||||
|
if direction != -1 && direction != 1 {
|
||||||
|
return math.NaN(), false
|
||||||
|
}
|
||||||
|
modelValue := target(modelCrossingJD)
|
||||||
|
if !isFinite(modelValue) {
|
||||||
|
return math.NaN(), false
|
||||||
|
}
|
||||||
|
step := jupiterGalileanContactBracketStepDays
|
||||||
|
maxSteps := int(math.Ceil(jupiterGalileanContactBracketSpanDays / step))
|
||||||
|
nearJD := modelCrossingJD
|
||||||
|
nearValue := modelValue
|
||||||
|
for i := 1; i <= maxSteps; i++ {
|
||||||
|
farJD := modelCrossingJD + float64(direction)*step*float64(i)
|
||||||
|
farValue := target(farJD)
|
||||||
|
if !isFinite(farValue) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if nearValue == 0 {
|
||||||
|
return nearJD, true
|
||||||
|
}
|
||||||
|
if farValue == 0 {
|
||||||
|
return farJD, true
|
||||||
|
}
|
||||||
|
if nearValue*farValue < 0 {
|
||||||
|
return bisectJupiterGalileanContactRoot(nearJD, nearValue, farJD, farValue, target)
|
||||||
|
}
|
||||||
|
nearJD = farJD
|
||||||
|
nearValue = farValue
|
||||||
|
}
|
||||||
|
return math.NaN(), false
|
||||||
|
}
|
||||||
|
|
||||||
|
func bisectJupiterGalileanContactRoot(
|
||||||
|
jd1, value1, jd2, value2 float64,
|
||||||
|
target func(jd float64) float64,
|
||||||
|
) (float64, bool) {
|
||||||
|
leftJD := jd1
|
||||||
|
rightJD := jd2
|
||||||
|
leftValue := value1
|
||||||
|
rightValue := value2
|
||||||
|
if rightJD < leftJD {
|
||||||
|
leftJD, rightJD = rightJD, leftJD
|
||||||
|
leftValue, rightValue = rightValue, leftValue
|
||||||
|
}
|
||||||
|
if !isFinite(leftValue) || !isFinite(rightValue) || leftValue*rightValue > 0 {
|
||||||
|
return math.NaN(), false
|
||||||
|
}
|
||||||
|
for i := 0; i < 80 && rightJD-leftJD > jupiterGalileanEventEpsilonDays; i++ {
|
||||||
|
midJD := (leftJD + rightJD) / 2
|
||||||
|
midValue := target(midJD)
|
||||||
|
if !isFinite(midValue) {
|
||||||
|
return math.NaN(), false
|
||||||
|
}
|
||||||
|
if midValue == 0 {
|
||||||
|
return midJD, true
|
||||||
|
}
|
||||||
|
if leftValue*midValue <= 0 {
|
||||||
|
rightJD = midJD
|
||||||
|
rightValue = midValue
|
||||||
|
} else {
|
||||||
|
leftJD = midJD
|
||||||
|
leftValue = midValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return (leftJD + rightJD) / 2, true
|
||||||
|
}
|
||||||
|
|
||||||
|
func jupiterGalileanContactGeometryAt(
|
||||||
|
jd float64,
|
||||||
|
satellite int,
|
||||||
|
phenomenonType JupiterGalileanPhenomenonType,
|
||||||
|
) (jupiterGalileanContactGeometry, bool) {
|
||||||
|
if !isFinite(jd) || satellite < 1 || satellite > 4 || !isValidJupiterGalileanPhenomenonType(phenomenonType) {
|
||||||
|
return jupiterGalileanContactGeometry{}, false
|
||||||
|
}
|
||||||
|
evaluationJD := TD2UT(jd, true)
|
||||||
|
context := newJupiterGalileanObservationContext(evaluationJD)
|
||||||
|
if context.jupiterDistance == 0 {
|
||||||
|
return jupiterGalileanContactGeometry{}, false
|
||||||
|
}
|
||||||
|
|
||||||
|
index := satellite - 1
|
||||||
|
observation := context.observationForSatellite(index)
|
||||||
|
stateVector := Vector3{observation.State.X, observation.State.Y, observation.State.Z}
|
||||||
|
satelliteRadius := jupiterGalileanSatelliteRadiusJupiterRadii(satellite)
|
||||||
|
if !isFinite(satelliteRadius) || satelliteRadius <= 0 {
|
||||||
|
return jupiterGalileanContactGeometry{}, false
|
||||||
|
}
|
||||||
|
|
||||||
|
switch phenomenonType {
|
||||||
|
case JupiterGalileanTransit, JupiterGalileanOccultation:
|
||||||
|
return jupiterGalileanContactGeometry{
|
||||||
|
signedDistance: ellipseSignedDistance(observation.OffsetXJupiterRadii, observation.OffsetYJupiterRadii, 1, context.earthMinorRadius),
|
||||||
|
effectiveRadius: satelliteRadius,
|
||||||
|
}, true
|
||||||
|
case JupiterGalileanEclipse:
|
||||||
|
xSunAU := vectorDot(stateVector, context.sunEast)
|
||||||
|
ySunAU := vectorDot(stateVector, context.sunNorth)
|
||||||
|
zSunAU := vectorDot(stateVector, context.sunLineOfSight)
|
||||||
|
umbraScale := jupiterUmbraScale(zSunAU, context.sunDistanceAU)
|
||||||
|
if zSunAU <= 0 || umbraScale <= 0 {
|
||||||
|
return jupiterGalileanContactGeometry{}, false
|
||||||
|
}
|
||||||
|
radiusAU := jupiterGalileanEquatorialRadiusKM / astronomicalUnitKM
|
||||||
|
return jupiterGalileanContactGeometry{
|
||||||
|
signedDistance: ellipseSignedDistance(xSunAU/radiusAU, ySunAU/radiusAU, umbraScale, context.sunMinorRadius*umbraScale),
|
||||||
|
effectiveRadius: satelliteRadius,
|
||||||
|
}, true
|
||||||
|
case JupiterGalileanShadowTransit:
|
||||||
|
radiusAU := jupiterGalileanEquatorialRadiusKM / astronomicalUnitKM
|
||||||
|
axisDenominator := vectorDot(context.sunLineOfSight, context.lineOfSight)
|
||||||
|
if math.Abs(axisDenominator) < 1e-12 {
|
||||||
|
return jupiterGalileanContactGeometry{}, false
|
||||||
|
}
|
||||||
|
axisDistanceAU := -vectorDot(stateVector, context.lineOfSight) / axisDenominator
|
||||||
|
if axisDistanceAU <= 0 {
|
||||||
|
return jupiterGalileanContactGeometry{}, false
|
||||||
|
}
|
||||||
|
axisPoint := Vector3{
|
||||||
|
stateVector[0] + axisDistanceAU*context.sunLineOfSight[0],
|
||||||
|
stateVector[1] + axisDistanceAU*context.sunLineOfSight[1],
|
||||||
|
stateVector[2] + axisDistanceAU*context.sunLineOfSight[2],
|
||||||
|
}
|
||||||
|
xAU := vectorDot(axisPoint, context.east)
|
||||||
|
yAU := vectorDot(axisPoint, context.north)
|
||||||
|
return jupiterGalileanContactGeometry{
|
||||||
|
signedDistance: ellipseSignedDistance(xAU/radiusAU, yAU/radiusAU, 1, context.earthMinorRadius),
|
||||||
|
effectiveRadius: jupiterGalileanPenumbraRadiusJupiterRadii(satellite, axisDistanceAU, context.sunDistanceAU),
|
||||||
|
}, true
|
||||||
|
default:
|
||||||
|
return jupiterGalileanContactGeometry{}, false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func jupiterGalileanEclipseSignedDistanceAt(jd float64, satellite int, penumbra bool) (float64, bool) {
|
||||||
|
if !isFinite(jd) || satellite < 1 || satellite > 4 {
|
||||||
|
return math.NaN(), false
|
||||||
|
}
|
||||||
|
evaluationJD := TD2UT(jd, true)
|
||||||
|
context := newJupiterGalileanObservationContext(evaluationJD)
|
||||||
|
if context.jupiterDistance == 0 {
|
||||||
|
return math.NaN(), false
|
||||||
|
}
|
||||||
|
state := context.observationForSatellite(satellite - 1).State
|
||||||
|
stateVector := Vector3{state.X, state.Y, state.Z}
|
||||||
|
xSunAU := vectorDot(stateVector, context.sunEast)
|
||||||
|
ySunAU := vectorDot(stateVector, context.sunNorth)
|
||||||
|
zSunAU := vectorDot(stateVector, context.sunLineOfSight)
|
||||||
|
if zSunAU <= 0 {
|
||||||
|
return math.NaN(), false
|
||||||
|
}
|
||||||
|
scale := jupiterUmbraScale(zSunAU, context.sunDistanceAU)
|
||||||
|
if penumbra {
|
||||||
|
scale = jupiterPenumbraScale(zSunAU, context.sunDistanceAU)
|
||||||
|
}
|
||||||
|
if scale <= 0 {
|
||||||
|
return math.NaN(), false
|
||||||
|
}
|
||||||
|
radiusAU := jupiterGalileanEquatorialRadiusKM / astronomicalUnitKM
|
||||||
|
return ellipseSignedDistance(xSunAU/radiusAU, ySunAU/radiusAU, scale, context.sunMinorRadius*scale), true
|
||||||
|
}
|
||||||
|
|
||||||
|
func jupiterGalileanSatelliteRadiusJupiterRadii(satellite int) float64 {
|
||||||
|
switch satellite {
|
||||||
|
case 1:
|
||||||
|
return 1821.6 / jupiterGalileanEquatorialRadiusKM
|
||||||
|
case 2:
|
||||||
|
return 1560.8 / jupiterGalileanEquatorialRadiusKM
|
||||||
|
case 3:
|
||||||
|
return 2634.1 / jupiterGalileanEquatorialRadiusKM
|
||||||
|
case 4:
|
||||||
|
return 2410.3 / jupiterGalileanEquatorialRadiusKM
|
||||||
|
default:
|
||||||
|
return math.NaN()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func jupiterPenumbraScale(distanceBehindAU, sunDistanceAU float64) float64 {
|
||||||
|
if distanceBehindAU <= 0 || sunDistanceAU <= 0 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
jupiterRadiusAU := jupiterGalileanEquatorialRadiusKM / astronomicalUnitKM
|
||||||
|
return 1 + distanceBehindAU*(solarRadiusAU+jupiterRadiusAU)/(sunDistanceAU*jupiterRadiusAU)
|
||||||
|
}
|
||||||
|
|
||||||
|
func jupiterGalileanUmbraRadiusJupiterRadii(satellite int, pathLengthAU, sunDistanceAU float64) float64 {
|
||||||
|
if pathLengthAU <= 0 || sunDistanceAU <= 0 {
|
||||||
|
return math.NaN()
|
||||||
|
}
|
||||||
|
satelliteRadiusAU := jupiterGalileanSatelliteRadiusJupiterRadii(satellite) * jupiterGalileanEquatorialRadiusKM / astronomicalUnitKM
|
||||||
|
umbraRadiusAU := satelliteRadiusAU - pathLengthAU*(solarRadiusAU-satelliteRadiusAU)/sunDistanceAU
|
||||||
|
if umbraRadiusAU <= 0 {
|
||||||
|
return math.NaN()
|
||||||
|
}
|
||||||
|
return umbraRadiusAU / (jupiterGalileanEquatorialRadiusKM / astronomicalUnitKM)
|
||||||
|
}
|
||||||
|
|
||||||
|
func jupiterGalileanPenumbraRadiusJupiterRadii(satellite int, pathLengthAU, sunDistanceAU float64) float64 {
|
||||||
|
if pathLengthAU <= 0 || sunDistanceAU <= 0 {
|
||||||
|
return math.NaN()
|
||||||
|
}
|
||||||
|
satelliteRadiusAU := jupiterGalileanSatelliteRadiusJupiterRadii(satellite) * jupiterGalileanEquatorialRadiusKM / astronomicalUnitKM
|
||||||
|
penumbraRadiusAU := satelliteRadiusAU + pathLengthAU*(solarRadiusAU+satelliteRadiusAU)/sunDistanceAU
|
||||||
|
if penumbraRadiusAU <= 0 {
|
||||||
|
return math.NaN()
|
||||||
|
}
|
||||||
|
return penumbraRadiusAU / (jupiterGalileanEquatorialRadiusKM / astronomicalUnitKM)
|
||||||
|
}
|
||||||
|
|
||||||
|
func jupiterGalileanShadowLimbMetricAt(jd float64, satellite int, penumbra bool) (float64, bool) {
|
||||||
|
if !isFinite(jd) || satellite < 1 || satellite > 4 {
|
||||||
|
return math.NaN(), false
|
||||||
|
}
|
||||||
|
evaluationJD := TD2UT(jd, true)
|
||||||
|
context := newJupiterGalileanObservationContext(evaluationJD)
|
||||||
|
if context.jupiterDistance == 0 {
|
||||||
|
return math.NaN(), false
|
||||||
|
}
|
||||||
|
state := context.observationForSatellite(satellite - 1).State
|
||||||
|
stateVector := Vector3{state.X, state.Y, state.Z}
|
||||||
|
satelliteBody := context.toBodyCoordinates(stateVector)
|
||||||
|
axisBody := normalizeVector(context.toBodyCoordinates(context.sunLineOfSight))
|
||||||
|
if vectorMagnitude(axisBody) == 0 {
|
||||||
|
return math.NaN(), false
|
||||||
|
}
|
||||||
|
radiusAU := jupiterGalileanEquatorialRadiusKM / astronomicalUnitKM
|
||||||
|
satelliteRadiusAU := jupiterGalileanSatelliteRadiusJupiterRadii(satellite) * radiusAU
|
||||||
|
limbU, limbV, ok := jupiterGalileanVisibleLimbBasis(context)
|
||||||
|
if !ok {
|
||||||
|
return math.NaN(), false
|
||||||
|
}
|
||||||
|
metricAtAngle := func(angle float64) float64 {
|
||||||
|
limbPointBody := jupiterGalileanVisibleLimbPoint(angle, limbU, limbV, jupiterPolarRadiusRatio())
|
||||||
|
limbPointAU := Vector3{
|
||||||
|
limbPointBody[0] * radiusAU,
|
||||||
|
limbPointBody[1] * radiusAU,
|
||||||
|
limbPointBody[2] * radiusAU,
|
||||||
|
}
|
||||||
|
return jupiterGalileanShadowConeMetricForPoint(limbPointAU, satelliteBody, axisBody, satelliteRadiusAU, context.sunDistanceAU, penumbra)
|
||||||
|
}
|
||||||
|
return minimizeJupiterGalileanPeriodicMetric(metricAtAngle)
|
||||||
|
}
|
||||||
|
|
||||||
|
func jupiterGalileanVisibleLimbBasis(context jupiterGalileanObservationContext) (Vector3, Vector3, bool) {
|
||||||
|
polar := jupiterPolarRadiusRatio()
|
||||||
|
earthBody := context.toBodyCoordinates(context.earthDirection)
|
||||||
|
planeNormal := Vector3{earthBody[0], earthBody[1], earthBody[2] / polar}
|
||||||
|
planeNormal = normalizeVector(planeNormal)
|
||||||
|
if vectorMagnitude(planeNormal) == 0 {
|
||||||
|
return Vector3{}, Vector3{}, false
|
||||||
|
}
|
||||||
|
reference := Vector3{0, 0, 1}
|
||||||
|
if math.Abs(vectorDot(reference, planeNormal)) > 0.9 {
|
||||||
|
reference = Vector3{1, 0, 0}
|
||||||
|
}
|
||||||
|
u := normalizeVector(pxp(planeNormal, reference))
|
||||||
|
if vectorMagnitude(u) == 0 {
|
||||||
|
reference = Vector3{0, 1, 0}
|
||||||
|
u = normalizeVector(pxp(planeNormal, reference))
|
||||||
|
if vectorMagnitude(u) == 0 {
|
||||||
|
return Vector3{}, Vector3{}, false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
v := normalizeVector(pxp(planeNormal, u))
|
||||||
|
if vectorMagnitude(v) == 0 {
|
||||||
|
return Vector3{}, Vector3{}, false
|
||||||
|
}
|
||||||
|
return u, v, true
|
||||||
|
}
|
||||||
|
|
||||||
|
func jupiterGalileanVisibleLimbPoint(angle float64, u, v Vector3, polar float64) Vector3 {
|
||||||
|
sinAngle := math.Sin(angle)
|
||||||
|
cosAngle := math.Cos(angle)
|
||||||
|
q := Vector3{
|
||||||
|
u[0]*cosAngle + v[0]*sinAngle,
|
||||||
|
u[1]*cosAngle + v[1]*sinAngle,
|
||||||
|
u[2]*cosAngle + v[2]*sinAngle,
|
||||||
|
}
|
||||||
|
return Vector3{q[0], q[1], polar * q[2]}
|
||||||
|
}
|
||||||
|
|
||||||
|
func jupiterGalileanShadowConeMetricForPoint(
|
||||||
|
pointAU, satelliteAU, axisUnit Vector3,
|
||||||
|
satelliteRadiusAU, sunDistanceAU float64,
|
||||||
|
penumbra bool,
|
||||||
|
) float64 {
|
||||||
|
vector := Vector3{
|
||||||
|
pointAU[0] - satelliteAU[0],
|
||||||
|
pointAU[1] - satelliteAU[1],
|
||||||
|
pointAU[2] - satelliteAU[2],
|
||||||
|
}
|
||||||
|
axisDistanceAU := vectorDot(vector, axisUnit)
|
||||||
|
if axisDistanceAU <= 0 {
|
||||||
|
return math.Inf(1)
|
||||||
|
}
|
||||||
|
perpendicular := Vector3{
|
||||||
|
vector[0] - axisDistanceAU*axisUnit[0],
|
||||||
|
vector[1] - axisDistanceAU*axisUnit[1],
|
||||||
|
vector[2] - axisDistanceAU*axisUnit[2],
|
||||||
|
}
|
||||||
|
perpendicularDistanceAU := vectorMagnitude(perpendicular)
|
||||||
|
if penumbra {
|
||||||
|
penumbraRadiusAU := satelliteRadiusAU + axisDistanceAU*(solarRadiusAU+satelliteRadiusAU)/sunDistanceAU
|
||||||
|
return perpendicularDistanceAU - penumbraRadiusAU
|
||||||
|
}
|
||||||
|
umbraRadiusAU := satelliteRadiusAU - axisDistanceAU*(solarRadiusAU-satelliteRadiusAU)/sunDistanceAU
|
||||||
|
return perpendicularDistanceAU - umbraRadiusAU
|
||||||
|
}
|
||||||
|
|
||||||
|
func minimizeJupiterGalileanPeriodicMetric(metric func(angle float64) float64) (float64, bool) {
|
||||||
|
const (
|
||||||
|
samples = 144
|
||||||
|
phi = 0.6180339887498948482
|
||||||
|
)
|
||||||
|
step := 2 * math.Pi / float64(samples)
|
||||||
|
bestAngle := 0.0
|
||||||
|
bestValue := math.Inf(1)
|
||||||
|
for i := 0; i < samples; i++ {
|
||||||
|
angle := float64(i) * step
|
||||||
|
value := metric(angle)
|
||||||
|
if value < bestValue {
|
||||||
|
bestValue = value
|
||||||
|
bestAngle = angle
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !isFinite(bestValue) {
|
||||||
|
return math.NaN(), false
|
||||||
|
}
|
||||||
|
left := bestAngle - step
|
||||||
|
right := bestAngle + step
|
||||||
|
x1 := right - phi*(right-left)
|
||||||
|
x2 := left + phi*(right-left)
|
||||||
|
f1 := metric(x1)
|
||||||
|
f2 := metric(x2)
|
||||||
|
for i := 0; i < 80; i++ {
|
||||||
|
if f1 <= f2 {
|
||||||
|
right = x2
|
||||||
|
x2 = x1
|
||||||
|
f2 = f1
|
||||||
|
x1 = right - phi*(right-left)
|
||||||
|
f1 = metric(x1)
|
||||||
|
} else {
|
||||||
|
left = x1
|
||||||
|
x1 = x2
|
||||||
|
f1 = f2
|
||||||
|
x2 = left + phi*(right-left)
|
||||||
|
f2 = metric(x2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return math.Min(bestValue, metric((left+right)/2)), true
|
||||||
|
}
|
||||||
|
|
||||||
|
func ellipseSignedDistance(x, y, major, minor float64) float64 {
|
||||||
|
if major <= 0 || minor <= 0 {
|
||||||
|
return math.NaN()
|
||||||
|
}
|
||||||
|
targetX := math.Abs(x)
|
||||||
|
targetY := math.Abs(y)
|
||||||
|
if targetX == 0 && targetY == 0 {
|
||||||
|
if minor < major {
|
||||||
|
return -minor
|
||||||
|
}
|
||||||
|
return -major
|
||||||
|
}
|
||||||
|
left := 0.0
|
||||||
|
right := math.Pi / 2
|
||||||
|
const phi = 0.6180339887498948482
|
||||||
|
x1 := right - phi*(right-left)
|
||||||
|
x2 := left + phi*(right-left)
|
||||||
|
f1 := ellipseDistanceSquaredAtAngle(targetX, targetY, major, minor, x1)
|
||||||
|
f2 := ellipseDistanceSquaredAtAngle(targetX, targetY, major, minor, x2)
|
||||||
|
for i := 0; i < 80; i++ {
|
||||||
|
if f1 <= f2 {
|
||||||
|
right = x2
|
||||||
|
x2 = x1
|
||||||
|
f2 = f1
|
||||||
|
x1 = right - phi*(right-left)
|
||||||
|
f1 = ellipseDistanceSquaredAtAngle(targetX, targetY, major, minor, x1)
|
||||||
|
} else {
|
||||||
|
left = x1
|
||||||
|
x1 = x2
|
||||||
|
f1 = f2
|
||||||
|
x2 = left + phi*(right-left)
|
||||||
|
f2 = ellipseDistanceSquaredAtAngle(targetX, targetY, major, minor, x2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
distance := math.Sqrt(ellipseDistanceSquaredAtAngle(targetX, targetY, major, minor, (left+right)/2))
|
||||||
|
if ellipseInside(x, y, major, minor) {
|
||||||
|
return -distance
|
||||||
|
}
|
||||||
|
return distance
|
||||||
|
}
|
||||||
|
|
||||||
|
func ellipseDistanceSquaredAtAngle(x, y, major, minor, angle float64) float64 {
|
||||||
|
ellipseX := major * math.Cos(angle)
|
||||||
|
ellipseY := minor * math.Sin(angle)
|
||||||
|
dx := ellipseX - x
|
||||||
|
dy := ellipseY - y
|
||||||
|
return dx*dx + dy*dy
|
||||||
|
}
|
||||||
|
|
||||||
|
func invalidJupiterGalileanPhenomenonContactEvent() JupiterGalileanPhenomenonContactEvent {
|
||||||
|
return JupiterGalileanPhenomenonContactEvent{
|
||||||
|
Disappearance: JupiterGalileanPhenomenonContact{
|
||||||
|
Start: math.NaN(),
|
||||||
|
ModelCrossing: math.NaN(),
|
||||||
|
End: math.NaN(),
|
||||||
|
},
|
||||||
|
Greatest: math.NaN(),
|
||||||
|
Reappearance: JupiterGalileanPhenomenonContact{
|
||||||
|
Start: math.NaN(),
|
||||||
|
ModelCrossing: math.NaN(),
|
||||||
|
End: math.NaN(),
|
||||||
|
},
|
||||||
|
GreatestPhenomenon: invalidJupiterGalileanPhenomenon(),
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,101 @@
|
|||||||
|
package basic
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestJupiterGalileanPhenomenonContactEventsAgainstIMCCEBaseline(t *testing.T) {
|
||||||
|
records := loadGalileanEventBaseline(t)
|
||||||
|
maxStartDiff := 0.0
|
||||||
|
maxEndDiff := 0.0
|
||||||
|
maxStartDurationDiff := 0.0
|
||||||
|
maxEndDurationDiff := 0.0
|
||||||
|
for _, record := range records {
|
||||||
|
startUTC := mustParseRFC3339Nano(t, record.StartUTC)
|
||||||
|
endUTC := mustParseRFC3339Nano(t, record.EndUTC)
|
||||||
|
queryMid := startUTC.Add(endUTC.Sub(startUTC) / 2)
|
||||||
|
phenomenonType := parseBasicGalileanPhenomenonType(t, record.Type)
|
||||||
|
|
||||||
|
event := ClosestJupiterGalileanPhenomenonContactEvent(Date2JDE(queryMid.UTC()), record.Satellite, phenomenonType)
|
||||||
|
if !event.Valid {
|
||||||
|
t.Fatalf("%s invalid contact event", record.Label)
|
||||||
|
}
|
||||||
|
|
||||||
|
gotStart := JDE2DateByZone(event.Disappearance.Start, time.UTC, false)
|
||||||
|
gotEnd := JDE2DateByZone(event.Reappearance.Start, time.UTC, false)
|
||||||
|
startDiff := math.Abs(gotStart.Sub(startUTC).Seconds())
|
||||||
|
endDiff := math.Abs(gotEnd.Sub(endUTC).Seconds())
|
||||||
|
startDurationDiff := math.Abs((event.Disappearance.End-event.Disappearance.Start)*86400 - record.StartDurationMinutes*60)
|
||||||
|
endDurationDiff := math.Abs((event.Reappearance.End-event.Reappearance.Start)*86400 - record.EndDurationMinutes*60)
|
||||||
|
|
||||||
|
if startDiff > maxStartDiff {
|
||||||
|
maxStartDiff = startDiff
|
||||||
|
}
|
||||||
|
if endDiff > maxEndDiff {
|
||||||
|
maxEndDiff = endDiff
|
||||||
|
}
|
||||||
|
if startDurationDiff > maxStartDurationDiff {
|
||||||
|
maxStartDurationDiff = startDurationDiff
|
||||||
|
}
|
||||||
|
if endDurationDiff > maxEndDurationDiff {
|
||||||
|
maxEndDurationDiff = endDurationDiff
|
||||||
|
}
|
||||||
|
|
||||||
|
if startDiff > galileanContactTimeToleranceSeconds(phenomenonType) {
|
||||||
|
t.Fatalf("%s disappearance start mismatch: got %s want %s", record.Label, gotStart.Format(time.RFC3339Nano), startUTC.Format(time.RFC3339Nano))
|
||||||
|
}
|
||||||
|
if endDiff > galileanContactTimeToleranceSeconds(phenomenonType) {
|
||||||
|
t.Fatalf("%s reappearance start mismatch: got %s want %s", record.Label, gotEnd.Format(time.RFC3339Nano), endUTC.Format(time.RFC3339Nano))
|
||||||
|
}
|
||||||
|
if startDurationDiff > galileanContactDurationToleranceSeconds(phenomenonType) {
|
||||||
|
t.Fatalf("%s disappearance duration mismatch: got %.1fs want %.1fs", record.Label, (event.Disappearance.End-event.Disappearance.Start)*86400, record.StartDurationMinutes*60)
|
||||||
|
}
|
||||||
|
if endDurationDiff > galileanContactDurationToleranceSeconds(phenomenonType) {
|
||||||
|
t.Fatalf("%s reappearance duration mismatch: got %.1fs want %.1fs", record.Label, (event.Reappearance.End-event.Reappearance.Start)*86400, record.EndDurationMinutes*60)
|
||||||
|
}
|
||||||
|
if !(event.Disappearance.Start <= event.Disappearance.ModelCrossing && event.Disappearance.ModelCrossing <= event.Disappearance.End) {
|
||||||
|
t.Fatalf("%s contact ordering invalid", record.Label)
|
||||||
|
}
|
||||||
|
if !(event.Reappearance.Start <= event.Reappearance.ModelCrossing && event.Reappearance.ModelCrossing <= event.Reappearance.End) {
|
||||||
|
t.Fatalf("%s reappearance ordering invalid", record.Label)
|
||||||
|
}
|
||||||
|
fullEvent := ClosestJupiterGalileanPhenomenonEvent(Date2JDE(queryMid.UTC()), record.Satellite, phenomenonType)
|
||||||
|
if phenomenonType != JupiterGalileanShadowTransit {
|
||||||
|
if math.Abs(event.Disappearance.ModelCrossing-fullEvent.Start)*86400 > 2 {
|
||||||
|
t.Fatalf("%s disappearance model crossing mismatch", record.Label)
|
||||||
|
}
|
||||||
|
if math.Abs(event.Reappearance.ModelCrossing-fullEvent.End)*86400 > 2 {
|
||||||
|
t.Fatalf("%s reappearance model crossing mismatch", record.Label)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
t.Logf(
|
||||||
|
"galilean contact baseline max diff: start=%.1fs end=%.1fs startDur=%.1fs endDur=%.1fs",
|
||||||
|
maxStartDiff,
|
||||||
|
maxEndDiff,
|
||||||
|
maxStartDurationDiff,
|
||||||
|
maxEndDurationDiff,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func galileanContactTimeToleranceSeconds(phenomenonType JupiterGalileanPhenomenonType) float64 {
|
||||||
|
switch phenomenonType {
|
||||||
|
case JupiterGalileanShadowTransit:
|
||||||
|
return 90.0
|
||||||
|
case JupiterGalileanEclipse:
|
||||||
|
return 180.0
|
||||||
|
default:
|
||||||
|
return 120.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func galileanContactDurationToleranceSeconds(phenomenonType JupiterGalileanPhenomenonType) float64 {
|
||||||
|
switch phenomenonType {
|
||||||
|
case JupiterGalileanShadowTransit:
|
||||||
|
return 15.0
|
||||||
|
default:
|
||||||
|
return 90.0
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,498 @@
|
|||||||
|
package basic
|
||||||
|
|
||||||
|
import "math"
|
||||||
|
|
||||||
|
const (
|
||||||
|
jupiterGalileanEventSearchSpanDays = 8 * 365.25
|
||||||
|
jupiterGalileanEventEpsilonDays = 1.0 / 86400.0
|
||||||
|
jupiterGalileanBoundaryStepDays = 1.0 / 24.0
|
||||||
|
)
|
||||||
|
|
||||||
|
// JupiterGalileanPhenomenonType 伽利略卫星现象类型 / Galilean-satellite phenomenon type.
|
||||||
|
type JupiterGalileanPhenomenonType string
|
||||||
|
|
||||||
|
const (
|
||||||
|
// JupiterGalileanTransit 凌日 / satellite transit across Jupiter.
|
||||||
|
JupiterGalileanTransit JupiterGalileanPhenomenonType = "transit"
|
||||||
|
// JupiterGalileanOccultation 掩蔽 / occultation behind Jupiter.
|
||||||
|
JupiterGalileanOccultation JupiterGalileanPhenomenonType = "occultation"
|
||||||
|
// JupiterGalileanEclipse 食 / eclipse in Jupiter's shadow.
|
||||||
|
JupiterGalileanEclipse JupiterGalileanPhenomenonType = "eclipse"
|
||||||
|
// JupiterGalileanShadowTransit 影凌 / shadow transit across Jupiter.
|
||||||
|
JupiterGalileanShadowTransit JupiterGalileanPhenomenonType = "shadow_transit"
|
||||||
|
)
|
||||||
|
|
||||||
|
// JupiterGalileanPhenomenonEvent 伽利略卫星现象整场事件 / full Galilean-satellite phenomenon event.
|
||||||
|
//
|
||||||
|
// Start、Greatest、End 都使用 UTC/UT 对应的儒略日。
|
||||||
|
// Start, Greatest, and End are UTC/UT Julian days.
|
||||||
|
type JupiterGalileanPhenomenonEvent struct {
|
||||||
|
Valid bool
|
||||||
|
Satellite int
|
||||||
|
Type JupiterGalileanPhenomenonType
|
||||||
|
|
||||||
|
Start float64
|
||||||
|
Greatest float64
|
||||||
|
End float64
|
||||||
|
|
||||||
|
GreatestPhenomenon JupiterGalileanPhenomenon
|
||||||
|
}
|
||||||
|
|
||||||
|
type jupiterGalileanMetricSample struct {
|
||||||
|
active bool
|
||||||
|
metric float64
|
||||||
|
phenomenon JupiterGalileanPhenomenon
|
||||||
|
}
|
||||||
|
|
||||||
|
type jupiterGalileanShadowPoint struct {
|
||||||
|
hasIntersection bool
|
||||||
|
visible bool
|
||||||
|
pathLengthAU float64
|
||||||
|
xAU float64
|
||||||
|
yAU float64
|
||||||
|
xJupiterRadii float64
|
||||||
|
yJupiterRadii float64
|
||||||
|
}
|
||||||
|
|
||||||
|
// LastJupiterGalileanPhenomenonEvent 上一次伽利略卫星现象 / previous Galilean-satellite event.
|
||||||
|
func LastJupiterGalileanPhenomenonEvent(jd float64, satellite int, phenomenonType JupiterGalileanPhenomenonType) JupiterGalileanPhenomenonEvent {
|
||||||
|
event, _ := searchJupiterGalileanPhenomenonEvent(jd, satellite, phenomenonType, -1, true)
|
||||||
|
return event
|
||||||
|
}
|
||||||
|
|
||||||
|
// NextJupiterGalileanPhenomenonEvent 下一次伽利略卫星现象 / next Galilean-satellite event.
|
||||||
|
func NextJupiterGalileanPhenomenonEvent(jd float64, satellite int, phenomenonType JupiterGalileanPhenomenonType) JupiterGalileanPhenomenonEvent {
|
||||||
|
event, _ := searchJupiterGalileanPhenomenonEvent(jd, satellite, phenomenonType, 1, false)
|
||||||
|
return event
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClosestJupiterGalileanPhenomenonEvent 最近一次伽利略卫星现象 / closest Galilean-satellite event.
|
||||||
|
func ClosestJupiterGalileanPhenomenonEvent(jd float64, satellite int, phenomenonType JupiterGalileanPhenomenonType) JupiterGalileanPhenomenonEvent {
|
||||||
|
last, hasLast := searchJupiterGalileanPhenomenonEvent(jd, satellite, phenomenonType, -1, true)
|
||||||
|
next, hasNext := searchJupiterGalileanPhenomenonEvent(jd, satellite, phenomenonType, 1, false)
|
||||||
|
switch {
|
||||||
|
case hasLast && !hasNext:
|
||||||
|
return last
|
||||||
|
case !hasLast && hasNext:
|
||||||
|
return next
|
||||||
|
case !hasLast && !hasNext:
|
||||||
|
return invalidJupiterGalileanPhenomenonEvent()
|
||||||
|
}
|
||||||
|
if math.Abs(last.Greatest-jd) <= math.Abs(next.Greatest-jd) {
|
||||||
|
return last
|
||||||
|
}
|
||||||
|
return next
|
||||||
|
}
|
||||||
|
|
||||||
|
func searchJupiterGalileanPhenomenonEvent(
|
||||||
|
jd float64,
|
||||||
|
satellite int,
|
||||||
|
phenomenonType JupiterGalileanPhenomenonType,
|
||||||
|
direction int,
|
||||||
|
includeCurrent bool,
|
||||||
|
) (JupiterGalileanPhenomenonEvent, bool) {
|
||||||
|
if !isFinite(jd) || direction == 0 || satellite < 1 || satellite > 4 || !isValidJupiterGalileanPhenomenonType(phenomenonType) {
|
||||||
|
return invalidJupiterGalileanPhenomenonEvent(), false
|
||||||
|
}
|
||||||
|
|
||||||
|
if sample := jupiterGalileanPhenomenonMetricAt(jd, satellite, phenomenonType); sample.active {
|
||||||
|
current := findJupiterGalileanPhenomenonEventAround(jd, satellite, phenomenonType)
|
||||||
|
if current.Valid && includeCurrent {
|
||||||
|
return current, true
|
||||||
|
}
|
||||||
|
if current.Valid {
|
||||||
|
if direction > 0 {
|
||||||
|
jd = current.End + jupiterGalileanEventEpsilonDays
|
||||||
|
} else {
|
||||||
|
jd = current.Start - jupiterGalileanEventEpsilonDays
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
stepDays := jupiterGalileanCoarseStepDays(satellite)
|
||||||
|
maxSteps := int(math.Ceil(jupiterGalileanEventSearchSpanDays / stepDays))
|
||||||
|
sign := float64(direction)
|
||||||
|
|
||||||
|
prevTime := jd
|
||||||
|
prevSample := jupiterGalileanPhenomenonMetricAt(prevTime, satellite, phenomenonType)
|
||||||
|
midTime := jd + sign*stepDays
|
||||||
|
midSample := jupiterGalileanPhenomenonMetricAt(midTime, satellite, phenomenonType)
|
||||||
|
|
||||||
|
for i := 2; i <= maxSteps; i++ {
|
||||||
|
nextTime := jd + sign*float64(i)*stepDays
|
||||||
|
nextSample := jupiterGalileanPhenomenonMetricAt(nextTime, satellite, phenomenonType)
|
||||||
|
if isFinite(midSample.metric) &&
|
||||||
|
midSample.metric <= prevSample.metric &&
|
||||||
|
midSample.metric <= nextSample.metric {
|
||||||
|
candidate := refineJupiterGalileanMetricMinimum(prevTime, nextTime, satellite, phenomenonType)
|
||||||
|
event := findJupiterGalileanPhenomenonEventAround(candidate, satellite, phenomenonType)
|
||||||
|
if event.Valid && jupiterGalileanEventMatchesDirection(event.Greatest, jd, direction, includeCurrent) {
|
||||||
|
return event, true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
prevTime, prevSample = midTime, midSample
|
||||||
|
midTime, midSample = nextTime, nextSample
|
||||||
|
}
|
||||||
|
|
||||||
|
return invalidJupiterGalileanPhenomenonEvent(), false
|
||||||
|
}
|
||||||
|
|
||||||
|
func findJupiterGalileanPhenomenonEventAround(jd float64, satellite int, phenomenonType JupiterGalileanPhenomenonType) JupiterGalileanPhenomenonEvent {
|
||||||
|
sample := jupiterGalileanPhenomenonMetricAt(jd, satellite, phenomenonType)
|
||||||
|
if !sample.active {
|
||||||
|
return invalidJupiterGalileanPhenomenonEvent()
|
||||||
|
}
|
||||||
|
|
||||||
|
stepDays := jupiterGalileanBoundaryStep(satellite)
|
||||||
|
maxBoundarySteps := int(math.Ceil(jupiterGalileanOrbitPeriodDays(satellite)/stepDays)) + 4
|
||||||
|
|
||||||
|
activeStart := jd
|
||||||
|
inactiveStart := math.NaN()
|
||||||
|
for i := 0; i < maxBoundarySteps; i++ {
|
||||||
|
candidate := activeStart - stepDays
|
||||||
|
if !jupiterGalileanPhenomenonMetricAt(candidate, satellite, phenomenonType).active {
|
||||||
|
inactiveStart = candidate
|
||||||
|
break
|
||||||
|
}
|
||||||
|
activeStart = candidate
|
||||||
|
}
|
||||||
|
if !isFinite(inactiveStart) {
|
||||||
|
return invalidJupiterGalileanPhenomenonEvent()
|
||||||
|
}
|
||||||
|
|
||||||
|
activeEnd := jd
|
||||||
|
inactiveEnd := math.NaN()
|
||||||
|
for i := 0; i < maxBoundarySteps; i++ {
|
||||||
|
candidate := activeEnd + stepDays
|
||||||
|
if !jupiterGalileanPhenomenonMetricAt(candidate, satellite, phenomenonType).active {
|
||||||
|
inactiveEnd = candidate
|
||||||
|
break
|
||||||
|
}
|
||||||
|
activeEnd = candidate
|
||||||
|
}
|
||||||
|
if !isFinite(inactiveEnd) {
|
||||||
|
return invalidJupiterGalileanPhenomenonEvent()
|
||||||
|
}
|
||||||
|
|
||||||
|
start := refineJupiterGalileanEventStart(inactiveStart, activeStart, satellite, phenomenonType)
|
||||||
|
end := refineJupiterGalileanEventEnd(activeEnd, inactiveEnd, satellite, phenomenonType)
|
||||||
|
if !isFinite(start) || !isFinite(end) || end <= start {
|
||||||
|
return invalidJupiterGalileanPhenomenonEvent()
|
||||||
|
}
|
||||||
|
|
||||||
|
greatest := refineJupiterGalileanMetricMinimum(start, end, satellite, phenomenonType)
|
||||||
|
greatestSample := jupiterGalileanPhenomenonMetricAt(greatest, satellite, phenomenonType)
|
||||||
|
if !greatestSample.active {
|
||||||
|
return invalidJupiterGalileanPhenomenonEvent()
|
||||||
|
}
|
||||||
|
|
||||||
|
return JupiterGalileanPhenomenonEvent{
|
||||||
|
Valid: true,
|
||||||
|
Satellite: satellite,
|
||||||
|
Type: phenomenonType,
|
||||||
|
Start: start,
|
||||||
|
Greatest: greatest,
|
||||||
|
End: end,
|
||||||
|
GreatestPhenomenon: greatestSample.phenomenon,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func refineJupiterGalileanEventStart(
|
||||||
|
outsideJD, insideJD float64,
|
||||||
|
satellite int,
|
||||||
|
phenomenonType JupiterGalileanPhenomenonType,
|
||||||
|
) float64 {
|
||||||
|
if insideJD < outsideJD {
|
||||||
|
outsideJD, insideJD = insideJD, outsideJD
|
||||||
|
}
|
||||||
|
if jupiterGalileanPhenomenonMetricAt(outsideJD, satellite, phenomenonType).active {
|
||||||
|
return math.NaN()
|
||||||
|
}
|
||||||
|
if !jupiterGalileanPhenomenonMetricAt(insideJD, satellite, phenomenonType).active {
|
||||||
|
return math.NaN()
|
||||||
|
}
|
||||||
|
left := outsideJD
|
||||||
|
right := insideJD
|
||||||
|
for i := 0; i < 80 && right-left > jupiterGalileanEventEpsilonDays; i++ {
|
||||||
|
mid := (left + right) / 2
|
||||||
|
if jupiterGalileanPhenomenonMetricAt(mid, satellite, phenomenonType).active {
|
||||||
|
right = mid
|
||||||
|
} else {
|
||||||
|
left = mid
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return right
|
||||||
|
}
|
||||||
|
|
||||||
|
func refineJupiterGalileanEventEnd(
|
||||||
|
insideJD, outsideJD float64,
|
||||||
|
satellite int,
|
||||||
|
phenomenonType JupiterGalileanPhenomenonType,
|
||||||
|
) float64 {
|
||||||
|
if outsideJD < insideJD {
|
||||||
|
insideJD, outsideJD = outsideJD, insideJD
|
||||||
|
}
|
||||||
|
if !jupiterGalileanPhenomenonMetricAt(insideJD, satellite, phenomenonType).active {
|
||||||
|
return math.NaN()
|
||||||
|
}
|
||||||
|
if jupiterGalileanPhenomenonMetricAt(outsideJD, satellite, phenomenonType).active {
|
||||||
|
return math.NaN()
|
||||||
|
}
|
||||||
|
left := insideJD
|
||||||
|
right := outsideJD
|
||||||
|
for i := 0; i < 80 && right-left > jupiterGalileanEventEpsilonDays; i++ {
|
||||||
|
mid := (left + right) / 2
|
||||||
|
if jupiterGalileanPhenomenonMetricAt(mid, satellite, phenomenonType).active {
|
||||||
|
left = mid
|
||||||
|
} else {
|
||||||
|
right = mid
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return left
|
||||||
|
}
|
||||||
|
|
||||||
|
func refineJupiterGalileanMetricMinimum(
|
||||||
|
jd1, jd2 float64,
|
||||||
|
satellite int,
|
||||||
|
phenomenonType JupiterGalileanPhenomenonType,
|
||||||
|
) float64 {
|
||||||
|
left := math.Min(jd1, jd2)
|
||||||
|
right := math.Max(jd1, jd2)
|
||||||
|
if right-left <= jupiterGalileanEventEpsilonDays {
|
||||||
|
return (left + right) / 2
|
||||||
|
}
|
||||||
|
const phi = 0.6180339887498948482
|
||||||
|
x1 := right - phi*(right-left)
|
||||||
|
x2 := left + phi*(right-left)
|
||||||
|
f1 := jupiterGalileanPhenomenonMetricAt(x1, satellite, phenomenonType).metric
|
||||||
|
f2 := jupiterGalileanPhenomenonMetricAt(x2, satellite, phenomenonType).metric
|
||||||
|
for i := 0; i < 80 && right-left > jupiterGalileanEventEpsilonDays; i++ {
|
||||||
|
if f1 <= f2 {
|
||||||
|
right = x2
|
||||||
|
x2 = x1
|
||||||
|
f2 = f1
|
||||||
|
x1 = right - phi*(right-left)
|
||||||
|
f1 = jupiterGalileanPhenomenonMetricAt(x1, satellite, phenomenonType).metric
|
||||||
|
} else {
|
||||||
|
left = x1
|
||||||
|
x1 = x2
|
||||||
|
f1 = f2
|
||||||
|
x2 = left + phi*(right-left)
|
||||||
|
f2 = jupiterGalileanPhenomenonMetricAt(x2, satellite, phenomenonType).metric
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return (left + right) / 2
|
||||||
|
}
|
||||||
|
|
||||||
|
func jupiterGalileanPhenomenonMetricAt(
|
||||||
|
jd float64,
|
||||||
|
satellite int,
|
||||||
|
phenomenonType JupiterGalileanPhenomenonType,
|
||||||
|
) jupiterGalileanMetricSample {
|
||||||
|
if !isFinite(jd) || satellite < 1 || satellite > 4 || !isValidJupiterGalileanPhenomenonType(phenomenonType) {
|
||||||
|
return jupiterGalileanMetricSample{
|
||||||
|
metric: math.Inf(1),
|
||||||
|
phenomenon: invalidJupiterGalileanPhenomenon(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
evaluationJD := TD2UT(jd, true)
|
||||||
|
context := newJupiterGalileanObservationContext(evaluationJD)
|
||||||
|
if context.jupiterDistance == 0 {
|
||||||
|
return jupiterGalileanMetricSample{
|
||||||
|
metric: math.Inf(1),
|
||||||
|
phenomenon: invalidJupiterGalileanPhenomenon(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
index := satellite - 1
|
||||||
|
observation := context.observationForSatellite(index)
|
||||||
|
stateVector := Vector3{observation.State.X, observation.State.Y, observation.State.Z}
|
||||||
|
radiusAU := jupiterGalileanEquatorialRadiusKM / astronomicalUnitKM
|
||||||
|
|
||||||
|
xEarth := observation.OffsetXJupiterRadii
|
||||||
|
yEarth := observation.OffsetYJupiterRadii
|
||||||
|
earthMetric := ellipseMetric(xEarth, yEarth, 1, context.earthMinorRadius)
|
||||||
|
onEarthDisk := ellipseInside(xEarth, yEarth, 1, context.earthMinorRadius)
|
||||||
|
|
||||||
|
xSunAU := vectorDot(stateVector, context.sunEast)
|
||||||
|
ySunAU := vectorDot(stateVector, context.sunNorth)
|
||||||
|
zSunAU := vectorDot(stateVector, context.sunLineOfSight)
|
||||||
|
xSun := xSunAU / radiusAU
|
||||||
|
ySun := ySunAU / radiusAU
|
||||||
|
umbraScale := jupiterUmbraScale(zSunAU, context.sunDistanceAU)
|
||||||
|
sunMetric := math.Inf(1)
|
||||||
|
eclipse := false
|
||||||
|
if umbraScale > 0 {
|
||||||
|
sunMetric = ellipseMetric(xSun, ySun, umbraScale, context.sunMinorRadius*umbraScale)
|
||||||
|
eclipse = zSunAU > 0 && sunMetric <= 1+1e-12
|
||||||
|
}
|
||||||
|
|
||||||
|
shadowPoint := context.shadowPointFor(stateVector)
|
||||||
|
shadowMetric := math.Inf(1)
|
||||||
|
shadowTransit := false
|
||||||
|
if shadowPoint.hasIntersection {
|
||||||
|
shadowMetric = ellipseMetric(shadowPoint.xJupiterRadii, shadowPoint.yJupiterRadii, 1, context.earthMinorRadius)
|
||||||
|
shadowTransit = shadowPoint.visible && shadowMetric <= 1+1e-12
|
||||||
|
}
|
||||||
|
|
||||||
|
phenomenon := JupiterGalileanPhenomenon{
|
||||||
|
Transit: onEarthDisk && observation.InFrontOfJupiter,
|
||||||
|
Occultation: onEarthDisk && !observation.InFrontOfJupiter,
|
||||||
|
Eclipse: eclipse,
|
||||||
|
ShadowTransit: shadowTransit,
|
||||||
|
|
||||||
|
ShadowOffsetXArcsec: math.NaN(),
|
||||||
|
ShadowOffsetYArcsec: math.NaN(),
|
||||||
|
ShadowOffsetXJupiterRadii: math.NaN(),
|
||||||
|
ShadowOffsetYJupiterRadii: math.NaN(),
|
||||||
|
}
|
||||||
|
if shadowTransit {
|
||||||
|
phenomenon.ShadowOffsetXArcsec = math.Atan2(shadowPoint.xAU, context.jupiterDistance) * deg * 3600
|
||||||
|
phenomenon.ShadowOffsetYArcsec = math.Atan2(shadowPoint.yAU, context.jupiterDistance) * deg * 3600
|
||||||
|
phenomenon.ShadowOffsetXJupiterRadii = shadowPoint.xJupiterRadii
|
||||||
|
phenomenon.ShadowOffsetYJupiterRadii = shadowPoint.yJupiterRadii
|
||||||
|
}
|
||||||
|
|
||||||
|
switch phenomenonType {
|
||||||
|
case JupiterGalileanTransit:
|
||||||
|
metric := earthMetric
|
||||||
|
if !observation.InFrontOfJupiter {
|
||||||
|
metric += 4
|
||||||
|
}
|
||||||
|
return jupiterGalileanMetricSample{active: phenomenon.Transit, metric: metric, phenomenon: phenomenon}
|
||||||
|
case JupiterGalileanOccultation:
|
||||||
|
metric := earthMetric
|
||||||
|
if observation.InFrontOfJupiter {
|
||||||
|
metric += 4
|
||||||
|
}
|
||||||
|
return jupiterGalileanMetricSample{active: phenomenon.Occultation, metric: metric, phenomenon: phenomenon}
|
||||||
|
case JupiterGalileanEclipse:
|
||||||
|
metric := sunMetric
|
||||||
|
if zSunAU <= 0 {
|
||||||
|
metric += 4
|
||||||
|
}
|
||||||
|
return jupiterGalileanMetricSample{active: phenomenon.Eclipse, metric: metric, phenomenon: phenomenon}
|
||||||
|
case JupiterGalileanShadowTransit:
|
||||||
|
metric := shadowMetric
|
||||||
|
if shadowPoint.hasIntersection && !shadowPoint.visible {
|
||||||
|
metric += 4
|
||||||
|
}
|
||||||
|
return jupiterGalileanMetricSample{active: phenomenon.ShadowTransit, metric: metric, phenomenon: phenomenon}
|
||||||
|
default:
|
||||||
|
return jupiterGalileanMetricSample{metric: math.Inf(1), phenomenon: invalidJupiterGalileanPhenomenon()}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (context jupiterGalileanObservationContext) shadowPointFor(stateVector Vector3) jupiterGalileanShadowPoint {
|
||||||
|
radiusAU := jupiterGalileanEquatorialRadiusKM / astronomicalUnitKM
|
||||||
|
satelliteBody := context.toBodyCoordinates(stateVector)
|
||||||
|
satelliteBody = Vector3{
|
||||||
|
satelliteBody[0] / radiusAU,
|
||||||
|
satelliteBody[1] / radiusAU,
|
||||||
|
satelliteBody[2] / radiusAU,
|
||||||
|
}
|
||||||
|
directionBody := context.toBodyCoordinates(context.sunLineOfSight)
|
||||||
|
intersectionBody, ok := ellipsoidRayIntersection(satelliteBody, directionBody, jupiterPolarRadiusRatio())
|
||||||
|
if !ok {
|
||||||
|
return jupiterGalileanShadowPoint{}
|
||||||
|
}
|
||||||
|
normalBody := Vector3{
|
||||||
|
intersectionBody[0],
|
||||||
|
intersectionBody[1],
|
||||||
|
intersectionBody[2] / (jupiterPolarRadiusRatio() * jupiterPolarRadiusRatio()),
|
||||||
|
}
|
||||||
|
earthBody := context.toBodyCoordinates(context.earthDirection)
|
||||||
|
intersection := context.fromBodyCoordinates(Vector3{
|
||||||
|
intersectionBody[0] * radiusAU,
|
||||||
|
intersectionBody[1] * radiusAU,
|
||||||
|
intersectionBody[2] * radiusAU,
|
||||||
|
})
|
||||||
|
dx := intersection[0] - stateVector[0]
|
||||||
|
dy := intersection[1] - stateVector[1]
|
||||||
|
dz := intersection[2] - stateVector[2]
|
||||||
|
xAU := vectorDot(intersection, context.east)
|
||||||
|
yAU := vectorDot(intersection, context.north)
|
||||||
|
xR := xAU / radiusAU
|
||||||
|
yR := yAU / radiusAU
|
||||||
|
return jupiterGalileanShadowPoint{
|
||||||
|
hasIntersection: true,
|
||||||
|
visible: vectorDot(normalBody, earthBody) > 0,
|
||||||
|
pathLengthAU: math.Sqrt(dx*dx + dy*dy + dz*dz),
|
||||||
|
xAU: xAU,
|
||||||
|
yAU: yAU,
|
||||||
|
xJupiterRadii: xR,
|
||||||
|
yJupiterRadii: yR,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func jupiterGalileanOrbitPeriodDays(satellite int) float64 {
|
||||||
|
switch satellite {
|
||||||
|
case 1:
|
||||||
|
return 1.769137786
|
||||||
|
case 2:
|
||||||
|
return 3.551181
|
||||||
|
case 3:
|
||||||
|
return 7.154553
|
||||||
|
case 4:
|
||||||
|
return 16.689018
|
||||||
|
default:
|
||||||
|
return math.NaN()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func jupiterGalileanCoarseStepDays(satellite int) float64 {
|
||||||
|
step := jupiterGalileanOrbitPeriodDays(satellite) / 16
|
||||||
|
maxStep := 2.0 / 24.0
|
||||||
|
if step > maxStep {
|
||||||
|
return maxStep
|
||||||
|
}
|
||||||
|
return step
|
||||||
|
}
|
||||||
|
|
||||||
|
func jupiterGalileanBoundaryStep(satellite int) float64 {
|
||||||
|
step := jupiterGalileanOrbitPeriodDays(satellite) / 32
|
||||||
|
if step > jupiterGalileanBoundaryStepDays {
|
||||||
|
return jupiterGalileanBoundaryStepDays
|
||||||
|
}
|
||||||
|
return step
|
||||||
|
}
|
||||||
|
|
||||||
|
func jupiterGalileanEventMatchesDirection(eventJD, targetJD float64, direction int, includeCurrent bool) bool {
|
||||||
|
diff := eventJD - targetJD
|
||||||
|
switch {
|
||||||
|
case direction < 0 && includeCurrent:
|
||||||
|
return diff <= jupiterGalileanEventEpsilonDays
|
||||||
|
case direction < 0:
|
||||||
|
return diff < -jupiterGalileanEventEpsilonDays
|
||||||
|
case includeCurrent:
|
||||||
|
return diff >= -jupiterGalileanEventEpsilonDays
|
||||||
|
default:
|
||||||
|
return diff > jupiterGalileanEventEpsilonDays
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ellipseMetric(x, y, major, minor float64) float64 {
|
||||||
|
if major <= 0 || minor <= 0 {
|
||||||
|
return math.Inf(1)
|
||||||
|
}
|
||||||
|
return (x*x)/(major*major) + (y*y)/(minor*minor)
|
||||||
|
}
|
||||||
|
|
||||||
|
func isValidJupiterGalileanPhenomenonType(phenomenonType JupiterGalileanPhenomenonType) bool {
|
||||||
|
switch phenomenonType {
|
||||||
|
case JupiterGalileanTransit, JupiterGalileanOccultation, JupiterGalileanEclipse, JupiterGalileanShadowTransit:
|
||||||
|
return true
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func invalidJupiterGalileanPhenomenonEvent() JupiterGalileanPhenomenonEvent {
|
||||||
|
return JupiterGalileanPhenomenonEvent{
|
||||||
|
Start: math.NaN(),
|
||||||
|
Greatest: math.NaN(),
|
||||||
|
End: math.NaN(),
|
||||||
|
GreatestPhenomenon: invalidJupiterGalileanPhenomenon(),
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,145 @@
|
|||||||
|
package basic
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"math"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
const galileanEventToleranceSeconds = 480.0
|
||||||
|
|
||||||
|
type galileanEventBaselineRecord struct {
|
||||||
|
Label string `json:"label"`
|
||||||
|
Satellite int `json:"satellite"`
|
||||||
|
Type string `json:"type"`
|
||||||
|
StartUTC string `json:"start_utc"`
|
||||||
|
StartDurationMinutes float64 `json:"start_duration_minutes"`
|
||||||
|
EndUTC string `json:"end_utc"`
|
||||||
|
EndDurationMinutes float64 `json:"end_duration_minutes"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestJupiterGalileanPhenomenonEventsAgainstIMCCEBaseline(t *testing.T) {
|
||||||
|
records := loadGalileanEventBaseline(t)
|
||||||
|
maxStartDiff := 0.0
|
||||||
|
maxEndDiff := 0.0
|
||||||
|
for _, record := range records {
|
||||||
|
startUTC := mustParseRFC3339Nano(t, record.StartUTC)
|
||||||
|
endUTC := mustParseRFC3339Nano(t, record.EndUTC)
|
||||||
|
queryBefore := startUTC.Add(-12 * time.Hour)
|
||||||
|
queryAfter := endUTC.Add(12 * time.Hour)
|
||||||
|
queryMid := startUTC.Add(endUTC.Sub(startUTC) / 2)
|
||||||
|
phenomenonType := parseBasicGalileanPhenomenonType(t, record.Type)
|
||||||
|
|
||||||
|
next := NextJupiterGalileanPhenomenonEvent(Date2JDE(queryBefore.UTC()), record.Satellite, phenomenonType)
|
||||||
|
last := LastJupiterGalileanPhenomenonEvent(Date2JDE(queryAfter.UTC()), record.Satellite, phenomenonType)
|
||||||
|
closest := ClosestJupiterGalileanPhenomenonEvent(Date2JDE(queryMid.UTC()), record.Satellite, phenomenonType)
|
||||||
|
|
||||||
|
assertGalileanEventMatchesBaseline(t, record.Label+" next", next, record, startUTC, endUTC, &maxStartDiff, &maxEndDiff)
|
||||||
|
assertGalileanEventMatchesBaseline(t, record.Label+" last", last, record, startUTC, endUTC, &maxStartDiff, &maxEndDiff)
|
||||||
|
assertGalileanEventMatchesBaseline(t, record.Label+" closest", closest, record, startUTC, endUTC, &maxStartDiff, &maxEndDiff)
|
||||||
|
}
|
||||||
|
t.Logf("galilean event baseline max diff: start=%.1fs end=%.1fs", maxStartDiff, maxEndDiff)
|
||||||
|
}
|
||||||
|
|
||||||
|
func assertGalileanEventMatchesBaseline(
|
||||||
|
t *testing.T,
|
||||||
|
name string,
|
||||||
|
event JupiterGalileanPhenomenonEvent,
|
||||||
|
record galileanEventBaselineRecord,
|
||||||
|
startUTC, endUTC time.Time,
|
||||||
|
maxStartDiff, maxEndDiff *float64,
|
||||||
|
) {
|
||||||
|
t.Helper()
|
||||||
|
if !event.Valid {
|
||||||
|
t.Fatalf("%s invalid event", name)
|
||||||
|
}
|
||||||
|
if event.Satellite != record.Satellite {
|
||||||
|
t.Fatalf("%s satellite mismatch: got %d want %d", name, event.Satellite, record.Satellite)
|
||||||
|
}
|
||||||
|
if string(event.Type) != record.Type {
|
||||||
|
t.Fatalf("%s type mismatch: got %q want %q", name, event.Type, record.Type)
|
||||||
|
}
|
||||||
|
gotStart := JDE2DateByZone(event.Start, time.UTC, false)
|
||||||
|
gotEnd := JDE2DateByZone(event.End, time.UTC, false)
|
||||||
|
startDiff := math.Abs(gotStart.Sub(startUTC).Seconds())
|
||||||
|
endDiff := math.Abs(gotEnd.Sub(endUTC).Seconds())
|
||||||
|
if startDiff > *maxStartDiff {
|
||||||
|
*maxStartDiff = startDiff
|
||||||
|
}
|
||||||
|
if endDiff > *maxEndDiff {
|
||||||
|
*maxEndDiff = endDiff
|
||||||
|
}
|
||||||
|
if startDiff > galileanEventToleranceSeconds {
|
||||||
|
t.Fatalf("%s start mismatch: got %s want %s", name, gotStart.Format(time.RFC3339Nano), startUTC.Format(time.RFC3339Nano))
|
||||||
|
}
|
||||||
|
if endDiff > galileanEventToleranceSeconds {
|
||||||
|
t.Fatalf("%s end mismatch: got %s want %s", name, gotEnd.Format(time.RFC3339Nano), endUTC.Format(time.RFC3339Nano))
|
||||||
|
}
|
||||||
|
if !(event.Start <= event.Greatest && event.Greatest <= event.End) {
|
||||||
|
t.Fatalf("%s greatest not inside event: start=%.9f greatest=%.9f end=%.9f", name, event.Start, event.Greatest, event.End)
|
||||||
|
}
|
||||||
|
if !galileanPhenomenonFlag(event.GreatestPhenomenon, event.Type) {
|
||||||
|
t.Fatalf("%s greatest state is not active", name)
|
||||||
|
}
|
||||||
|
if jupiterGalileanPhenomenonMetricAt(event.Start-5.0/86400.0, event.Satellite, event.Type).active {
|
||||||
|
t.Fatalf("%s still active 5s before start", name)
|
||||||
|
}
|
||||||
|
if jupiterGalileanPhenomenonMetricAt(event.End+5.0/86400.0, event.Satellite, event.Type).active {
|
||||||
|
t.Fatalf("%s still active 5s after end", name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func galileanPhenomenonFlag(phenomenon JupiterGalileanPhenomenon, phenomenonType JupiterGalileanPhenomenonType) bool {
|
||||||
|
switch phenomenonType {
|
||||||
|
case JupiterGalileanTransit:
|
||||||
|
return phenomenon.Transit
|
||||||
|
case JupiterGalileanOccultation:
|
||||||
|
return phenomenon.Occultation
|
||||||
|
case JupiterGalileanEclipse:
|
||||||
|
return phenomenon.Eclipse
|
||||||
|
case JupiterGalileanShadowTransit:
|
||||||
|
return phenomenon.ShadowTransit
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseBasicGalileanPhenomenonType(t *testing.T, value string) JupiterGalileanPhenomenonType {
|
||||||
|
t.Helper()
|
||||||
|
switch JupiterGalileanPhenomenonType(value) {
|
||||||
|
case JupiterGalileanTransit, JupiterGalileanOccultation, JupiterGalileanEclipse, JupiterGalileanShadowTransit:
|
||||||
|
return JupiterGalileanPhenomenonType(value)
|
||||||
|
default:
|
||||||
|
t.Fatalf("unknown galilean phenomenon type %q", value)
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadGalileanEventBaseline(t *testing.T) []galileanEventBaselineRecord {
|
||||||
|
t.Helper()
|
||||||
|
path := filepath.Join("..", "jupiter", "testdata", "galilean_events_imcce_2026.json")
|
||||||
|
data, err := os.ReadFile(path)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
var records []galileanEventBaselineRecord
|
||||||
|
if err := json.Unmarshal(data, &records); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if len(records) == 0 {
|
||||||
|
t.Fatal("empty galilean event baseline")
|
||||||
|
}
|
||||||
|
return records
|
||||||
|
}
|
||||||
|
|
||||||
|
func mustParseRFC3339Nano(t *testing.T, value string) time.Time {
|
||||||
|
t.Helper()
|
||||||
|
date, err := time.Parse(time.RFC3339Nano, value)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("parse %q: %v", value, err)
|
||||||
|
}
|
||||||
|
return date
|
||||||
|
}
|
||||||
@@ -0,0 +1,864 @@
|
|||||||
|
package basic
|
||||||
|
|
||||||
|
// Code generated by scripts/jupiter-galilean-l1/main.go; DO NOT EDIT.
|
||||||
|
|
||||||
|
var jupiterGalileanL1LongPeriodTerms = [4][4][]jupiterGalileanL1Term{
|
||||||
|
{
|
||||||
|
{
|
||||||
|
{Amp: 0.0000000006827622, Period: 462.51460581, Phase: -2.709383024651},
|
||||||
|
{Amp: 0.0000000006827622, Period: -462.51460581, Phase: 2.709383056866},
|
||||||
|
{Amp: 0.0000000003280812, Period: -482.05605144, Phase: 1.755848247185},
|
||||||
|
{Amp: 0.0000000003280812, Period: 482.05605145, Phase: -1.755848223779},
|
||||||
|
{Amp: 0.0000000001706288, Period: -403.51538063, Phase: -2.610358561262},
|
||||||
|
{Amp: 0.0000000001706288, Period: 403.51538062, Phase: 2.610358537855},
|
||||||
|
{Amp: 0.0000000001855996, Period: 485.60320176, Phase: -0.112246363060},
|
||||||
|
{Amp: 0.0000000001855996, Period: -485.60320175, Phase: 0.112246363894},
|
||||||
|
{Amp: 0.0000000001507209, Period: -254.63444650, Phase: -0.161685950186},
|
||||||
|
{Amp: 0.0000000001507209, Period: 254.63444650, Phase: 0.161685950186},
|
||||||
|
{Amp: 0.0000000000724860, Period: 2059.62104200, Phase: 2.190529222727},
|
||||||
|
{Amp: 0.0000000000724860, Period: -2059.62104200, Phase: -2.190529222727},
|
||||||
|
{Amp: 0.0000000000272746, Period: -274.02279431, Phase: -1.229708703052},
|
||||||
|
{Amp: 0.0000000000272746, Period: 274.02279431, Phase: 1.229708703058},
|
||||||
|
{Amp: 0.0000000000163051, Period: 250.13067987, Phase: 2.545191166411},
|
||||||
|
{Amp: 0.0000000000163051, Period: -250.13067987, Phase: -2.545191131299},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
{Amp: 0.0000962629174333, Period: -462.51460360, Phase: 1.138619193016},
|
||||||
|
{Amp: 0.0000962629174333, Period: 462.51460361, Phase: -1.138619147350},
|
||||||
|
{Amp: 0.0000485401798038, Period: 482.05605148, Phase: -0.185051689928},
|
||||||
|
{Amp: 0.0000485401798038, Period: -482.05605148, Phase: 0.185051701631},
|
||||||
|
{Amp: 0.0000449408708250, Period: -2059.62271753, Phase: 2.521072536783},
|
||||||
|
{Amp: 0.0000449408708250, Period: 2059.62271753, Phase: -2.521072536783},
|
||||||
|
{Amp: 0.0000276550525131, Period: -485.60320179, Phase: -1.458550472778},
|
||||||
|
{Amp: 0.0000276550525131, Period: 485.60320179, Phase: 1.458550472730},
|
||||||
|
{Amp: 0.0000209039435245, Period: -403.51538158, Phase: 2.102023003408},
|
||||||
|
{Amp: 0.0000209039435245, Period: 403.51538158, Phase: -2.102022944892},
|
||||||
|
{Amp: 0.0000186178298694, Period: 4332.93914977, Phase: 2.108666755900},
|
||||||
|
{Amp: 0.0000186178298694, Period: -4332.93914965, Phase: -2.108666744160},
|
||||||
|
{Amp: 0.0000117220266508, Period: 254.63444653, Phase: 1.732483394545},
|
||||||
|
{Amp: 0.0000117220266508, Period: -254.63444653, Phase: -1.732483394547},
|
||||||
|
{Amp: 0.0000080156582120, Period: -66002.48715215, Phase: 0.073578008624},
|
||||||
|
{Amp: 0.0000080156582120, Period: 66002.48716936, Phase: -0.073578000960},
|
||||||
|
{Amp: 0.0000041114727746, Period: -11428.11256828, Phase: 2.016207414938},
|
||||||
|
{Amp: 0.0000041114727746, Period: 11428.11256829, Phase: -2.016207414823},
|
||||||
|
{Amp: 0.0000043608559052, Period: 14133.23625267, Phase: 0.738064832500},
|
||||||
|
{Amp: 0.0000043608559052, Period: -14133.23624426, Phase: -0.738064751637},
|
||||||
|
{Amp: 0.0000037682740860, Period: 9692.27262460, Phase: 1.002642850091},
|
||||||
|
{Amp: 0.0000037682740860, Period: -9692.27262347, Phase: -1.002642825467},
|
||||||
|
{Amp: 0.0000026598151336, Period: 2166.51917628, Phase: -0.118101155294},
|
||||||
|
{Amp: 0.0000026598151336, Period: -2166.51917619, Phase: 0.118101190565},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
{Amp: 0.0041510849668155, Period: -486.80959494, Phase: 2.009725283179},
|
||||||
|
{Amp: 0.0000352747346169, Period: 49367.32662218, Phase: 0.253909334368},
|
||||||
|
{Amp: 0.0000198194483636, Period: 195946.92419811, Phase: 1.897179483608},
|
||||||
|
{Amp: 0.0000146399842989, Period: 2358.32875876, Phase: 1.478395862207},
|
||||||
|
{Amp: 0.0000052220588690, Period: 9267.76794016, Phase: 2.447815000610},
|
||||||
|
{Amp: 0.0000022044764663, Period: -237.17561538, Phase: -1.564292084282},
|
||||||
|
{Amp: 0.0000015410375360, Period: 2265.74291645, Phase: -1.823335732733},
|
||||||
|
{Amp: 0.0000012193607962, Period: -637.45860369, Phase: -1.957542685623},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
{Amp: 0.0003142172466014, Period: -2714.00626203, Phase: 0.017009891895},
|
||||||
|
{Amp: 0.0000904169207946, Period: -11038.50115941, Phase: 2.090564874290},
|
||||||
|
{Amp: 0.0000164452324013, Period: -50300.46261866, Phase: -1.808667780761},
|
||||||
|
{Amp: 0.0000055424829091, Period: -205593.77774416, Phase: 2.907426236797},
|
||||||
|
{Amp: 0.0000035856270353, Period: -248.89302051, Phase: 1.928881950702},
|
||||||
|
{Amp: 0.0000024180760140, Period: 2166.34061401, Phase: 2.301952132272},
|
||||||
|
{Amp: 0.0000008673084930, Period: -4333.05393383, Phase: 1.875397350363},
|
||||||
|
{Amp: 0.0000003176227277, Period: -267.38514937, Phase: 0.860886171255},
|
||||||
|
{Amp: 0.0000003152816608, Period: 1444.25588657, Phase: 2.837887905087},
|
||||||
|
{Amp: 0.0000002338676726, Period: 4332.83109038, Phase: -2.991881157330},
|
||||||
|
{Amp: 0.0000001754553689, Period: -244.58835226, Phase: -0.454720421567},
|
||||||
|
{Amp: 0.0000001286319583, Period: -243.40543219, Phase: 1.572977535435},
|
||||||
|
{Amp: 0.0000000967213304, Period: -2166.50569931, Phase: -1.943113737418},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
{
|
||||||
|
{Amp: 0.0000000090655385, Period: 482.05605605, Phase: 1.385772645301},
|
||||||
|
{Amp: 0.0000000090655385, Period: -482.05605605, Phase: -1.385772633599},
|
||||||
|
{Amp: 0.0000000047683565, Period: -485.60320520, Phase: -3.029413042035},
|
||||||
|
{Amp: 0.0000000047683565, Period: 485.60320520, Phase: 3.029413042096},
|
||||||
|
{Amp: 0.0000000035969671, Period: 462.51472501, Phase: -2.707591871848},
|
||||||
|
{Amp: 0.0000000035969671, Period: -462.51472501, Phase: 2.707591896577},
|
||||||
|
{Amp: 0.0000000008232399, Period: -403.51538403, Phase: 0.531187191370},
|
||||||
|
{Amp: 0.0000000008232399, Period: 403.51538403, Phase: -0.531187167963},
|
||||||
|
{Amp: 0.0000000005125913, Period: 2059.62196661, Phase: -0.950637805889},
|
||||||
|
{Amp: 0.0000000005125913, Period: -2059.62196661, Phase: 0.950637805889},
|
||||||
|
{Amp: 0.0000000003478592, Period: 250.13068971, Phase: -0.596195111066},
|
||||||
|
{Amp: 0.0000000003478592, Period: -250.13068971, Phase: 0.596195134473},
|
||||||
|
{Amp: 0.0000000002908957, Period: -254.63444649, Phase: 2.979903215092},
|
||||||
|
{Amp: 0.0000000002908957, Period: 254.63444649, Phase: -2.979903215092},
|
||||||
|
{Amp: 0.0000000001698689, Period: -241.91156453, Phase: 1.867830046014},
|
||||||
|
{Amp: 0.0000000001698689, Period: 241.91156453, Phase: -1.867830013805},
|
||||||
|
{Amp: 0.0000000001896089, Period: -248.89380995, Phase: 2.620073542296},
|
||||||
|
{Amp: 0.0000000001896089, Period: 248.89380995, Phase: -2.620073542267},
|
||||||
|
{Amp: 0.0000000001269077, Period: -274.02280385, Phase: 1.911888048968},
|
||||||
|
{Amp: 0.0000000001269077, Period: 274.02280385, Phase: -1.911888048527},
|
||||||
|
{Amp: 0.0000000001579746, Period: -241.02802357, Phase: -2.771389990456},
|
||||||
|
{Amp: 0.0000000001579746, Period: 241.02802357, Phase: 2.771389988517},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
{Amp: 0.0004288216586468, Period: 482.05605608, Phase: 2.956569304014},
|
||||||
|
{Amp: 0.0004288216586468, Period: -482.05605608, Phase: -2.956569292310},
|
||||||
|
{Amp: 0.0002274791437543, Period: 485.60320524, Phase: -1.682976093416},
|
||||||
|
{Amp: 0.0002274791437547, Period: -485.60320524, Phase: 1.682976093476},
|
||||||
|
{Amp: 0.0001537125039667, Period: -462.51473011, Phase: 1.136719181022},
|
||||||
|
{Amp: 0.0001537125039667, Period: 462.51473011, Phase: -1.136719137618},
|
||||||
|
{Amp: 0.0000917031775902, Period: -4332.93871803, Phase: -2.108663081132},
|
||||||
|
{Amp: 0.0000917031775902, Period: 4332.93871814, Phase: 2.108663092871},
|
||||||
|
{Amp: 0.0000991193072392, Period: 2059.37736140, Phase: 0.507502870024},
|
||||||
|
{Amp: 0.0000991193072392, Period: -2059.37736140, Phase: -0.507502870018},
|
||||||
|
{Amp: 0.0000316144888598, Period: -403.51538512, Phase: -1.039617955783},
|
||||||
|
{Amp: 0.0000316144888598, Period: 403.51538512, Phase: 1.039617990892},
|
||||||
|
{Amp: 0.0000174586375148, Period: -210240.18087863, Phase: 1.750407353612},
|
||||||
|
{Amp: 0.0000174586375134, Period: 210240.18167626, Phase: -1.750407317793},
|
||||||
|
{Amp: 0.0000162854547323, Period: 50284.65590194, Phase: -0.490087477075},
|
||||||
|
{Amp: 0.0000162854547324, Period: -50284.65590078, Phase: 0.490087477963},
|
||||||
|
{Amp: 0.0000102913236930, Period: -2166.51734380, Phase: -3.053369407734},
|
||||||
|
{Amp: 0.0000102913236930, Period: 2166.51734389, Phase: 3.053369442987},
|
||||||
|
{Amp: 0.0000084014158127, Period: 250.13068976, Phase: 0.974602969007},
|
||||||
|
{Amp: 0.0000084014158127, Period: -250.13068976, Phase: -0.974602933897},
|
||||||
|
{Amp: 0.0000070356577800, Period: 254.63444650, Phase: -1.409106426418},
|
||||||
|
{Amp: 0.0000070356577800, Period: -254.63444650, Phase: 1.409106424511},
|
||||||
|
{Amp: 0.0000070814366803, Period: -9676.80000028, Phase: 2.607323588975},
|
||||||
|
{Amp: 0.0000070814366803, Period: 9676.80000028, Phase: -2.607323588975},
|
||||||
|
{Amp: 0.0000050373040117, Period: 11362.43599928, Phase: 0.577394871339},
|
||||||
|
{Amp: 0.0000050373040117, Period: -11362.43599928, Phase: -0.577394871340},
|
||||||
|
{Amp: 0.0000053299308810, Period: 2078.23892619, Phase: -0.021997688216},
|
||||||
|
{Amp: 0.0000053299308811, Period: -2078.23892619, Phase: 0.021997688222},
|
||||||
|
{Amp: 0.0000047325683320, Period: 67215.42643142, Phase: 0.141213175007},
|
||||||
|
{Amp: 0.0000047325683325, Period: -67215.42643262, Phase: -0.141213175548},
|
||||||
|
{Amp: 0.0000039214851670, Period: 241.91154490, Phase: -0.297536953260},
|
||||||
|
{Amp: 0.0000039214851670, Period: -241.91154490, Phase: 0.297536962655},
|
||||||
|
{Amp: 0.0000045554036662, Period: 248.89381141, Phase: -1.049231904996},
|
||||||
|
{Amp: 0.0000045554036661, Period: -248.89381141, Phase: 1.049231905683},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
{Amp: 0.0093589104136341, Period: -486.80959494, Phase: -1.131867355050},
|
||||||
|
{Amp: 0.0001980963564781, Period: 9267.59815105, Phase: -0.700195659441},
|
||||||
|
{Amp: 0.0002139036390350, Period: 49367.31948358, Phase: 0.253903838790},
|
||||||
|
{Amp: 0.0001210388158965, Period: 196002.76969709, Phase: 1.900452411216},
|
||||||
|
{Amp: 0.0000139052321679, Period: -242.21058266, Phase: -2.517653962057},
|
||||||
|
{Amp: 0.0000073925894084, Period: -243.10282624, Phase: 2.121892237182},
|
||||||
|
{Amp: 0.0000051968296512, Period: -237.17564686, Phase: 1.575472602669},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
{Amp: 0.0040404917832303, Period: -11038.50070470, Phase: 2.090572293375},
|
||||||
|
{Amp: 0.0002200421034564, Period: -50300.46217493, Phase: -1.808667829190},
|
||||||
|
{Amp: 0.0000590282470983, Period: -205590.83436962, Phase: 2.907535129258},
|
||||||
|
{Amp: 0.0000105030331400, Period: -2714.00565902, Phase: -3.124346970559},
|
||||||
|
{Amp: 0.0000102943248250, Period: -248.89302050, Phase: -1.212709792839},
|
||||||
|
{Amp: 0.0000072600013020, Period: 2166.34096750, Phase: 2.302095210918},
|
||||||
|
{Amp: 0.0000018391258758, Period: -4333.05816401, Phase: -1.266797143358},
|
||||||
|
{Amp: 0.0000014880605763, Period: -244.58836553, Phase: -0.455088252478},
|
||||||
|
{Amp: 0.0000008828196274, Period: 1810.91607041, Phase: -0.522805812968},
|
||||||
|
{Amp: 0.0000008714042768, Period: 4332.82720044, Phase: -2.991672113265},
|
||||||
|
{Amp: 0.0000008536188044, Period: 1444.25631462, Phase: 2.838189555021},
|
||||||
|
{Amp: 0.0000006846214331, Period: -243.40441644, Phase: 1.606346686085},
|
||||||
|
{Amp: 0.0000004471826348, Period: -267.38514860, Phase: -2.280679314445},
|
||||||
|
{Amp: 0.0000003034392168, Period: -243.69327303, Phase: 1.113159867990},
|
||||||
|
{Amp: 0.0000001792048645, Period: -3111.52953257, Phase: 1.563078661742},
|
||||||
|
{Amp: 0.0000001799083735, Period: 7133.00782851, Phase: -0.505644195617},
|
||||||
|
{Amp: 0.0000001062153531, Period: -11679.84994953, Phase: 1.447094472227},
|
||||||
|
{Amp: 0.0000001098546626, Period: 1277.13984095, Phase: 0.006562244228},
|
||||||
|
{Amp: 0.0000001083128732, Period: -10479.05596460, Phase: -0.636294555123},
|
||||||
|
{Amp: 0.0000000768496749, Period: 1083.20345403, Phase: -2.906284048843},
|
||||||
|
{Amp: 0.0000000692273841, Period: 2076.86504405, Phase: -2.906237638896},
|
||||||
|
{Amp: 0.0000000676969224, Period: -14141.63136873, Phase: 0.044806456994},
|
||||||
|
{Amp: 0.0000000621559952, Period: -461.88727875, Phase: 0.684245423843},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
{
|
||||||
|
{Amp: 0.0000000054767566, Period: 462.51465961, Phase: 0.433012270674},
|
||||||
|
{Amp: 0.0000000054767566, Period: -462.51465961, Phase: -0.433012249211},
|
||||||
|
{Amp: 0.0000000056033730, Period: -482.05605692, Phase: 1.755815363396},
|
||||||
|
{Amp: 0.0000000056033730, Period: 482.05605692, Phase: -1.755815351694},
|
||||||
|
{Amp: 0.0000000028921342, Period: 485.60320600, Phase: -0.112163580985},
|
||||||
|
{Amp: 0.0000000028921342, Period: -485.60320600, Phase: 0.112163580799},
|
||||||
|
{Amp: 0.0000000003409630, Period: 254.63444667, Phase: -2.979904123822},
|
||||||
|
{Amp: 0.0000000003409630, Period: -254.63444667, Phase: 2.979904123823},
|
||||||
|
{Amp: 0.0000000002216888, Period: 250.13068507, Phase: 2.545311746332},
|
||||||
|
{Amp: 0.0000000002216888, Period: -250.13068507, Phase: -2.545311746332},
|
||||||
|
{Amp: 0.0000000001418329, Period: -241.91155085, Phase: -1.273238319314},
|
||||||
|
{Amp: 0.0000000001418329, Period: 241.91155085, Phase: 1.273238319313},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
{Amp: 0.0001155398943113, Period: 4332.93886608, Phase: 2.108691866697},
|
||||||
|
{Amp: 0.0001155398943113, Period: -4332.93886574, Phase: -2.108691831480},
|
||||||
|
{Amp: 0.0000914317982059, Period: -482.05605702, Phase: 0.185017491519},
|
||||||
|
{Amp: 0.0000914317982059, Period: 482.05605703, Phase: -0.185017456411},
|
||||||
|
{Amp: 0.0000756189389102, Period: 462.51466388, Phase: 2.003875199838},
|
||||||
|
{Amp: 0.0000756189389102, Period: -462.51466387, Phase: -2.003875175333},
|
||||||
|
{Amp: 0.0000477739034923, Period: 485.60320603, Phase: 1.458631213994},
|
||||||
|
{Amp: 0.0000477739034926, Period: -485.60320603, Phase: -1.458631213811},
|
||||||
|
{Amp: 0.0000271961236501, Period: 210274.48426729, Phase: -1.785123499277},
|
||||||
|
{Amp: 0.0000271961236540, Period: -210274.48346940, Phase: 1.785123534351},
|
||||||
|
{Amp: 0.0000244626823237, Period: -50284.47674577, Phase: 0.491194059142},
|
||||||
|
{Amp: 0.0000244626823247, Period: 50284.47674762, Phase: -0.491194057704},
|
||||||
|
{Amp: 0.0000077362945254, Period: 9642.10026223, Phase: 2.580679451841},
|
||||||
|
{Amp: 0.0000077362945254, Period: -9642.10026227, Phase: -2.580679452618},
|
||||||
|
{Amp: 0.0000084883673229, Period: 2059.54227670, Phase: -2.558255736482},
|
||||||
|
{Amp: 0.0000084883673229, Period: -2059.54227670, Phase: 2.558255736428},
|
||||||
|
{Amp: 0.0000067827229369, Period: -11287.76456818, Phase: -0.945782523799},
|
||||||
|
{Amp: 0.0000067827229369, Period: 11287.76456818, Phase: 0.945782523799},
|
||||||
|
{Amp: 0.0000037731001815, Period: -67979.99326489, Phase: -0.181520301079},
|
||||||
|
{Amp: 0.0000037731001804, Period: 67979.99326458, Phase: 0.181520300800},
|
||||||
|
{Amp: 0.0000032076570609, Period: 2166.51777632, Phase: 3.080339678337},
|
||||||
|
{Amp: 0.0000032076570609, Period: -2166.51777629, Phase: -3.080339666591},
|
||||||
|
{Amp: 0.0000026285122860, Period: -254.63444659, Phase: 1.409111585164},
|
||||||
|
{Amp: 0.0000026285122860, Period: 254.63444659, Phase: -1.409111565386},
|
||||||
|
{Amp: 0.0000028282486512, Period: 14014.13678272, Phase: 2.891690797470},
|
||||||
|
{Amp: 0.0000028282486512, Period: -14014.13678272, Phase: -2.891690797471},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
{Amp: 0.0014289811307319, Period: 49367.34034574, Phase: 0.253921225395},
|
||||||
|
{Amp: 0.0007710931226760, Period: 195955.54100807, Phase: 1.897688885982},
|
||||||
|
{Amp: 0.0005925911780766, Period: -486.80959494, Phase: 2.009725311237},
|
||||||
|
{Amp: 0.0000070808673396, Period: -242.21058256, Phase: 0.623939292029},
|
||||||
|
{Amp: 0.0000070804404020, Period: 9270.75474882, Phase: 2.567494996183},
|
||||||
|
{Amp: 0.0000048299146941, Period: 2265.73531869, Phase: 1.315002005009},
|
||||||
|
{Amp: 0.0000038142769361, Period: -243.10282598, Phase: -1.019683047586},
|
||||||
|
{Amp: 0.0000022651772374, Period: 4332.66561900, Phase: 0.788371189690},
|
||||||
|
{Amp: 0.0000023143850535, Period: 2190.53482611, Phase: -0.326448440973},
|
||||||
|
{Amp: 0.0000007268381420, Period: -237.17572590, Phase: -1.570723011296},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
{Amp: 0.0015932721570848, Period: -50300.46317781, Phase: -1.808668110228},
|
||||||
|
{Amp: 0.0003513347911037, Period: -205594.76675742, Phase: 2.907439275438},
|
||||||
|
{Amp: 0.0001441929255483, Period: -11038.50233695, Phase: -1.051046669426},
|
||||||
|
{Amp: 0.0000157303527750, Period: 2166.34184154, Phase: 2.302490124225},
|
||||||
|
{Amp: 0.0000025161319881, Period: -4333.06554786, Phase: -1.267594020600},
|
||||||
|
{Amp: 0.0000020438305183, Period: 4332.81770831, Phase: -2.991340015519},
|
||||||
|
{Amp: 0.0000017939612784, Period: 1444.25697259, Phase: 2.838852837237},
|
||||||
|
{Amp: 0.0000013614276895, Period: -248.89302051, Phase: 1.928883308456},
|
||||||
|
{Amp: 0.0000008996109017, Period: 2076.86534892, Phase: -2.906045098558},
|
||||||
|
{Amp: 0.0000008702078430, Period: -2714.00571514, Phase: -3.123659682744},
|
||||||
|
{Amp: 0.0000004371144064, Period: -244.58835722, Phase: 2.686731305626},
|
||||||
|
{Amp: 0.0000001926397869, Period: 2142.19483368, Phase: -1.985925544377},
|
||||||
|
{Amp: 0.0000002174259374, Period: -243.40560179, Phase: -1.574163434803},
|
||||||
|
{Amp: 0.0000001589279656, Period: 1083.20364160, Phase: -2.905875929922},
|
||||||
|
{Amp: 0.0000001432228753, Period: -3989.29234760, Phase: -2.346001284423},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
{
|
||||||
|
{Amp: 0.0000000001238821, Period: 9641.95997751, Phase: -2.136521368996},
|
||||||
|
{Amp: 0.0000000001238821, Period: -9641.95997747, Phase: 2.136521369695},
|
||||||
|
{Amp: 0.0000000000937288, Period: 11292.66166947, Phase: 2.658438162555},
|
||||||
|
{Amp: 0.0000000000937288, Period: -11292.66166870, Phase: -2.658438150854},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
{Amp: 0.0002793020061912, Period: -4332.93860171, Phase: -2.108786357946},
|
||||||
|
{Amp: 0.0002793020061912, Period: 4332.93860182, Phase: 2.108786369685},
|
||||||
|
{Amp: 0.0001902906934088, Period: 211344.06814397, Phase: 1.346615691710},
|
||||||
|
{Amp: 0.0001902906934113, Period: -211344.06760662, Phase: -1.346615668368},
|
||||||
|
{Amp: 0.0000167518242157, Period: -50289.57428101, Phase: -2.654427789777},
|
||||||
|
{Amp: 0.0000167518242264, Period: 50289.57429022, Phase: 2.654427797106},
|
||||||
|
{Amp: 0.0000092970467736, Period: -2166.51555964, Phase: -2.989880275328},
|
||||||
|
{Amp: 0.0000092970467736, Period: 2166.51555970, Phase: 2.989880298849},
|
||||||
|
{Amp: 0.0000085219011443, Period: 9641.95655559, Phase: -0.565865906865},
|
||||||
|
{Amp: 0.0000085219011444, Period: -9641.95655560, Phase: 0.565865906783},
|
||||||
|
{Amp: 0.0000075696916557, Period: -11291.33721849, Phase: 2.059123798134},
|
||||||
|
{Amp: 0.0000075696916557, Period: 11291.33721925, Phase: -2.059123786437},
|
||||||
|
{Amp: 0.0000058058699386, Period: -67440.56838013, Phase: 2.926756672932},
|
||||||
|
{Amp: 0.0000058058699344, Period: 67440.56837337, Phase: -2.926756675958},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
{Amp: 0.0073755808467977, Period: 195950.90846202, Phase: 1.897427359615},
|
||||||
|
{Amp: 0.0001561131605348, Period: 49367.23074528, Phase: -2.887649087328},
|
||||||
|
{Amp: 0.0000540660842731, Period: 2190.52954139, Phase: -0.328140763057},
|
||||||
|
{Amp: 0.0000090411191759, Period: 4332.68219367, Phase: 0.789967351008},
|
||||||
|
{Amp: 0.0000060406575087, Period: 1454.71609134, Phase: -0.020851153251},
|
||||||
|
{Amp: 0.0000028551622596, Period: -198930.12372162, Phase: -0.327774752201},
|
||||||
|
{Amp: 0.0000026588026505, Period: 4430.38639549, Phase: 2.290679594163},
|
||||||
|
{Amp: 0.0000016317841395, Period: 4238.97138743, Phase: 2.407098908451},
|
||||||
|
{Amp: 0.0000016159095087, Period: -4430.93037398, Phase: -1.783540964911},
|
||||||
|
{Amp: 0.0000012029942283, Period: 2265.73177106, Phase: -1.827994616498},
|
||||||
|
{Amp: 0.0000008869382117, Period: 9192.14352819, Phase: -0.159251424576},
|
||||||
|
{Amp: 0.0000004518928658, Period: 2164.87438400, Phase: 0.627199957599},
|
||||||
|
{Amp: 0.0000004739086661, Period: 10677.43163337, Phase: -1.721255818131},
|
||||||
|
{Amp: 0.0000005321318002, Period: 1089.21699649, Phase: 0.749679902895},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
{Amp: 0.0022453891791894, Period: -205593.16871630, Phase: 2.907607823156},
|
||||||
|
{Amp: 0.0002604479450559, Period: -50300.45144562, Phase: 1.332921952672},
|
||||||
|
{Amp: 0.0000332112143230, Period: 2166.33408559, Phase: 2.299271304157},
|
||||||
|
{Amp: 0.0000049727136261, Period: -4333.06038571, Phase: -1.266951160328},
|
||||||
|
{Amp: 0.0000049416729114, Period: -11038.56696703, Phase: -1.052098159774},
|
||||||
|
{Amp: 0.0000043945193428, Period: 4332.82948108, Phase: -2.986950453661},
|
||||||
|
{Amp: 0.0000037630501589, Period: 1444.25618193, Phase: 2.837982671867},
|
||||||
|
{Amp: 0.0000030823418750, Period: 2143.47705615, Phase: -1.442836410158},
|
||||||
|
{Amp: 0.0000004637177865, Period: -4242.89245749, Phase: 2.432125315770},
|
||||||
|
{Amp: 0.0000004719790711, Period: 4426.19806280, Phase: 0.277692210330},
|
||||||
|
{Amp: 0.0000003467132626, Period: 1433.98920963, Phase: -0.966205311247},
|
||||||
|
{Amp: 0.0000003497224175, Period: 2076.87114473, Phase: 0.238492877349},
|
||||||
|
{Amp: 0.0000003324412570, Period: 1083.20735284, Phase: -2.900230549782},
|
||||||
|
{Amp: 0.0000001945374351, Period: 2181.03566283, Phase: 2.659258242659},
|
||||||
|
{Amp: 0.0000001727743329, Period: -2166.53579568, Phase: -1.915921390647},
|
||||||
|
{Amp: 0.0000001485176585, Period: 4243.19386437, Phase: 1.378730646509},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
var jupiterGalileanL1CrossPeriodTerms = [4][4][]jupiterGalileanL1Term{
|
||||||
|
{
|
||||||
|
{
|
||||||
|
{Amp: 0.0028210960212903, Period: 0.00000000, Phase: 0.000000000000},
|
||||||
|
{Amp: 0.0000000381012294, Period: 1.76273174, Phase: -2.643895074882},
|
||||||
|
{Amp: 0.0000000381012294, Period: -1.76273174, Phase: 2.643895075707},
|
||||||
|
{Amp: 0.0000000086168826, Period: 3.52546349, Phase: 1.819648782093},
|
||||||
|
{Amp: 0.0000000086168826, Period: -3.52546349, Phase: -1.819648774502},
|
||||||
|
{Amp: 0.0000000090450162, Period: -0.88136587, Phase: -0.995547070485},
|
||||||
|
{Amp: 0.0000000090450162, Period: 0.88136587, Phase: 0.995547070485},
|
||||||
|
{Amp: 0.0000000047397043, Period: -0.78343633, Phase: 2.807162813908},
|
||||||
|
{Amp: 0.0000000047397043, Period: 0.78343633, Phase: -2.807162778199},
|
||||||
|
{Amp: 0.0000000046098133, Period: -0.58757725, Phase: 1.648484811816},
|
||||||
|
{Amp: 0.0000000046098133, Period: 0.58757725, Phase: -1.648484776721},
|
||||||
|
{Amp: 0.0000000050863040, Period: 0.70509270, Phase: 2.815055976377},
|
||||||
|
{Amp: 0.0000000050863040, Period: -0.70509270, Phase: -2.815055941282},
|
||||||
|
{Amp: 0.0000000029290802, Period: -2.35030899, Phase: -1.158674633569},
|
||||||
|
{Amp: 0.0000000029290802, Period: 2.35030899, Phase: 1.158674656974},
|
||||||
|
{Amp: 0.0000000017446377, Period: 0.50363764, Phase: 0.171224706165},
|
||||||
|
{Amp: 0.0000000017446377, Period: -0.50363764, Phase: -0.171224671071},
|
||||||
|
{Amp: 0.0000000015421426, Period: 0.98945742, Phase: -2.666154170053},
|
||||||
|
{Amp: 0.0000000015421426, Period: -0.98945742, Phase: 2.666154193457},
|
||||||
|
{Amp: 0.0000000018109074, Period: -1.17515450, Phase: 0.824225124590},
|
||||||
|
{Amp: 0.0000000018109074, Period: 1.17515450, Phase: -0.824225124846},
|
||||||
|
{Amp: 0.0000000010397325, Period: -0.52701992, Phase: 1.990665563264},
|
||||||
|
{Amp: 0.0000000010397325, Period: 0.52701992, Phase: -1.990665563264},
|
||||||
|
{Amp: 0.0000000005841286, Period: 0.49036751, Phase: 0.489778750861},
|
||||||
|
{Amp: 0.0000000005841286, Period: -0.49036751, Phase: -0.489778750861},
|
||||||
|
{Amp: 0.0000000004015988, Period: 0.61965131, Phase: 2.472795335277},
|
||||||
|
{Amp: 0.0000000004015988, Period: -0.61965131, Phase: -2.472795323587},
|
||||||
|
{Amp: 0.0000000003507059, Period: -0.88493019, Phase: 1.953447599078},
|
||||||
|
{Amp: 0.0000000003507059, Period: 0.88493019, Phase: -1.953447575672},
|
||||||
|
{Amp: 0.0000000003654755, Period: 0.75178915, Phase: 0.653129619279},
|
||||||
|
{Amp: 0.0000000003654755, Period: -0.75178915, Phase: -0.653129619279},
|
||||||
|
{Amp: 0.0000000002876544, Period: 0.65963828, Phase: -0.857967356136},
|
||||||
|
{Amp: 0.0000000002876544, Period: -0.65963828, Phase: 0.857967374635},
|
||||||
|
{Amp: 0.0000000002179774, Period: 0.95555745, Phase: -1.167011088750},
|
||||||
|
{Amp: 0.0000000002179774, Period: -0.95555745, Phase: 1.167011088750},
|
||||||
|
{Amp: 0.0000000001614172, Period: 1.31085840, Phase: -2.986185416058},
|
||||||
|
{Amp: 0.0000000001614172, Period: -1.31085840, Phase: 2.986185416058},
|
||||||
|
{Amp: 0.0000000001716490, Period: 1.97891465, Phase: 1.799472338743},
|
||||||
|
{Amp: 0.0000000001716490, Period: -1.97891465, Phase: -1.799472336412},
|
||||||
|
{Amp: 0.0000000000827916, Period: 1.41018540, Phase: 2.978320583302},
|
||||||
|
{Amp: 0.0000000000827916, Period: -1.41018540, Phase: -2.978320559896},
|
||||||
|
{Amp: 0.0000000000763996, Period: 0.84151364, Phase: -1.827565110733},
|
||||||
|
{Amp: 0.0000000000763996, Period: -0.84151364, Phase: 1.827565116962},
|
||||||
|
{Amp: 0.0000000000806455, Period: 2.08677541, Phase: 1.477346039152},
|
||||||
|
{Amp: 0.0000000000806455, Period: -2.08677541, Phase: -1.477346042522},
|
||||||
|
{Amp: 0.0000000000853835, Period: 1.76920091, Phase: -1.211307445204},
|
||||||
|
{Amp: 0.0000000000853835, Period: -1.76920091, Phase: 1.211307445226},
|
||||||
|
{Amp: 0.0000000000761656, Period: 1.40607178, Phase: -1.650855579663},
|
||||||
|
{Amp: 0.0000000000761656, Period: -1.40607178, Phase: 1.650855579663},
|
||||||
|
{Amp: 0.0000000000594344, Period: -0.88511089, Phase: 0.951017241368},
|
||||||
|
{Amp: 0.0000000000594344, Period: 0.88511089, Phase: -0.951017239898},
|
||||||
|
{Amp: 0.0000000000564629, Period: 0.49472871, Phase: 0.950314977999},
|
||||||
|
{Amp: 0.0000000000564629, Period: -0.49472871, Phase: -0.950314966307},
|
||||||
|
{Amp: 0.0000000000493043, Period: -5.11358278, Phase: 0.341909441869},
|
||||||
|
{Amp: 0.0000000000493043, Period: 5.11358278, Phase: -0.341909430740},
|
||||||
|
{Amp: 0.0000000000428597, Period: 1.05181306, Phase: -2.914901885532},
|
||||||
|
{Amp: 0.0000000000428597, Period: -1.05181306, Phase: 2.914901883107},
|
||||||
|
{Amp: 0.0000000000438860, Period: 3.49879406, Phase: 0.481234140725},
|
||||||
|
{Amp: 0.0000000000438860, Period: -3.49879406, Phase: -0.481234137799},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
{Amp: 1.4462132960212235, Period: 0.00000000, Phase: 0.000000000000},
|
||||||
|
{Amp: 0.0000251792213075, Period: 1.76273177, Phase: -1.071369371364},
|
||||||
|
{Amp: 0.0000251792213075, Period: -1.76273177, Phase: 1.071369371632},
|
||||||
|
{Amp: 0.0000222206385058, Period: -3.52546349, Phase: 2.892741097493},
|
||||||
|
{Amp: 0.0000222206385058, Period: 3.52546349, Phase: -2.892741074803},
|
||||||
|
{Amp: 0.0000054507134660, Period: 2.35030899, Phase: 2.729470598014},
|
||||||
|
{Amp: 0.0000054507134660, Period: -2.35030899, Phase: -2.729470598014},
|
||||||
|
{Amp: 0.0000059524877849, Period: 0.88136587, Phase: 2.566011851738},
|
||||||
|
{Amp: 0.0000059524877849, Period: -0.88136587, Phase: -2.566011851738},
|
||||||
|
{Amp: 0.0000028787912389, Period: -0.78343633, Phase: 1.236365304804},
|
||||||
|
{Amp: 0.0000028787912389, Period: 0.78343633, Phase: -1.236365280939},
|
||||||
|
{Amp: 0.0000030726401981, Period: -0.70509270, Phase: 1.897339035434},
|
||||||
|
{Amp: 0.0000030726401981, Period: 0.70509270, Phase: -1.897339023744},
|
||||||
|
{Amp: 0.0000023908706663, Period: -2056.36375976, Phase: -0.211262261618},
|
||||||
|
{Amp: 0.0000023908713552, Period: 2056.36431648, Phase: 0.211277724557},
|
||||||
|
{Amp: 0.0000022777007661, Period: 274.03235254, Phase: 0.387713436622},
|
||||||
|
{Amp: 0.0000022777007661, Period: -274.03235254, Phase: -0.387713436591},
|
||||||
|
{Amp: 0.0000025590603468, Period: -0.58757725, Phase: 0.077681276181},
|
||||||
|
{Amp: 0.0000025590603468, Period: 0.58757725, Phase: -0.077681252776},
|
||||||
|
{Amp: 0.0000021602067349, Period: 400.78715490, Phase: 0.013492534637},
|
||||||
|
{Amp: 0.0000021602067349, Period: -400.78715480, Phase: -0.013492473587},
|
||||||
|
{Amp: 0.0000018842049141, Period: 1.17515450, Phase: 0.746568951200},
|
||||||
|
{Amp: 0.0000018842049141, Period: -1.17515450, Phase: -0.746568951200},
|
||||||
|
{Amp: 0.0000015701869124, Period: -249.77392671, Phase: 2.493981352238},
|
||||||
|
{Amp: 0.0000015701869123, Period: 249.77392671, Phase: -2.493981352263},
|
||||||
|
{Amp: 0.0000008114937768, Period: -10400.18449376, Phase: -1.567931706749},
|
||||||
|
{Amp: 0.0000008114933753, Period: 10400.18102399, Phase: 1.567928771616},
|
||||||
|
{Amp: 0.0000012168267714, Period: 2471.07130547, Phase: -0.717589824480},
|
||||||
|
{Amp: 0.0000012168291116, Period: -2471.05875738, Phase: 0.717795029776},
|
||||||
|
{Amp: 0.0000010144950846, Period: -0.98945742, Phase: 1.095489123829},
|
||||||
|
{Amp: 0.0000010144950846, Period: 0.98945742, Phase: -1.095489112139},
|
||||||
|
{Amp: 0.0000009276215519, Period: 0.50363764, Phase: 1.741664442016},
|
||||||
|
{Amp: 0.0000009276215519, Period: -0.50363764, Phase: -1.741664395199},
|
||||||
|
{Amp: 0.0000009332719352, Period: -462.34993510, Phase: 3.008175468973},
|
||||||
|
{Amp: 0.0000009332719348, Period: 462.34993512, Phase: -3.008175463874},
|
||||||
|
{Amp: 0.0000001301198829, Period: 1375.10423851, Phase: -0.137037396295},
|
||||||
|
{Amp: 0.0000001301198573, Period: -1375.10422893, Phase: 0.137037333790},
|
||||||
|
{Amp: 0.0000004035364904, Period: 1668.45604889, Phase: -1.183442165366},
|
||||||
|
{Amp: 0.0000004035364083, Period: -1668.45590610, Phase: 1.183447162176},
|
||||||
|
{Amp: 0.0000006580493802, Period: -437.59465164, Phase: -3.042608902290},
|
||||||
|
{Amp: 0.0000006580493801, Period: 437.59465164, Phase: 3.042608902840},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
{Amp: 0.0006260521444113, Period: 1.76913777, Phase: 1.446188898563},
|
||||||
|
{Amp: 0.0000096819265753, Period: 3.55118107, Phase: 2.768134001457},
|
||||||
|
{Amp: 0.0000098749504021, Period: -1.75637194, Phase: 0.450761187889},
|
||||||
|
{Amp: 0.0000083063168209, Period: 7.15455323, Phase: -2.854077904786},
|
||||||
|
{Amp: 0.0000045951340101, Period: -1.40611218, Phase: -2.029833930094},
|
||||||
|
{Amp: 0.0000059689735869, Period: 0.88296443, Phase: -1.209110059349},
|
||||||
|
{Amp: 0.0000046538995236, Period: -1.17232451, Phase: -1.368864973222},
|
||||||
|
{Amp: 0.0000037405126681, Period: -0.87977305, Phase: 3.094673734781},
|
||||||
|
{Amp: 0.0000037711061757, Period: -3.50011573, Phase: 2.270416702740},
|
||||||
|
{Amp: 0.0000018698303790, Period: -2.24513352, Phase: -2.170781015720},
|
||||||
|
{Amp: 0.0000013214613496, Period: -0.70407292, Phase: 1.275017772430},
|
||||||
|
{Amp: 0.0000012707585609, Period: 16.68653361, Phase: 1.972514861645},
|
||||||
|
{Amp: 0.0000011886104747, Period: 1.17799818, Phase: 0.124242374879},
|
||||||
|
{Amp: 0.0000007689215742, Period: -2302.10434716, Phase: 0.689291076961},
|
||||||
|
{Amp: 0.0000008742035177, Period: 2493.83004733, Phase: 2.390352831189},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
{Amp: 0.0000175695395780, Period: 0.00000000, Phase: 2.415080967945},
|
||||||
|
{Amp: 0.0000000000692310, Period: -193.28859060, Phase: -2.208588622047},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
{
|
||||||
|
{Amp: 0.0044871037804314, Period: 0.00000000, Phase: 0.000000000000},
|
||||||
|
{Amp: 0.0000002162183749, Period: 3.52546349, Phase: 1.819645606290},
|
||||||
|
{Amp: 0.0000002162183749, Period: -3.52546349, Phase: -1.819645606290},
|
||||||
|
{Amp: 0.0000000801807375, Period: -2.35030899, Phase: 1.982912654261},
|
||||||
|
{Amp: 0.0000000801807375, Period: 2.35030899, Phase: -1.982912619140},
|
||||||
|
{Amp: 0.0000000509573393, Period: -1.17515450, Phase: -2.317355432932},
|
||||||
|
{Amp: 0.0000000509573393, Period: 1.17515450, Phase: 2.317355432932},
|
||||||
|
{Amp: 0.0000000462367393, Period: -7.05092698, Phase: 0.660971402306},
|
||||||
|
{Amp: 0.0000000462367393, Period: 7.05092698, Phase: -0.660971402162},
|
||||||
|
{Amp: 0.0000000255754500, Period: -1.41018540, Phase: -2.978330737054},
|
||||||
|
{Amp: 0.0000000255754500, Period: 1.41018540, Phase: 2.978330748744},
|
||||||
|
{Amp: 0.0000000261832900, Period: 1.76273174, Phase: 0.497691978790},
|
||||||
|
{Amp: 0.0000000261832900, Period: -1.76273174, Phase: -0.497691978790},
|
||||||
|
{Amp: 0.0000000136429969, Period: -0.70509270, Phase: 0.327144622755},
|
||||||
|
{Amp: 0.0000000136429969, Period: 0.70509270, Phase: -0.327144587998},
|
||||||
|
{Amp: 0.0000000155953890, Period: -0.88136587, Phase: 2.146927076004},
|
||||||
|
{Amp: 0.0000000155953890, Period: 0.88136587, Phase: -2.146927075722},
|
||||||
|
{Amp: 0.0000000087480272, Period: 1.00727528, Phase: 1.656394163933},
|
||||||
|
{Amp: 0.0000000087480272, Period: -1.00727528, Phase: -1.656394128838},
|
||||||
|
{Amp: 0.0000000116112914, Period: 2.25553591, Phase: -0.022341870823},
|
||||||
|
{Amp: 0.0000000116112914, Period: -2.25553591, Phase: 0.022341870823},
|
||||||
|
{Amp: 0.0000000061437036, Period: 0.58757726, Phase: 1.500536383468},
|
||||||
|
{Amp: 0.0000000061437036, Period: -0.58757726, Phase: -1.500536348490},
|
||||||
|
{Amp: 0.0000000042431918, Period: -0.50363764, Phase: 2.970124107071},
|
||||||
|
{Amp: 0.0000000042431918, Period: 0.50363764, Phase: -2.970124071981},
|
||||||
|
{Amp: 0.0000000034561177, Period: -1.50369059, Phase: 0.034310693267},
|
||||||
|
{Amp: 0.0000000034561177, Period: 1.50369059, Phase: -0.034310721884},
|
||||||
|
{Amp: 0.0000000030688784, Period: 0.78343633, Phase: 0.334349762913},
|
||||||
|
{Amp: 0.0000000030688784, Period: -0.78343633, Phase: -0.334349729096},
|
||||||
|
{Amp: 0.0000000022671527, Period: 0.52701992, Phase: 1.152376957614},
|
||||||
|
{Amp: 0.0000000022671527, Period: -0.52701992, Phase: -1.152376945916},
|
||||||
|
{Amp: 0.0000000022287342, Period: 1.77703490, Phase: 0.345978042967},
|
||||||
|
{Amp: 0.0000000022287342, Period: -1.77703490, Phase: -0.345978042889},
|
||||||
|
{Amp: 0.0000000021175036, Period: 4.51107181, Phase: -0.011219786970},
|
||||||
|
{Amp: 0.0000000021175036, Period: -4.51107181, Phase: 0.011219786970},
|
||||||
|
{Amp: 0.0000000014391886, Period: -0.61965131, Phase: 0.669288476670},
|
||||||
|
{Amp: 0.0000000014391886, Period: 0.61965131, Phase: -0.669288418187},
|
||||||
|
{Amp: 0.0000000012177331, Period: 6.94927396, Phase: 0.995871908751},
|
||||||
|
{Amp: 0.0000000012177331, Period: -6.94927396, Phase: -0.995871908751},
|
||||||
|
{Amp: 0.0000000010786785, Period: 1.12776795, Phase: -0.045280251144},
|
||||||
|
{Amp: 0.0000000010786785, Period: -1.12776795, Phase: 0.045280251144},
|
||||||
|
{Amp: 0.0000000011266470, Period: 0.64099336, Phase: -0.987288717782},
|
||||||
|
{Amp: 0.0000000011266470, Period: -0.64099336, Phase: 0.987288718396},
|
||||||
|
{Amp: 0.0000000008265031, Period: -0.75178905, Phase: 2.504075950065},
|
||||||
|
{Amp: 0.0000000008265031, Period: 0.75178905, Phase: -2.504075904237},
|
||||||
|
{Amp: 0.0000000005794919, Period: -3.55143712, Phase: 3.009946487657},
|
||||||
|
{Amp: 0.0000000005794919, Period: 3.55143712, Phase: -3.009946487654},
|
||||||
|
{Amp: 0.0000000003807491, Period: 1.77777556, Phase: 1.686281241558},
|
||||||
|
{Amp: 0.0000000003807491, Period: -1.77777556, Phase: -1.686281227667},
|
||||||
|
{Amp: 0.0000000003552247, Period: -2.60797333, Phase: 0.371913588076},
|
||||||
|
{Amp: 0.0000000003552247, Period: 2.60797333, Phase: -0.371913588692},
|
||||||
|
{Amp: 0.0000000005101755, Period: -0.95555743, Phase: -1.972390618495},
|
||||||
|
{Amp: 0.0000000005101755, Period: 0.95555743, Phase: 1.972390620107},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
{Amp: 0.3735263437471362, Period: 0.00000000, Phase: -3.141592653403},
|
||||||
|
{Amp: 0.0001624469912587, Period: -3.52546349, Phase: -0.248853126552},
|
||||||
|
{Amp: 0.0001624469912587, Period: 3.52546349, Phase: 0.248853148768},
|
||||||
|
{Amp: 0.0000717191594226, Period: 7.05092698, Phase: 0.909825056364},
|
||||||
|
{Amp: 0.0000717191594226, Period: -7.05092698, Phase: -0.909825056364},
|
||||||
|
{Amp: 0.0000385969570472, Period: 2.35030899, Phase: -0.412116543189},
|
||||||
|
{Amp: 0.0000385969570472, Period: -2.35030899, Phase: 0.412116578303},
|
||||||
|
{Amp: 0.0000218287365705, Period: 1.76273174, Phase: 2.068494534904},
|
||||||
|
{Amp: 0.0000218287365705, Period: -1.76273174, Phase: -2.068494534904},
|
||||||
|
{Amp: 0.0000223383238694, Period: 1.17515450, Phase: -2.395036777814},
|
||||||
|
{Amp: 0.0000223383238694, Period: -1.17515450, Phase: 2.395036777814},
|
||||||
|
{Amp: 0.0000096353043778, Period: 1.41018540, Phase: -1.734057827225},
|
||||||
|
{Amp: 0.0000096353043778, Period: -1.41018540, Phase: 1.734057862320},
|
||||||
|
{Amp: 0.0000065973457880, Period: 0.88136587, Phase: -0.574948883091},
|
||||||
|
{Amp: 0.0000065973457880, Period: -0.88136587, Phase: 0.574948883295},
|
||||||
|
{Amp: 0.0000048707009708, Period: -0.70509245, Phase: -1.160449891993},
|
||||||
|
{Amp: 0.0000048707009708, Period: 0.70509245, Phase: 1.160449916162},
|
||||||
|
{Amp: 0.0000043860283834, Period: 400.80554123, Phase: 3.108492565509},
|
||||||
|
{Amp: 0.0000043860283834, Period: -400.80554119, Phase: -3.108492540844},
|
||||||
|
{Amp: 0.0000037783019709, Period: -14526.63126285, Phase: 1.653763811537},
|
||||||
|
{Amp: 0.0000037783070079, Period: 14526.64946924, Phase: -1.653758308820},
|
||||||
|
{Amp: 0.0000052005863869, Period: 2.25553591, Phase: 1.548440639446},
|
||||||
|
{Amp: 0.0000052005863869, Period: -2.25553591, Phase: -1.548440639370},
|
||||||
|
{Amp: 0.0000033290495376, Period: -274.03305205, Phase: 2.753261920991},
|
||||||
|
{Amp: 0.0000033290495376, Period: 274.03305205, Phase: -2.753261920909},
|
||||||
|
{Amp: 0.0000029065567615, Period: -1.00727528, Phase: 3.055999599574},
|
||||||
|
{Amp: 0.0000029065567615, Period: 1.00727528, Phase: -3.055999587858},
|
||||||
|
{Amp: 0.0000032927071387, Period: -240.79911272, Phase: 2.850621646594},
|
||||||
|
{Amp: 0.0000032927071387, Period: 240.79911272, Phase: -2.850621646515},
|
||||||
|
{Amp: 0.0000027860432638, Period: 2465.81058901, Phase: 2.385773248504},
|
||||||
|
{Amp: 0.0000027860415206, Period: -2465.81062125, Phase: -2.385773134668},
|
||||||
|
{Amp: 0.0000024099254453, Period: -4.51107182, Phase: -1.559634116074},
|
||||||
|
{Amp: 0.0000024099254453, Period: 4.51107182, Phase: 1.559634116074},
|
||||||
|
{Amp: 0.0000021364215633, Period: 0.58757722, Phase: 3.051294967909},
|
||||||
|
{Amp: 0.0000021364215633, Period: -0.58757722, Phase: -3.051294932621},
|
||||||
|
{Amp: 0.0000021087772652, Period: -2082.97789867, Phase: 0.219861989989},
|
||||||
|
{Amp: 0.0000021087773173, Period: 2082.97802228, Phase: -0.219858903141},
|
||||||
|
{Amp: 0.0000018853812260, Period: 249.13840056, Phase: -2.750530921943},
|
||||||
|
{Amp: 0.0000018853812260, Period: -249.13840055, Phase: 2.750530930849},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
{Amp: 0.0002988994545555, Period: 3.55118107, Phase: -0.373458788613},
|
||||||
|
{Amp: 0.0000823525166369, Period: 1.76913777, Phase: 1.446188770927},
|
||||||
|
{Amp: 0.0000837042048393, Period: -6.95025971, Phase: 1.609453836719},
|
||||||
|
{Amp: 0.0000278946698536, Period: -3.50011572, Phase: -0.871155222557},
|
||||||
|
{Amp: 0.0000315906532820, Period: 7.15455323, Phase: -2.854080409609},
|
||||||
|
{Amp: 0.0000294503681314, Period: -1.75637194, Phase: -2.690812623889},
|
||||||
|
{Amp: 0.0000144958688621, Period: -2.33901625, Phase: 2.931395664158},
|
||||||
|
{Amp: 0.0000108374431350, Period: -6.18210653, Phase: -0.351109195546},
|
||||||
|
{Amp: 0.0000082175838585, Period: -1.17232451, Phase: 1.772880355220},
|
||||||
|
{Amp: 0.0000062618381566, Period: -0.87977309, Phase: -0.057891668832},
|
||||||
|
{Amp: 0.0000041298266970, Period: -1.40611218, Phase: -2.029848170071},
|
||||||
|
{Amp: 0.0000043507065743, Period: 16.68653641, Phase: 1.973436581069},
|
||||||
|
{Amp: 0.0000042081682285, Period: 1.41426445, Phase: 3.120261383685},
|
||||||
|
{Amp: 0.0000027357551003, Period: -0.70407293, Phase: -1.868111985056},
|
||||||
|
{Amp: 0.0000036991221930, Period: 2.36171130, Phase: 2.107163763597},
|
||||||
|
{Amp: 0.0000020163445050, Period: -2.60801351, Phase: -0.434765781769},
|
||||||
|
{Amp: 0.0000026854901206, Period: 397.49502864, Phase: -2.385626020918},
|
||||||
|
{Amp: 0.0000017894118824, Period: -0.58686890, Phase: 2.596961125563},
|
||||||
|
{Amp: 0.0000023074479953, Period: 0.88296447, Phase: 1.943899853407},
|
||||||
|
{Amp: 0.0000018159137522, Period: 1.00936379, Phase: 2.604869046210},
|
||||||
|
{Amp: 0.0000011726743714, Period: -437.81569630, Phase: 1.882700621195},
|
||||||
|
{Amp: 0.0000016518864520, Period: 1.17799818, Phase: -3.017336059369},
|
||||||
|
{Amp: 0.0000018506530067, Period: 9207.93895292, Phase: -0.457770143364},
|
||||||
|
{Amp: 0.0000013196935928, Period: -1.00519540, Phase: -0.707882741855},
|
||||||
|
{Amp: 0.0000014426949422, Period: -3.55690202, Phase: -0.281777715997},
|
||||||
|
{Amp: 0.0000015660692561, Period: -548.57786416, Phase: 3.037342396932},
|
||||||
|
{Amp: 0.0000005646670312, Period: -488.13559324, Phase: 2.168360257491},
|
||||||
|
{Amp: 0.0000007495662748, Period: 3126.34340655, Phase: -0.151955418922},
|
||||||
|
{Amp: 0.0000007569857746, Period: -2303.92749369, Phase: 0.668283437067},
|
||||||
|
{Amp: 0.0000009550285338, Period: 4406.90403072, Phase: -0.300652348938},
|
||||||
|
{Amp: 0.0000007091149133, Period: -3153.42860903, Phase: 2.713933181549},
|
||||||
|
{Amp: 0.0000002004455524, Period: -1919.99999983, Phase: -1.852207712464},
|
||||||
|
{Amp: 0.0000001623489363, Period: 1408.49105459, Phase: -2.899698107364},
|
||||||
|
{Amp: 0.0000001058862562, Period: 1599.99999991, Phase: -1.747489966524},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
{Amp: 0.0001662544744719, Period: 0.00000000, Phase: 2.413486237501},
|
||||||
|
{Amp: 0.0000000000608298, Period: -193.26413985, Phase: -2.230228354541},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
{
|
||||||
|
{Amp: 0.0071566594572575, Period: 0.00000000, Phase: 0.000000000000},
|
||||||
|
{Amp: 0.0000006965149555, Period: -2.35030899, Phase: -1.158674588527},
|
||||||
|
{Amp: 0.0000006965149555, Period: 2.35030899, Phase: 1.158674600230},
|
||||||
|
{Amp: 0.0000003224914673, Period: 7.05092698, Phase: -0.660970737035},
|
||||||
|
{Amp: 0.0000003224914673, Period: -7.05092698, Phase: 0.660970737035},
|
||||||
|
{Amp: 0.0000001149029760, Period: -6.26162437, Phase: -1.299592404339},
|
||||||
|
{Amp: 0.0000001149029760, Period: 6.26162437, Phase: 1.299592404339},
|
||||||
|
{Amp: 0.0000000610717185, Period: -3.52546349, Phase: -1.819650979517},
|
||||||
|
{Amp: 0.0000000610717185, Period: 3.52546349, Phase: 1.819651032396},
|
||||||
|
{Amp: 0.0000000547899088, Period: 4.17441617, Phase: 1.948670877782},
|
||||||
|
{Amp: 0.0000000547899088, Period: -4.17441617, Phase: -1.948670828180},
|
||||||
|
{Amp: 0.0000000350717808, Period: -12.52324882, Phase: -0.649785081154},
|
||||||
|
{Amp: 0.0000000350717808, Period: 12.52324882, Phase: 0.649785104562},
|
||||||
|
{Amp: 0.0000000273934283, Period: -3.13081218, Phase: -2.599205067142},
|
||||||
|
{Amp: 0.0000000273934283, Period: 3.13081218, Phase: 2.599205102257},
|
||||||
|
{Amp: 0.0000000181610714, Period: -1.76273174, Phase: -0.497708209553},
|
||||||
|
{Amp: 0.0000000181610714, Period: 1.76273174, Phase: 0.497708210129},
|
||||||
|
{Amp: 0.0000000197317929, Period: 1.17515450, Phase: -0.824239093090},
|
||||||
|
{Amp: 0.0000000197317929, Period: -1.17515450, Phase: 0.824239093704},
|
||||||
|
{Amp: 0.0000000140622484, Period: -2.50464974, Phase: 3.034184231000},
|
||||||
|
{Amp: 0.0000000140622484, Period: 2.50464974, Phase: -3.034184231000},
|
||||||
|
{Amp: 0.0000000103962349, Period: 1.41018540, Phase: -0.163261763515},
|
||||||
|
{Amp: 0.0000000103962349, Period: -1.41018540, Phase: 0.163261798610},
|
||||||
|
{Amp: 0.0000000145474682, Period: -3.58319300, Phase: -2.012339222997},
|
||||||
|
{Amp: 0.0000000145474682, Period: 3.58319300, Phase: 2.012339225153},
|
||||||
|
{Amp: 0.0000000073448387, Period: 2.08720812, Phase: -2.384360905770},
|
||||||
|
{Amp: 0.0000000073448387, Period: -2.08720812, Phase: 2.384360906102},
|
||||||
|
{Amp: 0.0000000059965021, Period: -1.00727528, Phase: 1.485195822039},
|
||||||
|
{Amp: 0.0000000059965021, Period: 1.00727528, Phase: -1.485195786944},
|
||||||
|
{Amp: 0.0000000049933886, Period: 10.02108773, Phase: 0.967007202582},
|
||||||
|
{Amp: 0.0000000049933886, Period: -10.02108773, Phase: -0.967007214068},
|
||||||
|
{Amp: 0.0000000038834130, Period: 1.78903553, Phase: -1.734548005475},
|
||||||
|
{Amp: 0.0000000038834130, Period: -1.78903553, Phase: 1.734548012548},
|
||||||
|
{Amp: 0.0000000033173319, Period: 0.78343633, Phase: 0.334410735342},
|
||||||
|
{Amp: 0.0000000033173319, Period: -0.78343633, Phase: -0.334410703853},
|
||||||
|
{Amp: 0.0000000037071986, Period: 50.13968535, Phase: 0.161404498578},
|
||||||
|
{Amp: 0.0000000037071986, Period: -50.13968535, Phase: -0.161404499697},
|
||||||
|
{Amp: 0.0000000024697553, Period: 3.58615746, Phase: 3.014796806223},
|
||||||
|
{Amp: 0.0000000024697553, Period: -3.58615746, Phase: -3.014796771059},
|
||||||
|
{Amp: 0.0000000027884176, Period: 5.56655476, Phase: 1.323585106216},
|
||||||
|
{Amp: 0.0000000027884176, Period: -5.56655476, Phase: -1.323585106216},
|
||||||
|
{Amp: 0.0000000020719852, Period: 1.56540608, Phase: -1.084729476400},
|
||||||
|
{Amp: 0.0000000020719852, Period: -1.56540608, Phase: 1.084729523216},
|
||||||
|
{Amp: 0.0000000018431031, Period: 0.58757726, Phase: 1.497090964345},
|
||||||
|
{Amp: 0.0000000018431031, Period: -0.58757726, Phase: -1.497090952676},
|
||||||
|
{Amp: 0.0000000016808769, Period: 7.15554098, Phase: -2.533819463034},
|
||||||
|
{Amp: 0.0000000016808769, Period: -7.15554098, Phase: 2.533819476120},
|
||||||
|
{Amp: 0.0000000020382815, Period: -0.88136587, Phase: 2.146155229321},
|
||||||
|
{Amp: 0.0000000020382815, Period: 0.88136587, Phase: -2.146155229461},
|
||||||
|
{Amp: 0.0000000016674142, Period: 3.85367596, Phase: 2.266819681887},
|
||||||
|
{Amp: 0.0000000016674142, Period: -3.85367596, Phase: -2.266819678138},
|
||||||
|
{Amp: 0.0000000012877349, Period: -3.49986546, Phase: 2.808660684496},
|
||||||
|
{Amp: 0.0000000012877349, Period: 3.49986546, Phase: -2.808660671629},
|
||||||
|
{Amp: 0.0000000012181542, Period: 55.93578462, Phase: 1.960483840747},
|
||||||
|
{Amp: 0.0000000012181542, Period: -55.93578462, Phase: -1.960483829044},
|
||||||
|
{Amp: 0.0000000010016338, Period: -2.94686240, Phase: -2.916664806186},
|
||||||
|
{Amp: 0.0000000010016338, Period: 2.94686240, Phase: 2.916664806820},
|
||||||
|
{Amp: 0.0000000009057853, Period: -6.94927480, Phase: 2.143765079507},
|
||||||
|
{Amp: 0.0000000009057853, Period: 6.94927480, Phase: -2.143765093634},
|
||||||
|
{Amp: 0.0000000011132716, Period: -1.39147207, Phase: 0.434910736795},
|
||||||
|
{Amp: 0.0000000011132716, Period: 1.39147207, Phase: -0.434910734723},
|
||||||
|
{Amp: 0.0000000007267503, Period: 0.70508749, Phase: 1.874821204178},
|
||||||
|
{Amp: 0.0000000007267503, Period: -0.70508749, Phase: -1.874821204178},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
{Amp: 0.2874089391143348, Period: 0.00000000, Phase: 0.000000000000},
|
||||||
|
{Amp: 0.0000581860484889, Period: -2.35030899, Phase: 0.412122994161},
|
||||||
|
{Amp: 0.0000581860484889, Period: 2.35030899, Phase: -0.412122959047},
|
||||||
|
{Amp: 0.0000407623427232, Period: -7.05092698, Phase: 2.231767920686},
|
||||||
|
{Amp: 0.0000407623427232, Period: 7.05092698, Phase: -2.231767920577},
|
||||||
|
{Amp: 0.0000400609839801, Period: 6.26162437, Phase: 2.870388622006},
|
||||||
|
{Amp: 0.0000400609839801, Period: -6.26162437, Phase: -2.870388622006},
|
||||||
|
{Amp: 0.0000303508630091, Period: 12.52324880, Phase: 2.220584023571},
|
||||||
|
{Amp: 0.0000303508630091, Period: -12.52324880, Phase: -2.220583988461},
|
||||||
|
{Amp: 0.0000213787490768, Period: 3.52546349, Phase: -2.892737639574},
|
||||||
|
{Amp: 0.0000213787490768, Period: -3.52546349, Phase: 2.892737684197},
|
||||||
|
{Amp: 0.0000153680208913, Period: 4.17441628, Phase: -2.762551710070},
|
||||||
|
{Amp: 0.0000153680208913, Period: -4.17441628, Phase: 2.762551710070},
|
||||||
|
{Amp: 0.0000067324310952, Period: -3.13081218, Phase: 2.113191687546},
|
||||||
|
{Amp: 0.0000067324310952, Period: 3.13081218, Phase: -2.113191675837},
|
||||||
|
{Amp: 0.0000072634431824, Period: -50.14182397, Phase: -1.759435081522},
|
||||||
|
{Amp: 0.0000072634431824, Period: 50.14182397, Phase: 1.759435084589},
|
||||||
|
{Amp: 0.0000043977562585, Period: -1.76273174, Phase: -2.068506076335},
|
||||||
|
{Amp: 0.0000043977562585, Period: 1.76273174, Phase: 2.068506076335},
|
||||||
|
{Amp: 0.0000047762008660, Period: 1.17515450, Phase: 0.746555718634},
|
||||||
|
{Amp: 0.0000047762008660, Period: -1.17515450, Phase: -0.746555718634},
|
||||||
|
{Amp: 0.0000031610812971, Period: -2.50464974, Phase: 1.463376735121},
|
||||||
|
{Amp: 0.0000031610812971, Period: 2.50464974, Phase: -1.463376734852},
|
||||||
|
{Amp: 0.0000035573097979, Period: -3.58319294, Phase: 2.700332818172},
|
||||||
|
{Amp: 0.0000035573097979, Period: 3.58319294, Phase: -2.700332827650},
|
||||||
|
{Amp: 0.0000023282599410, Period: 14399.99998683, Phase: -1.099877635453},
|
||||||
|
{Amp: 0.0000023282599359, Period: -14399.99998683, Phase: 1.099877639624},
|
||||||
|
{Amp: 0.0000023502114735, Period: -10.02107511, Phase: -2.536966831342},
|
||||||
|
{Amp: 0.0000023502114735, Period: 10.02107511, Phase: 2.536966842808},
|
||||||
|
{Amp: 0.0000023510383997, Period: 1.41018540, Phase: 1.407535452307},
|
||||||
|
{Amp: 0.0000023510383997, Period: -1.41018540, Phase: -1.407535440592},
|
||||||
|
{Amp: 0.0000021174661004, Period: 55.93578577, Phase: -2.751914513472},
|
||||||
|
{Amp: 0.0000021174661004, Period: -55.93578577, Phase: 2.751914548581},
|
||||||
|
{Amp: 0.0000016288866844, Period: -3679.84840303, Phase: -0.973793744174},
|
||||||
|
{Amp: 0.0000016288866845, Period: 3679.84840303, Phase: 0.973793744140},
|
||||||
|
{Amp: 0.0000019377870959, Period: -249.86158491, Phase: 2.450426504098},
|
||||||
|
{Amp: 0.0000019377870959, Period: 249.86158492, Phase: -2.450426503434},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
{Amp: 0.0002045597496146, Period: -50.17098410, Phase: -1.011816940225},
|
||||||
|
{Amp: 0.0001785118648258, Period: 7.15455319, Phase: 0.287431567198},
|
||||||
|
{Amp: 0.0001131999784893, Period: 1.76913777, Phase: 1.446212727724},
|
||||||
|
{Amp: 0.0000658778169210, Period: -3.50011574, Phase: -0.871350254578},
|
||||||
|
{Amp: 0.0000497058888328, Period: 3.55118106, Phase: -0.373506086712},
|
||||||
|
{Amp: 0.0000316384926978, Period: -6.95025977, Phase: -1.532287159698},
|
||||||
|
{Amp: 0.0000287801237327, Period: -10.02171452, Phase: -1.661453180348},
|
||||||
|
{Amp: 0.0000181744317896, Period: 16.68901713, Phase: 2.779471484341},
|
||||||
|
{Amp: 0.0000105558175161, Period: -5.56684974, Phase: -2.311166167228},
|
||||||
|
{Amp: 0.0000057610853129, Period: -1.40611219, Phase: 1.111461108666},
|
||||||
|
{Amp: 0.0000061046181888, Period: -7.17825897, Phase: -1.726424077467},
|
||||||
|
{Amp: 0.0000057310334964, Period: -2.33901627, Phase: -0.210412331329},
|
||||||
|
{Amp: 0.0000046610005483, Period: -3.85376806, Phase: -2.960887284255},
|
||||||
|
{Amp: 0.0000034982417330, Period: 3.33918688, Phase: 1.586656801126},
|
||||||
|
{Amp: 0.0000030091617315, Period: -1.75637195, Phase: 0.450524745204},
|
||||||
|
{Amp: 0.0000024732926446, Period: 25.01011480, Phase: 2.204580355873},
|
||||||
|
{Amp: 0.0000024171568015, Period: 0.00000000, Phase: -2.832382068191},
|
||||||
|
{Amp: 0.0000022247695560, Period: -2.94691618, Phase: 2.672542463618},
|
||||||
|
{Amp: 0.0000020947921969, Period: 2.63625763, Phase: 2.235037411637},
|
||||||
|
{Amp: 0.0000024416432533, Period: -25.08296275, Phase: -1.578237604387},
|
||||||
|
{Amp: 0.0000014042712722, Period: 4.55326510, Phase: -2.204412209524},
|
||||||
|
{Amp: 0.0000011180389240, Period: -1.17232452, Phase: 1.772399331347},
|
||||||
|
{Amp: 0.0000010371714944, Period: -7.19017077, Phase: -2.734365259635},
|
||||||
|
{Amp: 0.0000011076384510, Period: -2.38555726, Phase: 2.022753854104},
|
||||||
|
{Amp: 0.0000011932531874, Period: 2.17780908, Phase: 2.886194141381},
|
||||||
|
{Amp: 0.0000007178049665, Period: 2.36171130, Phase: 2.107149695705},
|
||||||
|
{Amp: 0.0000006908412319, Period: -8.35287888, Phase: -2.223571888688},
|
||||||
|
{Amp: 0.0000006659820028, Period: 1.85518927, Phase: -2.747232277664},
|
||||||
|
{Amp: 0.0000006772314920, Period: 2.38747924, Phase: 2.301347989676},
|
||||||
|
{Amp: 0.0000008993128501, Period: -0.87977305, Phase: -0.047323465226},
|
||||||
|
{Amp: 0.0000006286307601, Period: 0.88296448, Phase: -1.197531764168},
|
||||||
|
{Amp: 0.0000006784151570, Period: 1478.51706690, Phase: 0.743089169080},
|
||||||
|
{Amp: 0.0000005660807396, Period: -2.00384437, Phase: 1.372931645641},
|
||||||
|
{Amp: 0.0000006339665249, Period: 1.41428228, Phase: 0.785273916249},
|
||||||
|
{Amp: 0.0000006128705113, Period: 1.00936322, Phase: -0.638851146096},
|
||||||
|
{Amp: 0.0000005206551413, Period: -1.00519540, Phase: 2.433337444677},
|
||||||
|
{Amp: 0.0000004583970422, Period: -0.64015046, Phase: -1.206485628814},
|
||||||
|
{Amp: 0.0000003466029660, Period: 397.49139960, Phase: 0.754124274965},
|
||||||
|
{Amp: 0.0000004577854173, Period: 3.57743657, Phase: 3.093485444125},
|
||||||
|
{Amp: 0.0000004718481418, Period: 37429.59476194, Phase: 1.395067854967},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
{Amp: 0.0008533093128905, Period: 0.00000000, Phase: 2.413388168755},
|
||||||
|
{Amp: 0.0000000926213408, Period: -243.69680308, Phase: -0.936338792860},
|
||||||
|
{Amp: 0.0000000000106902, Period: -192.66710225, Phase: -1.706763976490},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
{
|
||||||
|
{Amp: 0.0125879701715314, Period: 0.00000000, Phase: 0.000000000000},
|
||||||
|
{Amp: 0.0000013790105326, Period: -1.97891484, Phase: -1.808423578125},
|
||||||
|
{Amp: 0.0000013790105326, Period: 1.97891484, Phase: 1.808423589815},
|
||||||
|
{Amp: 0.0000017976024735, Period: 12.52324852, Phase: 0.649657760137},
|
||||||
|
{Amp: 0.0000017976024735, Period: -12.52324852, Phase: -0.649657760137},
|
||||||
|
{Amp: 0.0000006437448086, Period: -4.51107179, Phase: 0.011294478638},
|
||||||
|
{Amp: 0.0000006437448086, Period: 4.51107179, Phase: -0.011294478574},
|
||||||
|
{Amp: 0.0000002086864553, Period: -6.26162422, Phase: 1.842527624347},
|
||||||
|
{Amp: 0.0000002086864553, Period: 6.26162422, Phase: -1.842527612645},
|
||||||
|
{Amp: 0.0000001395378859, Period: 8.37677335, Phase: 0.714288700422},
|
||||||
|
{Amp: 0.0000001395378859, Period: -8.37677335, Phase: -0.714288676300},
|
||||||
|
{Amp: 0.0000000999126129, Period: 4.17441617, Phase: -1.192604552413},
|
||||||
|
{Amp: 0.0000000999126129, Period: -4.17441617, Phase: 1.192604552682},
|
||||||
|
{Amp: 0.0000000500574919, Period: -3.13081213, Phase: 0.542875780384},
|
||||||
|
{Amp: 0.0000000500574919, Period: 3.13081213, Phase: -0.542875768668},
|
||||||
|
{Amp: 0.0000000256983546, Period: 2.50464971, Phase: 0.106887217028},
|
||||||
|
{Amp: 0.0000000256983546, Period: -2.50464971, Phase: -0.106887217655},
|
||||||
|
{Amp: 0.0000000237843996, Period: -8.39299929, Phase: -1.721959538178},
|
||||||
|
{Amp: 0.0000000237843996, Period: 8.39299929, Phase: 1.721959573291},
|
||||||
|
{Amp: 0.0000000174121120, Period: 16.69021715, Phase: 0.150827135054},
|
||||||
|
{Amp: 0.0000000174121120, Period: -16.69021715, Phase: -0.150827135054},
|
||||||
|
{Amp: 0.0000000119553173, Period: 10.02131069, Phase: -1.925833362861},
|
||||||
|
{Amp: 0.0000000119553173, Period: -10.02131069, Phase: 1.925833397083},
|
||||||
|
{Amp: 0.0000000131617319, Period: -2.08719489, Phase: -0.208389567476},
|
||||||
|
{Amp: 0.0000000131617319, Period: 2.08719489, Phase: 0.208389567476},
|
||||||
|
{Amp: 0.0000000141920315, Period: -50.14242063, Phase: 1.115887970655},
|
||||||
|
{Amp: 0.0000000141920315, Period: 50.14242063, Phase: -1.115887945684},
|
||||||
|
{Amp: 0.0000000109988711, Period: 2.24510856, Phase: 1.507540480873},
|
||||||
|
{Amp: 0.0000000109988711, Period: -2.24510856, Phase: -1.507540445897},
|
||||||
|
{Amp: 0.0000000085572239, Period: -2.25553590, Phase: -3.119143532879},
|
||||||
|
{Amp: 0.0000000085572239, Period: 2.25553590, Phase: 3.119143533646},
|
||||||
|
{Amp: 0.0000000070978417, Period: -1.78903551, Phase: -1.406515118334},
|
||||||
|
{Amp: 0.0000000070978417, Period: 1.78903551, Phase: 1.406515133811},
|
||||||
|
{Amp: 0.0000000060001815, Period: -5.56672420, Phase: 1.283203905605},
|
||||||
|
{Amp: 0.0000000060001815, Period: 5.56672420, Phase: -1.283203904944},
|
||||||
|
{Amp: 0.0000000054209452, Period: -0.93228920, Phase: 0.795871427118},
|
||||||
|
{Amp: 0.0000000054209452, Period: 0.93228920, Phase: -0.795871427118},
|
||||||
|
{Amp: 0.0000000054109127, Period: 6.18191832, Phase: -0.305922255492},
|
||||||
|
{Amp: 0.0000000054109127, Period: -6.18191832, Phase: 0.305922255492},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
{Amp: 0.3620341291375704, Period: 0.00000000, Phase: -3.141592653590},
|
||||||
|
{Amp: 0.0001102576431631, Period: -12.52324875, Phase: 0.920999800869},
|
||||||
|
{Amp: 0.0001102576431631, Period: 12.52324875, Phase: -0.920999800869},
|
||||||
|
{Amp: 0.0000938947575579, Period: 1.97891484, Phase: 0.237682433552},
|
||||||
|
{Amp: 0.0000938947575579, Period: -1.97891484, Phase: -0.237682433079},
|
||||||
|
{Amp: 0.0000383458487621, Period: 4.51107181, Phase: -1.581970202032},
|
||||||
|
{Amp: 0.0000383458487621, Period: -4.51107181, Phase: 1.581970202035},
|
||||||
|
{Amp: 0.0000373528427553, Period: 6.26162437, Phase: -0.271204706549},
|
||||||
|
{Amp: 0.0000373528427553, Period: -6.26162437, Phase: 0.271204706549},
|
||||||
|
{Amp: 0.0000194161648683, Period: 8.37677211, Phase: 2.283688674383},
|
||||||
|
{Amp: 0.0000194161648683, Period: -8.37677211, Phase: -2.283688674381},
|
||||||
|
{Amp: 0.0000146516338969, Period: -4.17441624, Phase: -0.378597607197},
|
||||||
|
{Amp: 0.0000146516338969, Period: 4.17441624, Phase: 0.378597607197},
|
||||||
|
{Amp: 0.0000064963948341, Period: 3.13081218, Phase: 1.028401028095},
|
||||||
|
{Amp: 0.0000064963948341, Period: -3.13081218, Phase: -1.028401016392},
|
||||||
|
{Amp: 0.0000074412818628, Period: -50.14197245, Phase: 1.380281518586},
|
||||||
|
{Amp: 0.0000074412818628, Period: 50.14197245, Phase: -1.380281518587},
|
||||||
|
{Amp: 0.0000032693721243, Period: 3796.36874430, Phase: 1.987215709322},
|
||||||
|
{Amp: 0.0000032693721243, Period: -3796.36874429, Phase: -1.987215709307},
|
||||||
|
{Amp: 0.0000033105851447, Period: -8.39299149, Phase: 2.997485814677},
|
||||||
|
{Amp: 0.0000033105851447, Period: 8.39299149, Phase: -2.997485813557},
|
||||||
|
{Amp: 0.0000030790399070, Period: 2.50464974, Phase: 1.678207462150},
|
||||||
|
{Amp: 0.0000030790399070, Period: -2.50464974, Phase: -1.678207462150},
|
||||||
|
{Amp: 0.0000023398570389, Period: -10.02107449, Phase: 0.604674627622},
|
||||||
|
{Amp: 0.0000023398570389, Period: 10.02107449, Phase: -0.604674593224},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
{Amp: 0.0002065924169942, Period: 16.68901704, Phase: -0.362202150664},
|
||||||
|
{Amp: 0.0001589869764021, Period: 7.15455319, Phase: 0.287440062412},
|
||||||
|
{Amp: 0.0001486043380971, Period: 1.76913777, Phase: 1.446213430046},
|
||||||
|
{Amp: 0.0000635073108731, Period: 3.55118106, Phase: -0.373504978601},
|
||||||
|
{Amp: 0.0000599351698525, Period: -2.24513352, Phase: -2.170633548755},
|
||||||
|
{Amp: 0.0000489596900866, Period: -10.02171449, Phase: 1.480222294735},
|
||||||
|
{Amp: 0.0000333682283528, Period: -16.81857729, Phase: -1.076487783264},
|
||||||
|
{Amp: 0.0000292325461337, Period: -50.17099062, Phase: -1.012422966902},
|
||||||
|
{Amp: 0.0000295832427279, Period: -6.18210646, Phase: -0.350915517480},
|
||||||
|
{Amp: 0.0000197588369441, Period: 0.00000000, Phase: -2.951408504882},
|
||||||
|
{Amp: 0.0000183551029746, Period: -5.56684974, Phase: 0.830451671341},
|
||||||
|
{Amp: 0.0000081987970452, Period: -3.85376806, Phase: 0.180738718447},
|
||||||
|
{Amp: 0.0000039403527376, Period: -2.94691618, Phase: -0.469009428127},
|
||||||
|
{Amp: 0.0000056895636122, Period: -16.88411373, Phase: -2.084089640414},
|
||||||
|
{Amp: 0.0000040434854859, Period: -25.08297371, Phase: 1.559247963578},
|
||||||
|
{Amp: 0.0000036901291978, Period: 5.57732714, Phase: 0.352077722692},
|
||||||
|
{Amp: 0.0000019322089806, Period: 25.00957616, Phase: -1.141325761390},
|
||||||
|
{Amp: 0.0000019711212463, Period: -2.38555726, Phase: -1.118790685515},
|
||||||
|
{Amp: 0.0000018673159813, Period: 4.55326506, Phase: -2.204843732624},
|
||||||
|
{Amp: 0.0000016695689644, Period: 3.33918689, Phase: -1.554811604707},
|
||||||
|
{Amp: 0.0000014034621874, Period: -2.60801097, Phase: 2.801758550422},
|
||||||
|
{Amp: 0.0000011758260607, Period: -8.35287933, Phase: 0.916517243491},
|
||||||
|
{Amp: 0.0000016838424078, Period: 8.34481080, Phase: -0.203581964410},
|
||||||
|
{Amp: 0.0000010798624964, Period: 2.63625769, Phase: -0.905106521704},
|
||||||
|
{Amp: 0.0000010108880552, Period: -2.00384437, Phase: -1.768605450166},
|
||||||
|
{Amp: 0.0000008876681807, Period: -12.52396907, Phase: 1.911818107846},
|
||||||
|
{Amp: 0.0000008194699011, Period: -16.75354536, Phase: 3.077495182201},
|
||||||
|
{Amp: 0.0000006728737059, Period: -16.95016232, Phase: -3.092183700860},
|
||||||
|
{Amp: 0.0000006297345982, Period: 5.58451544, Phase: 1.359571973412},
|
||||||
|
{Amp: 0.0000006128899757, Period: 0.88296471, Phase: -1.142969177277},
|
||||||
|
{Amp: 0.0000007093782158, Period: -2.59408012, Phase: -1.871354043032},
|
||||||
|
{Amp: 0.0000005580987049, Period: -5.01075568, Phase: 0.270180657271},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
{Amp: 0.0038422977898495, Period: 0.00000000, Phase: 2.413392208631},
|
||||||
|
{Amp: 0.0000000000666922, Period: -192.00000000, Phase: -2.221562730997},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
var jupiterGalileanL1Chebyshev = [4][5][9]float64{
|
||||||
|
{
|
||||||
|
{8.26698820334074e-005, -5.46986318473484e-006, 5.94965634346142e-005, -1.10076009434021e-005, -5.75048718532862e-006, 5.74994024502453e-006, 1.78506935927118e-006, -2.40677412003469e-006, -1.38513209122323e-006},
|
||||||
|
{-1.33581115495368e-007, -7.58579668623469e-009, -1.73800453537518e-009, -3.02381800276162e-009, -7.21047445746571e-009, -3.92259073275152e-010, -4.16879673796892e-009, -3.29083173926104e-009, 2.88537255009037e-009},
|
||||||
|
{-4.47550080805276e-008, -5.78177122641661e-010, -5.35142525086474e-009, 1.02942385189608e-010, -8.49040607452073e-009, -2.52840670456370e-009, -6.13405545173217e-009, -3.43800069555589e-009, -3.59984191898440e-009},
|
||||||
|
{-2.91234929817703e-007, -1.62905002759586e-007, -3.68431020234090e-009, 7.77164290527916e-010, 1.59932283427177e-009, -2.28491296590504e-009, 7.03033944342130e-010, -2.89289190860300e-011, 4.86451562749639e-010},
|
||||||
|
{-3.17117027043262e-007, -1.26321744097649e-007, 6.51758518535435e-009, 2.62070675646012e-009, 2.59238370832140e-009, -7.26318061740534e-010, -1.18191642174809e-009, 5.90816038543625e-010, 1.23053999757825e-009},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
{-2.34691437046728e-004, 2.59036121717763e-005, -1.73835841495123e-004, 3.80673357683932e-005, -1.03485888270120e-007, 1.17376776995138e-005, 3.19509608989603e-006, -4.83167496026174e-006, 4.47094431509368e-007},
|
||||||
|
{-1.00355398526925e-006, -5.80438343691240e-008, 3.80875695145265e-008, -2.10657418625019e-007, 3.12238251072476e-007, -1.72444072203130e-007, 2.35985832098386e-007, 6.13727340553035e-008, -1.58131098756712e-007},
|
||||||
|
{-2.61024960215820e-007, -1.82656275716032e-007, 2.78016374834476e-008, -1.58858363289506e-007, 2.31850746815330e-008, -1.10691155968765e-007, -3.69048459423356e-008, 6.78631069587260e-009, 1.90777145516695e-008},
|
||||||
|
{-2.74704767394256e-006, -1.54365304913777e-006, -3.49546762061882e-008, 2.86789414864130e-009, 1.75452766852069e-008, -1.97744662243578e-008, 3.43169676541053e-009, -1.30924950693160e-010, 1.35338072425105e-008},
|
||||||
|
{-2.98415696654187e-006, -1.19708851225481e-006, 5.67146658521585e-008, 2.68814727457189e-008, 2.61897719058852e-008, -5.11649608698392e-009, -1.27841777400320e-008, 1.19534263567129e-008, 1.15749543781328e-008},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
{-4.02499890223698e-004, 4.22870514937427e-005, -2.88258753042082e-004, 6.23338370193608e-005, 1.78692873660163e-006, 1.44147643545141e-005, 6.58944353306297e-006, -6.19139500170464e-006, 7.82954615405540e-007},
|
||||||
|
{-1.11797459279082e-007, -2.78967292638245e-007, -7.04645336679784e-008, -2.38693040681653e-007, 1.22869992713930e-008, -1.53370399783488e-007, 4.47318789843126e-008, -1.65802124252890e-008, -8.28946056921499e-008},
|
||||||
|
{-1.07044201697345e-007, -2.23668695371038e-007, -2.18527598721759e-007, -2.00040954734845e-007, -3.19586844323525e-007, -2.22086683443490e-007, -2.98609138042077e-007, -1.29217448782639e-007, -1.50654590880644e-007},
|
||||||
|
{-1.41968295962213e-005, -7.93995493982020e-006, -2.08882224662785e-007, 2.09762683478441e-008, 8.35070484335148e-008, -1.07729670354460e-007, 3.06301230195125e-008, 1.12449433356374e-008, 7.08786417409988e-009},
|
||||||
|
{-1.53976109321613e-005, -6.12022141299496e-006, 3.15217558237908e-007, 1.48179418616661e-007, 1.41150094231365e-007, -2.24063936978287e-008, -3.33311708439601e-008, 1.07863442857316e-008, 6.62499119729457e-008},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
{3.06204347064615e-004, -1.68321429486551e-004, 2.02182016979082e-004, -1.37641833741129e-004, -5.30404402536766e-005, -1.10474926063654e-005, -1.05634674903822e-005, 3.11526543050599e-005, 4.66495863544146e-006},
|
||||||
|
{-1.81310025984216e-007, -1.26014618556329e-006, -3.82306332383069e-007, -6.67102945644653e-007, -5.05214970641581e-008, 1.53918794369926e-007, 9.28279781805284e-008, 3.66048038576706e-007, 5.68038769950531e-008},
|
||||||
|
{-1.40782617429892e-006, -1.35394137498700e-006, -3.20084097458594e-007, -6.20618627039507e-007, -6.11742588608485e-007, -2.76537195728238e-007, -1.33810977431205e-007, 2.89677483700145e-007, 4.70713328715423e-008},
|
||||||
|
{-6.40936655172450e-005, -3.57948862513955e-005, -8.87261624128964e-007, 1.64506004407617e-007, 4.77415826267116e-007, -3.59030239515192e-007, 1.17406290303369e-007, 8.95051737231057e-008, 9.71875658565188e-009},
|
||||||
|
{-6.96069435911693e-005, -2.76636397672750e-005, 1.38328020437413e-006, 6.51631405812798e-007, 6.20307849865976e-007, -2.66081185506962e-007, -2.09003718083271e-007, -1.46372661194868e-007, -1.22390974721364e-007},
|
||||||
|
},
|
||||||
|
}
|
||||||
@@ -0,0 +1,201 @@
|
|||||||
|
package basic
|
||||||
|
|
||||||
|
import "math"
|
||||||
|
|
||||||
|
const solarRadiusAU = 695700.0 / astronomicalUnitKM
|
||||||
|
|
||||||
|
// JupiterGalileanPhenomenon 木星伽利略卫星瞬时现象 / instantaneous Galilean-satellite phenomena.
|
||||||
|
//
|
||||||
|
// Transit 表示卫星本体在木星盘前;Occultation 表示卫星在木星盘后被掩蔽;Eclipse 表示卫星落入木星本影;ShadowTransit 表示卫星影心落在可见木星盘面上。
|
||||||
|
// Transit means the satellite itself is in front of Jupiter's disk; Occultation means it is hidden behind the disk; Eclipse means the satellite lies in Jupiter's umbra; ShadowTransit means the center of the satellite shadow falls on the visible Jovian disk.
|
||||||
|
type JupiterGalileanPhenomenon struct {
|
||||||
|
Transit bool
|
||||||
|
Occultation bool
|
||||||
|
Eclipse bool
|
||||||
|
ShadowTransit bool
|
||||||
|
|
||||||
|
ShadowOffsetXArcsec float64
|
||||||
|
ShadowOffsetYArcsec float64
|
||||||
|
|
||||||
|
ShadowOffsetXJupiterRadii float64
|
||||||
|
ShadowOffsetYJupiterRadii float64
|
||||||
|
}
|
||||||
|
|
||||||
|
// JupiterGalileanSatellitePhenomenon 单颗伽利略卫星瞬时现象 / instantaneous phenomena of one Galilean satellite.
|
||||||
|
func JupiterGalileanSatellitePhenomenon(jd float64, satellite int) JupiterGalileanPhenomenon {
|
||||||
|
if satellite < 1 || satellite > 4 || !isFinite(jd) {
|
||||||
|
return invalidJupiterGalileanPhenomenon()
|
||||||
|
}
|
||||||
|
context := newJupiterGalileanObservationContext(jd)
|
||||||
|
return context.phenomenonForSatellite(satellite - 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// JupiterGalileanSatellitePhenomena 四颗伽利略卫星瞬时现象 / instantaneous phenomena of the four Galilean satellites.
|
||||||
|
func JupiterGalileanSatellitePhenomena(jd float64) [4]JupiterGalileanPhenomenon {
|
||||||
|
var phenomena [4]JupiterGalileanPhenomenon
|
||||||
|
context := newJupiterGalileanObservationContext(jd)
|
||||||
|
for i := range phenomena {
|
||||||
|
phenomena[i] = context.phenomenonForSatellite(i)
|
||||||
|
}
|
||||||
|
return phenomena
|
||||||
|
}
|
||||||
|
|
||||||
|
func (context jupiterGalileanObservationContext) phenomenonForSatellite(index int) JupiterGalileanPhenomenon {
|
||||||
|
if index < 0 || index >= 4 || context.jupiterDistance == 0 {
|
||||||
|
return invalidJupiterGalileanPhenomenon()
|
||||||
|
}
|
||||||
|
observation := context.observationForSatellite(index)
|
||||||
|
stateVector := Vector3{observation.State.X, observation.State.Y, observation.State.Z}
|
||||||
|
radiusAU := jupiterGalileanEquatorialRadiusKM / astronomicalUnitKM
|
||||||
|
xEarth := observation.OffsetXJupiterRadii
|
||||||
|
yEarth := observation.OffsetYJupiterRadii
|
||||||
|
onEarthDisk := ellipseInside(xEarth, yEarth, 1, context.earthMinorRadius)
|
||||||
|
|
||||||
|
xSunAU := vectorDot(stateVector, context.sunEast)
|
||||||
|
ySunAU := vectorDot(stateVector, context.sunNorth)
|
||||||
|
zSunAU := vectorDot(stateVector, context.sunLineOfSight)
|
||||||
|
xSun := xSunAU / radiusAU
|
||||||
|
ySun := ySunAU / radiusAU
|
||||||
|
umbraScale := jupiterUmbraScale(zSunAU, context.sunDistanceAU)
|
||||||
|
eclipse := false
|
||||||
|
if zSunAU > 0 && umbraScale > 0 {
|
||||||
|
eclipse = ellipseInside(xSun, ySun, umbraScale, context.sunMinorRadius*umbraScale)
|
||||||
|
}
|
||||||
|
|
||||||
|
shadowTransit, shadowXAU, shadowYAU := context.shadowTransitFor(stateVector)
|
||||||
|
phenomenon := JupiterGalileanPhenomenon{
|
||||||
|
Transit: onEarthDisk && observation.InFrontOfJupiter,
|
||||||
|
Occultation: onEarthDisk && !observation.InFrontOfJupiter,
|
||||||
|
Eclipse: eclipse,
|
||||||
|
ShadowTransit: shadowTransit,
|
||||||
|
|
||||||
|
ShadowOffsetXArcsec: math.NaN(),
|
||||||
|
ShadowOffsetYArcsec: math.NaN(),
|
||||||
|
ShadowOffsetXJupiterRadii: math.NaN(),
|
||||||
|
ShadowOffsetYJupiterRadii: math.NaN(),
|
||||||
|
}
|
||||||
|
if shadowTransit {
|
||||||
|
phenomenon.ShadowOffsetXArcsec = math.Atan2(shadowXAU, context.jupiterDistance) * deg * 3600
|
||||||
|
phenomenon.ShadowOffsetYArcsec = math.Atan2(shadowYAU, context.jupiterDistance) * deg * 3600
|
||||||
|
phenomenon.ShadowOffsetXJupiterRadii = shadowXAU / radiusAU
|
||||||
|
phenomenon.ShadowOffsetYJupiterRadii = shadowYAU / radiusAU
|
||||||
|
}
|
||||||
|
return phenomenon
|
||||||
|
}
|
||||||
|
|
||||||
|
func (context jupiterGalileanObservationContext) shadowTransitFor(stateVector Vector3) (bool, float64, float64) {
|
||||||
|
radiusAU := jupiterGalileanEquatorialRadiusKM / astronomicalUnitKM
|
||||||
|
satelliteBody := context.toBodyCoordinates(stateVector)
|
||||||
|
satelliteBody = Vector3{satelliteBody[0] / radiusAU, satelliteBody[1] / radiusAU, satelliteBody[2] / radiusAU}
|
||||||
|
directionBody := context.toBodyCoordinates(context.sunLineOfSight)
|
||||||
|
intersectionBody, ok := ellipsoidRayIntersection(satelliteBody, directionBody, jupiterPolarRadiusRatio())
|
||||||
|
if !ok {
|
||||||
|
return false, 0, 0
|
||||||
|
}
|
||||||
|
normalBody := Vector3{intersectionBody[0], intersectionBody[1], intersectionBody[2] / (jupiterPolarRadiusRatio() * jupiterPolarRadiusRatio())}
|
||||||
|
earthBody := context.toBodyCoordinates(context.earthDirection)
|
||||||
|
if vectorDot(normalBody, earthBody) <= 0 {
|
||||||
|
return false, 0, 0
|
||||||
|
}
|
||||||
|
intersection := context.fromBodyCoordinates(Vector3{
|
||||||
|
intersectionBody[0] * radiusAU,
|
||||||
|
intersectionBody[1] * radiusAU,
|
||||||
|
intersectionBody[2] * radiusAU,
|
||||||
|
})
|
||||||
|
xAU := vectorDot(intersection, context.east)
|
||||||
|
yAU := vectorDot(intersection, context.north)
|
||||||
|
x := xAU / radiusAU
|
||||||
|
y := yAU / radiusAU
|
||||||
|
if !ellipseInside(x, y, 1, context.earthMinorRadius) {
|
||||||
|
return false, 0, 0
|
||||||
|
}
|
||||||
|
return true, xAU, yAU
|
||||||
|
}
|
||||||
|
|
||||||
|
func (context jupiterGalileanObservationContext) toBodyCoordinates(vector Vector3) Vector3 {
|
||||||
|
return Vector3{
|
||||||
|
vectorDot(vector, context.bodyX),
|
||||||
|
vectorDot(vector, context.bodyY),
|
||||||
|
vectorDot(vector, context.bodyZ),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (context jupiterGalileanObservationContext) fromBodyCoordinates(vector Vector3) Vector3 {
|
||||||
|
return Vector3{
|
||||||
|
context.bodyX[0]*vector[0] + context.bodyY[0]*vector[1] + context.bodyZ[0]*vector[2],
|
||||||
|
context.bodyX[1]*vector[0] + context.bodyY[1]*vector[1] + context.bodyZ[1]*vector[2],
|
||||||
|
context.bodyX[2]*vector[0] + context.bodyY[2]*vector[1] + context.bodyZ[2]*vector[2],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func jupiterProjectedMinorRadius(direction, pole Vector3) float64 {
|
||||||
|
sinBeta := vectorDot(direction, pole)
|
||||||
|
cos2Beta := 1 - sinBeta*sinBeta
|
||||||
|
if cos2Beta < 0 {
|
||||||
|
cos2Beta = 0
|
||||||
|
}
|
||||||
|
ratio := jupiterPolarRadiusRatio()
|
||||||
|
return math.Sqrt(sinBeta*sinBeta + ratio*ratio*cos2Beta)
|
||||||
|
}
|
||||||
|
|
||||||
|
func jupiterPolarRadiusRatio() float64 {
|
||||||
|
return jupiterPhysicalModel.polarRadius / jupiterPhysicalModel.equatorialRadius
|
||||||
|
}
|
||||||
|
|
||||||
|
func ellipseInside(x, y, major, minor float64) bool {
|
||||||
|
if major <= 0 || minor <= 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return (x*x)/(major*major)+(y*y)/(minor*minor) <= 1+1e-12
|
||||||
|
}
|
||||||
|
|
||||||
|
func ellipsoidRayIntersection(origin, direction Vector3, polarRatio float64) (Vector3, bool) {
|
||||||
|
invPolar2 := 1 / (polarRatio * polarRatio)
|
||||||
|
a := direction[0]*direction[0] + direction[1]*direction[1] + direction[2]*direction[2]*invPolar2
|
||||||
|
b := 2 * (origin[0]*direction[0] + origin[1]*direction[1] + origin[2]*direction[2]*invPolar2)
|
||||||
|
c := origin[0]*origin[0] + origin[1]*origin[1] + origin[2]*origin[2]*invPolar2 - 1
|
||||||
|
discriminant := b*b - 4*a*c
|
||||||
|
if discriminant < 0 {
|
||||||
|
return Vector3{}, false
|
||||||
|
}
|
||||||
|
sqrtDiscriminant := math.Sqrt(discriminant)
|
||||||
|
t1 := (-b - sqrtDiscriminant) / (2 * a)
|
||||||
|
t2 := (-b + sqrtDiscriminant) / (2 * a)
|
||||||
|
t := math.Inf(1)
|
||||||
|
if t1 > 0 {
|
||||||
|
t = t1
|
||||||
|
}
|
||||||
|
if t2 > 0 && t2 < t {
|
||||||
|
t = t2
|
||||||
|
}
|
||||||
|
if !isFinite(t) {
|
||||||
|
return Vector3{}, false
|
||||||
|
}
|
||||||
|
return Vector3{
|
||||||
|
origin[0] + t*direction[0],
|
||||||
|
origin[1] + t*direction[1],
|
||||||
|
origin[2] + t*direction[2],
|
||||||
|
}, true
|
||||||
|
}
|
||||||
|
|
||||||
|
func jupiterUmbraScale(distanceBehindAU, sunDistanceAU float64) float64 {
|
||||||
|
if distanceBehindAU <= 0 || sunDistanceAU <= 0 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
jupiterRadiusAU := jupiterGalileanEquatorialRadiusKM / astronomicalUnitKM
|
||||||
|
umbraLength := jupiterRadiusAU * sunDistanceAU / (solarRadiusAU - jupiterRadiusAU)
|
||||||
|
if umbraLength <= 0 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return 1 - distanceBehindAU/umbraLength
|
||||||
|
}
|
||||||
|
|
||||||
|
func invalidJupiterGalileanPhenomenon() JupiterGalileanPhenomenon {
|
||||||
|
nan := math.NaN()
|
||||||
|
return JupiterGalileanPhenomenon{
|
||||||
|
ShadowOffsetXArcsec: nan,
|
||||||
|
ShadowOffsetYArcsec: nan,
|
||||||
|
ShadowOffsetXJupiterRadii: nan,
|
||||||
|
ShadowOffsetYJupiterRadii: nan,
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,424 @@
|
|||||||
|
package basic
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math"
|
||||||
|
|
||||||
|
"b612.me/astro/planet"
|
||||||
|
. "b612.me/astro/tools"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
jupiterGalileanReferenceJD = 2433282.5
|
||||||
|
jupiterGalileanLongPeriodShift = 310910.16
|
||||||
|
jupiterGalileanMinSolarLPYear = 1150.0
|
||||||
|
jupiterGalileanMaxSolarLPYear = 2750.0
|
||||||
|
jupiterGalileanEquatorialRadiusKM = 71492.0
|
||||||
|
astronomicalUnitKM = 149597870.691
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
jupiterGalileanBaseMeanLongitudes = [4]float64{3.55155228618240, 1.76932271112347, 0.878207923589328, 0.376486233433828}
|
||||||
|
jupiterGalileanMu = [4]float64{2.82489428433814e-07, 2.82483274392893e-07, 2.82498184184723e-07, 2.82492144889909e-07}
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
jupiterGalileanFrameNode = 6.24950183065715
|
||||||
|
jupiterGalileanFrameTilt = 0.445094736497665
|
||||||
|
)
|
||||||
|
|
||||||
|
type jupiterGalileanL1Term struct {
|
||||||
|
Amp float64
|
||||||
|
Period float64
|
||||||
|
Phase float64
|
||||||
|
}
|
||||||
|
|
||||||
|
// JupiterGalileanState 木星伽利略卫星原始状态 / raw Galilean-satellite state.
|
||||||
|
//
|
||||||
|
// 输入 jd 使用 TT/TDB 对应的儒略日;返回值为 IMCCE L1 理论的木心 J2000 平赤道直角坐标与速度,单位 AU / AU/day。
|
||||||
|
// The input jd is a TT/TDB Julian day. Returned coordinates are Jovicentric J2000 mean-equatorial position and velocity from the IMCCE L1 theory, in AU and AU/day.
|
||||||
|
type JupiterGalileanState struct {
|
||||||
|
X float64
|
||||||
|
Y float64
|
||||||
|
Z float64
|
||||||
|
VX float64
|
||||||
|
VY float64
|
||||||
|
VZ float64
|
||||||
|
}
|
||||||
|
|
||||||
|
// JupiterGalileanObservation 木星伽利略卫星视位置 / apparent Galilean-satellite geometry.
|
||||||
|
//
|
||||||
|
// 视位置相对木星中心定义:X 向天球东为正,Y 向天球北为正,Z>0 表示比木星更远、位于盘后。
|
||||||
|
// Apparent offsets are relative to Jupiter's center: X is positive to celestial east, Y to celestial north, and Z>0 means farther than Jupiter and behind the disk.
|
||||||
|
type JupiterGalileanObservation struct {
|
||||||
|
State JupiterGalileanState
|
||||||
|
|
||||||
|
RA float64
|
||||||
|
Dec float64
|
||||||
|
Distance float64
|
||||||
|
|
||||||
|
OffsetXArcsec float64
|
||||||
|
OffsetYArcsec float64
|
||||||
|
|
||||||
|
OffsetXJupiterRadii float64
|
||||||
|
OffsetYJupiterRadii float64
|
||||||
|
OffsetZJupiterRadii float64
|
||||||
|
|
||||||
|
InFrontOfJupiter bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// JupiterGalileanSatelliteState 伽利略卫星木心 J2000 状态 / Jovicentric J2000 state of a Galilean satellite.
|
||||||
|
//
|
||||||
|
// satellite 取 1=Io, 2=Europa, 3=Ganymede, 4=Callisto。jd 为 TT/TDB 对应儒略日。
|
||||||
|
// satellite is 1=Io, 2=Europa, 3=Ganymede, 4=Callisto. jd is a TT/TDB Julian day.
|
||||||
|
func JupiterGalileanSatelliteState(jd float64, satellite int) JupiterGalileanState {
|
||||||
|
if satellite < 1 || satellite > 4 || !isFinite(jd) {
|
||||||
|
return invalidJupiterGalileanState()
|
||||||
|
}
|
||||||
|
et := jd - jupiterGalileanReferenceJD
|
||||||
|
includeSolarLongPeriod := jupiterGalileanUseSolarLongPeriod(jd)
|
||||||
|
return jupiterGalileanSatelliteStateAtET(et, satellite-1, includeSolarLongPeriod)
|
||||||
|
}
|
||||||
|
|
||||||
|
// JupiterGalileanSatelliteStates 四颗伽利略卫星木心 J2000 状态 / Jovicentric J2000 states of the four Galilean satellites.
|
||||||
|
//
|
||||||
|
// 返回次序固定为 Io、Europa、Ganymede、Callisto。
|
||||||
|
// The returned order is Io, Europa, Ganymede, Callisto.
|
||||||
|
func JupiterGalileanSatelliteStates(jd float64) [4]JupiterGalileanState {
|
||||||
|
var states [4]JupiterGalileanState
|
||||||
|
et := jd - jupiterGalileanReferenceJD
|
||||||
|
includeSolarLongPeriod := jupiterGalileanUseSolarLongPeriod(jd)
|
||||||
|
for i := range states {
|
||||||
|
states[i] = jupiterGalileanSatelliteStateAtET(et, i, includeSolarLongPeriod)
|
||||||
|
}
|
||||||
|
return states
|
||||||
|
}
|
||||||
|
|
||||||
|
// JupiterGalileanSatelliteObservation 伽利略卫星视位置 / apparent geometry of a Galilean satellite.
|
||||||
|
//
|
||||||
|
// jd 为 TT/TDB 对应儒略日;返回卫星的天球视赤道坐标,以及相对木星中心的东/北平面偏移。
|
||||||
|
// jd is a TT/TDB Julian day. The result contains the satellite's astrometric equatorial coordinates and its east/north sky-plane offsets relative to Jupiter's center.
|
||||||
|
func JupiterGalileanSatelliteObservation(jd float64, satellite int) JupiterGalileanObservation {
|
||||||
|
if satellite < 1 || satellite > 4 || !isFinite(jd) {
|
||||||
|
return invalidJupiterGalileanObservation()
|
||||||
|
}
|
||||||
|
context := newJupiterGalileanObservationContext(jd)
|
||||||
|
return context.observationForSatellite(satellite - 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// JupiterGalileanSatelliteObservations 四颗伽利略卫星视位置 / apparent geometry of the four Galilean satellites.
|
||||||
|
//
|
||||||
|
// 返回次序固定为 Io、Europa、Ganymede、Callisto。
|
||||||
|
// The returned order is Io, Europa, Ganymede, Callisto.
|
||||||
|
func JupiterGalileanSatelliteObservations(jd float64) [4]JupiterGalileanObservation {
|
||||||
|
var observations [4]JupiterGalileanObservation
|
||||||
|
context := newJupiterGalileanObservationContext(jd)
|
||||||
|
for i := range observations {
|
||||||
|
observations[i] = context.observationForSatellite(i)
|
||||||
|
}
|
||||||
|
return observations
|
||||||
|
}
|
||||||
|
|
||||||
|
type jupiterGalileanObservationContext struct {
|
||||||
|
jd float64
|
||||||
|
targetJD float64
|
||||||
|
earthHelioJ2000 Vector3
|
||||||
|
jupiterGeoJ2000 Vector3
|
||||||
|
jupiterDistance float64
|
||||||
|
jupiterLightTime float64
|
||||||
|
sunDistanceAU float64
|
||||||
|
east Vector3
|
||||||
|
north Vector3
|
||||||
|
lineOfSight Vector3
|
||||||
|
earthDirection Vector3
|
||||||
|
sunDirection Vector3
|
||||||
|
sunLineOfSight Vector3
|
||||||
|
sunEast Vector3
|
||||||
|
sunNorth Vector3
|
||||||
|
earthMinorRadius float64
|
||||||
|
sunMinorRadius float64
|
||||||
|
bodyX Vector3
|
||||||
|
bodyY Vector3
|
||||||
|
bodyZ Vector3
|
||||||
|
}
|
||||||
|
|
||||||
|
func newJupiterGalileanObservationContext(jd float64) jupiterGalileanObservationContext {
|
||||||
|
context := jupiterGalileanObservationContext{jd: jd}
|
||||||
|
if !isFinite(jd) {
|
||||||
|
return context
|
||||||
|
}
|
||||||
|
context.earthHelioJ2000 = rotateEclipticToEquatorial(earthHeliocentricVectorJ2000(jd), orbitJ2000Obliquity)
|
||||||
|
context.jupiterGeoJ2000, context.jupiterLightTime = jupiterAstrometricGeocentricVectorJ2000(jd, context.earthHelioJ2000)
|
||||||
|
context.targetJD = jd - context.jupiterLightTime
|
||||||
|
context.jupiterDistance = vectorMagnitude(context.jupiterGeoJ2000)
|
||||||
|
if context.jupiterDistance == 0 {
|
||||||
|
return context
|
||||||
|
}
|
||||||
|
context.lineOfSight = normalizeVector(context.jupiterGeoJ2000)
|
||||||
|
context.earthDirection = Vector3{-context.lineOfSight[0], -context.lineOfSight[1], -context.lineOfSight[2]}
|
||||||
|
ra, dec := vectorToRaDec(context.lineOfSight)
|
||||||
|
context.east = Vector3{-Sin(ra), Cos(ra), 0}
|
||||||
|
context.north = Vector3{-Cos(ra) * Sin(dec), -Sin(ra) * Sin(dec), Cos(dec)}
|
||||||
|
jupiterHelio := rotateEclipticToEquatorial(jupiterHeliocentricVectorJ2000(context.targetJD), orbitJ2000Obliquity)
|
||||||
|
context.sunDistanceAU = vectorMagnitude(jupiterHelio)
|
||||||
|
context.sunDirection = normalizeVector(Vector3{-jupiterHelio[0], -jupiterHelio[1], -jupiterHelio[2]})
|
||||||
|
context.sunLineOfSight = Vector3{-context.sunDirection[0], -context.sunDirection[1], -context.sunDirection[2]}
|
||||||
|
sunRA, sunDec := vectorToRaDec(context.sunLineOfSight)
|
||||||
|
context.sunEast = Vector3{-Sin(sunRA), Cos(sunRA), 0}
|
||||||
|
context.sunNorth = Vector3{-Cos(sunRA) * Sin(sunDec), -Sin(sunRA) * Sin(sunDec), Cos(sunDec)}
|
||||||
|
poleRA, poleDec, _ := jupiterPoleRotation(context.targetJD)
|
||||||
|
context.bodyZ = raDecToVector(poleRA, poleDec)
|
||||||
|
context.bodyX = normalizeVector(Vector3{-math.Sin(poleRA * rad), math.Cos(poleRA * rad), 0})
|
||||||
|
context.bodyY = normalizeVector(pxp(context.bodyZ, context.bodyX))
|
||||||
|
context.earthMinorRadius = jupiterProjectedMinorRadius(context.earthDirection, context.bodyZ)
|
||||||
|
context.sunMinorRadius = jupiterProjectedMinorRadius(context.sunDirection, context.bodyZ)
|
||||||
|
return context
|
||||||
|
}
|
||||||
|
|
||||||
|
func (context jupiterGalileanObservationContext) observationForSatellite(index int) JupiterGalileanObservation {
|
||||||
|
if index < 0 || index >= 4 || context.jupiterDistance == 0 {
|
||||||
|
return invalidJupiterGalileanObservation()
|
||||||
|
}
|
||||||
|
state, geocentric := jupiterGalileanSatelliteAstrometricGeocentric(index, context.jd, context.jupiterLightTime, context.earthHelioJ2000)
|
||||||
|
direction := normalizeVector(geocentric)
|
||||||
|
ra, dec := vectorToRaDec(direction)
|
||||||
|
distance := vectorMagnitude(geocentric)
|
||||||
|
relative := Vector3{
|
||||||
|
geocentric[0] - context.jupiterGeoJ2000[0],
|
||||||
|
geocentric[1] - context.jupiterGeoJ2000[1],
|
||||||
|
geocentric[2] - context.jupiterGeoJ2000[2],
|
||||||
|
}
|
||||||
|
zAU := vectorDot(relative, context.lineOfSight)
|
||||||
|
radiusAU := jupiterGalileanEquatorialRadiusKM / astronomicalUnitKM
|
||||||
|
offsetXRad, offsetYRad := tangentPlaneOffsetAngles(direction, context.lineOfSight, context.east, context.north)
|
||||||
|
jupiterSemidiameterArcsec := math.Atan2(radiusAU, context.jupiterDistance) * deg * 3600
|
||||||
|
return JupiterGalileanObservation{
|
||||||
|
State: state,
|
||||||
|
|
||||||
|
RA: ra,
|
||||||
|
Dec: dec,
|
||||||
|
Distance: distance,
|
||||||
|
|
||||||
|
OffsetXArcsec: offsetXRad * deg * 3600,
|
||||||
|
OffsetYArcsec: offsetYRad * deg * 3600,
|
||||||
|
|
||||||
|
OffsetXJupiterRadii: offsetXRad * deg * 3600 / jupiterSemidiameterArcsec,
|
||||||
|
OffsetYJupiterRadii: offsetYRad * deg * 3600 / jupiterSemidiameterArcsec,
|
||||||
|
OffsetZJupiterRadii: zAU / radiusAU,
|
||||||
|
|
||||||
|
InFrontOfJupiter: zAU < 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func tangentPlaneOffsetAngles(target, center, east, north Vector3) (float64, float64) {
|
||||||
|
denominator := vectorDot(target, center)
|
||||||
|
return math.Atan2(vectorDot(target, east), denominator), math.Atan2(vectorDot(target, north), denominator)
|
||||||
|
}
|
||||||
|
|
||||||
|
func jupiterGalileanSatelliteAstrometricGeocentric(index int, jd, initialLightTime float64, earthHelioJ2000 Vector3) (JupiterGalileanState, Vector3) {
|
||||||
|
lightTime := initialLightTime
|
||||||
|
state := JupiterGalileanState{}
|
||||||
|
result := Vector3{}
|
||||||
|
includeSolarLongPeriod := jupiterGalileanUseSolarLongPeriod(jd)
|
||||||
|
for i := 0; i < 8; i++ {
|
||||||
|
targetJD := jd - lightTime
|
||||||
|
jupiterHelio := rotateEclipticToEquatorial(jupiterHeliocentricVectorJ2000(targetJD), orbitJ2000Obliquity)
|
||||||
|
state = jupiterGalileanSatelliteStateAtET(targetJD-jupiterGalileanReferenceJD, index, includeSolarLongPeriod)
|
||||||
|
result = Vector3{
|
||||||
|
jupiterHelio[0] + state.X - earthHelioJ2000[0],
|
||||||
|
jupiterHelio[1] + state.Y - earthHelioJ2000[1],
|
||||||
|
jupiterHelio[2] + state.Z - earthHelioJ2000[2],
|
||||||
|
}
|
||||||
|
nextLightTime := lightTimeDaysPerAU * vectorMagnitude(result)
|
||||||
|
if math.Abs(nextLightTime-lightTime) < 1e-12 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
lightTime = nextLightTime
|
||||||
|
}
|
||||||
|
return state, result
|
||||||
|
}
|
||||||
|
|
||||||
|
func jupiterAstrometricGeocentricVectorJ2000(jd float64, earthHelioJ2000 Vector3) (Vector3, float64) {
|
||||||
|
lightTime := 0.0
|
||||||
|
result := Vector3{}
|
||||||
|
for i := 0; i < 8; i++ {
|
||||||
|
jupiterHelio := rotateEclipticToEquatorial(jupiterHeliocentricVectorJ2000(jd-lightTime), orbitJ2000Obliquity)
|
||||||
|
result = Vector3{
|
||||||
|
jupiterHelio[0] - earthHelioJ2000[0],
|
||||||
|
jupiterHelio[1] - earthHelioJ2000[1],
|
||||||
|
jupiterHelio[2] - earthHelioJ2000[2],
|
||||||
|
}
|
||||||
|
nextLightTime := lightTimeDaysPerAU * vectorMagnitude(result)
|
||||||
|
if math.Abs(nextLightTime-lightTime) < 1e-12 {
|
||||||
|
return result, nextLightTime
|
||||||
|
}
|
||||||
|
lightTime = nextLightTime
|
||||||
|
}
|
||||||
|
return result, lightTime
|
||||||
|
}
|
||||||
|
|
||||||
|
func jupiterHeliocentricVectorJ2000(jd float64) Vector3 {
|
||||||
|
return eclipticVectorAtReferenceEpoch(
|
||||||
|
eclipticCartesian(
|
||||||
|
planet.WherePlanet(4, 0, jd),
|
||||||
|
planet.WherePlanet(4, 1, jd),
|
||||||
|
planet.WherePlanet(4, 2, jd),
|
||||||
|
),
|
||||||
|
jd,
|
||||||
|
orbitReferenceJD,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func jupiterGalileanSatelliteStateAtET(et float64, index int, includeSolarLongPeriod bool) JupiterGalileanState {
|
||||||
|
elements := jupiterGalileanElementsAtET(et, index, includeSolarLongPeriod)
|
||||||
|
pv := jupiterGalileanElementsToPV(jupiterGalileanMu[index], elements)
|
||||||
|
cosNode, sinNode := math.Cos(jupiterGalileanFrameNode), math.Sin(jupiterGalileanFrameNode)
|
||||||
|
cosTilt, sinTilt := math.Cos(jupiterGalileanFrameTilt), math.Sin(jupiterGalileanFrameTilt)
|
||||||
|
return JupiterGalileanState{
|
||||||
|
X: pv[0]*cosNode - pv[1]*sinNode*cosTilt + pv[2]*sinTilt*sinNode,
|
||||||
|
Y: pv[0]*sinNode + pv[1]*cosNode*cosTilt - pv[2]*sinTilt*cosNode,
|
||||||
|
Z: pv[1]*sinTilt + pv[2]*cosTilt,
|
||||||
|
VX: pv[3]*cosNode - pv[4]*sinNode*cosTilt + pv[5]*sinTilt*sinNode,
|
||||||
|
VY: pv[3]*sinNode + pv[4]*cosNode*cosTilt - pv[5]*sinTilt*cosNode,
|
||||||
|
VZ: pv[4]*sinTilt + pv[5]*cosTilt,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type jupiterGalileanElements struct {
|
||||||
|
A float64
|
||||||
|
L float64
|
||||||
|
K float64
|
||||||
|
H float64
|
||||||
|
Q float64
|
||||||
|
P float64
|
||||||
|
}
|
||||||
|
|
||||||
|
func jupiterGalileanElementsAtET(et float64, index int, includeSolarLongPeriod bool) jupiterGalileanElements {
|
||||||
|
longPeriod := jupiterGalileanEvaluateSeries(jupiterGalileanL1LongPeriodTerms[index], et+jupiterGalileanLongPeriodShift, et, includeSolarLongPeriod, index)
|
||||||
|
crossPeriod := jupiterGalileanEvaluateSeries(jupiterGalileanL1CrossPeriodTerms[index], et, et, false, index)
|
||||||
|
combined := jupiterGalileanElements{
|
||||||
|
A: longPeriod.A + crossPeriod.A,
|
||||||
|
L: longPeriod.L + crossPeriod.L + jupiterGalileanBaseMeanLongitudes[index]*et,
|
||||||
|
K: longPeriod.K + crossPeriod.K,
|
||||||
|
H: longPeriod.H + crossPeriod.H,
|
||||||
|
Q: longPeriod.Q + crossPeriod.Q,
|
||||||
|
P: longPeriod.P + crossPeriod.P,
|
||||||
|
}
|
||||||
|
combined.L = math.Atan2(math.Sin(combined.L), math.Cos(combined.L))
|
||||||
|
if combined.L < 0 {
|
||||||
|
combined.L += 2 * math.Pi
|
||||||
|
}
|
||||||
|
return combined
|
||||||
|
}
|
||||||
|
|
||||||
|
func jupiterGalileanEvaluateSeries(blocks [4][]jupiterGalileanL1Term, angleTime, et float64, includeSolarLongPeriod bool, index int) jupiterGalileanElements {
|
||||||
|
vals := [5]float64{}
|
||||||
|
if includeSolarLongPeriod {
|
||||||
|
x := (et/365.25 - 0.5*(812.721806990360-819.727638594856)) / (0.5 * (812.721806990360 - -819.727638594856))
|
||||||
|
tn := [9]float64{1, x}
|
||||||
|
for i := 2; i < len(tn); i++ {
|
||||||
|
tn[i] = 2*x*tn[i-1] - tn[i-2]
|
||||||
|
}
|
||||||
|
for variable := 0; variable < len(vals); variable++ {
|
||||||
|
sum := 0.0
|
||||||
|
for term := 0; term < len(tn); term++ {
|
||||||
|
sum += jupiterGalileanL1Chebyshev[index][variable][term] * tn[term]
|
||||||
|
}
|
||||||
|
vals[variable] = sum - 0.5*jupiterGalileanL1Chebyshev[index][variable][0]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
result := jupiterGalileanElements{}
|
||||||
|
for blockIndex, terms := range blocks {
|
||||||
|
realPart, imagPart := 0.0, 0.0
|
||||||
|
for _, term := range terms {
|
||||||
|
angle := term.Phase
|
||||||
|
if term.Period != 0 {
|
||||||
|
angle += 2 * math.Pi * angleTime / term.Period
|
||||||
|
}
|
||||||
|
realPart += term.Amp * math.Cos(angle)
|
||||||
|
imagPart += term.Amp * math.Sin(angle)
|
||||||
|
}
|
||||||
|
switch blockIndex {
|
||||||
|
case 0:
|
||||||
|
result.A = realPart
|
||||||
|
case 1:
|
||||||
|
result.L = realPart + vals[0]
|
||||||
|
case 2:
|
||||||
|
result.K = realPart + vals[1]
|
||||||
|
result.H = imagPart + vals[2]
|
||||||
|
case 3:
|
||||||
|
result.Q = realPart + vals[3]
|
||||||
|
result.P = imagPart + vals[4]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func jupiterGalileanElementsToPV(mu float64, elements jupiterGalileanElements) [6]float64 {
|
||||||
|
k := elements.K
|
||||||
|
h := elements.H
|
||||||
|
q := elements.Q
|
||||||
|
p := elements.P
|
||||||
|
a := elements.A
|
||||||
|
al := elements.L
|
||||||
|
an := math.Sqrt(mu / math.Pow(a, 3))
|
||||||
|
ee := al + k*math.Sin(al) - h*math.Cos(al)
|
||||||
|
for {
|
||||||
|
ce := math.Cos(ee)
|
||||||
|
se := math.Sin(ee)
|
||||||
|
de := (al - ee + k*se - h*ce) / (1 - k*ce - h*se)
|
||||||
|
ee += de
|
||||||
|
if math.Abs(de) < 1e-12 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ce := math.Cos(ee)
|
||||||
|
se := math.Sin(ee)
|
||||||
|
dle := h*ce - k*se
|
||||||
|
rsam1 := -k*ce - h*se
|
||||||
|
asr := 1 / (1 + rsam1)
|
||||||
|
phi := math.Sqrt(1 - k*k - h*h)
|
||||||
|
psi := 1 / (1 + phi)
|
||||||
|
x1 := a * (ce - k - psi*h*dle)
|
||||||
|
y1 := a * (se - h + psi*k*dle)
|
||||||
|
vx1 := an * asr * a * (-se - psi*h*rsam1)
|
||||||
|
vy1 := an * asr * a * (ce + psi*k*rsam1)
|
||||||
|
f2 := 2 * math.Sqrt(1-q*q-p*p)
|
||||||
|
p2 := 1 - 2*p*p
|
||||||
|
q2 := 1 - 2*q*q
|
||||||
|
pq := 2 * p * q
|
||||||
|
return [6]float64{
|
||||||
|
x1*p2 + y1*pq,
|
||||||
|
x1*pq + y1*q2,
|
||||||
|
(q*y1 - x1*p) * f2,
|
||||||
|
vx1*p2 + vy1*pq,
|
||||||
|
vx1*pq + vy1*q2,
|
||||||
|
(q*vy1 - vx1*p) * f2,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func jupiterGalileanUseSolarLongPeriod(jd float64) bool {
|
||||||
|
year := 2000.0 + (jd-2451545.0)/365.25
|
||||||
|
return year >= jupiterGalileanMinSolarLPYear && year <= jupiterGalileanMaxSolarLPYear
|
||||||
|
}
|
||||||
|
|
||||||
|
func invalidJupiterGalileanState() JupiterGalileanState {
|
||||||
|
nan := math.NaN()
|
||||||
|
return JupiterGalileanState{X: nan, Y: nan, Z: nan, VX: nan, VY: nan, VZ: nan}
|
||||||
|
}
|
||||||
|
|
||||||
|
func invalidJupiterGalileanObservation() JupiterGalileanObservation {
|
||||||
|
nan := math.NaN()
|
||||||
|
return JupiterGalileanObservation{
|
||||||
|
State: invalidJupiterGalileanState(),
|
||||||
|
RA: nan,
|
||||||
|
Dec: nan,
|
||||||
|
Distance: nan,
|
||||||
|
OffsetXArcsec: nan,
|
||||||
|
OffsetYArcsec: nan,
|
||||||
|
OffsetXJupiterRadii: nan,
|
||||||
|
OffsetYJupiterRadii: nan,
|
||||||
|
OffsetZJupiterRadii: nan,
|
||||||
|
InFrontOfJupiter: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,75 @@
|
|||||||
|
package basic
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
const galileanSampleToleranceAU = 1e-15
|
||||||
|
const galileanSampleToleranceAUDay = 1e-15
|
||||||
|
|
||||||
|
type galileanSample struct {
|
||||||
|
name string
|
||||||
|
index int
|
||||||
|
state JupiterGalileanState
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestJupiterGalileanSatelliteStateMatchesIMCCESample(t *testing.T) {
|
||||||
|
samples := []galileanSample{
|
||||||
|
{
|
||||||
|
name: "Io",
|
||||||
|
index: 1,
|
||||||
|
state: JupiterGalileanState{X: 2.671999370920431e-003, Y: 7.644018403387422e-004, Z: 4.087344808808269e-004, VX: -3.116203340625001e-003, VY: 8.645679572984422e-003, VZ: 4.066210333795641e-003},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Europa",
|
||||||
|
index: 2,
|
||||||
|
state: JupiterGalileanState{X: -3.751373844521062e-003, Y: -2.136179970327756e-003, Z: -1.056765216826830e-003, VX: 4.310591732986133e-003, VY: -6.143199976514738e-003, VZ: -2.800434328620005e-003},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Ganymede",
|
||||||
|
index: 3,
|
||||||
|
state: JupiterGalileanState{X: -5.490036250442612e-003, Y: -4.112229247907583e-003, Z: -2.033821277493470e-003, VX: 4.036147912130572e-003, VY: -4.364866691392988e-003, VZ: -2.037111499364415e-003},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Callisto",
|
||||||
|
index: 4,
|
||||||
|
state: JupiterGalileanState{X: 2.172082907229073e-003, Y: 1.118792302205555e-002, Z: 5.322275059416266e-003, VX: -4.662583658656747e-003, VY: 7.976685330152526e-004, VZ: 3.092058747362411e-004},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
const jd = 2451545.0
|
||||||
|
maxPosDiff := 0.0
|
||||||
|
maxVelDiff := 0.0
|
||||||
|
for _, sample := range samples {
|
||||||
|
got := JupiterGalileanSatelliteState(jd, sample.index)
|
||||||
|
posDiffs := []float64{
|
||||||
|
math.Abs(got.X - sample.state.X),
|
||||||
|
math.Abs(got.Y - sample.state.Y),
|
||||||
|
math.Abs(got.Z - sample.state.Z),
|
||||||
|
}
|
||||||
|
velDiffs := []float64{
|
||||||
|
math.Abs(got.VX - sample.state.VX),
|
||||||
|
math.Abs(got.VY - sample.state.VY),
|
||||||
|
math.Abs(got.VZ - sample.state.VZ),
|
||||||
|
}
|
||||||
|
for i, diff := range posDiffs {
|
||||||
|
if diff > maxPosDiff {
|
||||||
|
maxPosDiff = diff
|
||||||
|
}
|
||||||
|
if diff > galileanSampleToleranceAU {
|
||||||
|
t.Fatalf("%s position[%d] mismatch: got %.18e want %.18e", sample.name, i, []float64{got.X, got.Y, got.Z}[i], []float64{sample.state.X, sample.state.Y, sample.state.Z}[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for i, diff := range velDiffs {
|
||||||
|
if diff > maxVelDiff {
|
||||||
|
maxVelDiff = diff
|
||||||
|
}
|
||||||
|
if diff > galileanSampleToleranceAUDay {
|
||||||
|
t.Fatalf("%s velocity[%d] mismatch: got %.18e want %.18e", sample.name, i, []float64{got.VX, got.VY, got.VZ}[i], []float64{sample.state.VX, sample.state.VY, sample.state.VZ}[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Official IMCCE README example for V1_1 at JD 2451545.0.
|
||||||
|
t.Logf("galilean IMCCE sample max diff: position=%.3e AU velocity=%.3e AU/day", maxPosDiff, maxVelDiff)
|
||||||
|
}
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
package basic
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestJupiter(t *testing.T) {
|
|
||||||
jde := GetNowJDE() - 6000
|
|
||||||
for i := 0.00; i < 20; i++ {
|
|
||||||
fmt.Println(jde+i*365, JDE2Date(jde+i*365), JDE2Date(NextJupiterRetrogradeToPrograde(jde+i*365)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,347 @@
|
|||||||
|
package basic
|
||||||
|
|
||||||
|
import "math"
|
||||||
|
|
||||||
|
// LunarEclipseType 表示月食类型。
|
||||||
|
type LunarEclipseType string
|
||||||
|
|
||||||
|
const (
|
||||||
|
// LunarEclipseNone 表示该次望月没有发生月食。
|
||||||
|
LunarEclipseNone LunarEclipseType = "none"
|
||||||
|
// LunarEclipsePenumbral 表示半影月食。
|
||||||
|
LunarEclipsePenumbral LunarEclipseType = "penumbral"
|
||||||
|
// LunarEclipsePartial 表示月偏食。
|
||||||
|
LunarEclipsePartial LunarEclipseType = "partial"
|
||||||
|
// LunarEclipseTotal 表示月全食。
|
||||||
|
LunarEclipseTotal LunarEclipseType = "total"
|
||||||
|
)
|
||||||
|
|
||||||
|
// LunarEclipseResult 表示一次望月附近的月食几何结果。
|
||||||
|
//
|
||||||
|
// 所有时刻字段都使用力学时儒略日(JDE, TT)。
|
||||||
|
// 输入 seedJDE 只需要落在目标望月附近,允许相差数天。
|
||||||
|
type LunarEclipseResult struct {
|
||||||
|
Type LunarEclipseType
|
||||||
|
|
||||||
|
// Maximum 是食甚时刻;即使最终没有月食,也会返回该次望月附近
|
||||||
|
// “月面中心最接近地影中心”的几何极值时刻。
|
||||||
|
Maximum float64
|
||||||
|
|
||||||
|
// Magnitude 是本影食分。纯半影月食时可为负值;无月食时为 0。
|
||||||
|
Magnitude float64
|
||||||
|
// PenumbralMagnitude 是半影食分。无半影接触时为 0。
|
||||||
|
PenumbralMagnitude float64
|
||||||
|
|
||||||
|
// MinimumDistance 是食甚时月心到地影中心的最小角距离,单位为弧度。
|
||||||
|
MinimumDistance float64
|
||||||
|
|
||||||
|
// Contact times:
|
||||||
|
// PenumbralStart / PenumbralEnd: 半影食始 / 半影食终
|
||||||
|
// PartialStart / PartialEnd: 初亏 / 复圆
|
||||||
|
// TotalStart / TotalEnd: 食既 / 生光
|
||||||
|
PenumbralStart float64
|
||||||
|
PenumbralEnd float64
|
||||||
|
PartialStart float64
|
||||||
|
PartialEnd float64
|
||||||
|
TotalStart float64
|
||||||
|
TotalEnd float64
|
||||||
|
|
||||||
|
HasPenumbral bool
|
||||||
|
HasPartial bool
|
||||||
|
HasTotal bool
|
||||||
|
}
|
||||||
|
|
||||||
|
type lunarShadowState struct {
|
||||||
|
jde float64
|
||||||
|
x float64
|
||||||
|
y float64
|
||||||
|
moonRadiusRad float64
|
||||||
|
umbraRadiusRad float64
|
||||||
|
penumbraRadiusRad float64
|
||||||
|
}
|
||||||
|
|
||||||
|
type lunarEclipseShadowModel int
|
||||||
|
|
||||||
|
const (
|
||||||
|
lunarEclipseShadowDanjon lunarEclipseShadowModel = iota
|
||||||
|
lunarEclipseShadowChauvenet
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
lunarEarthEquatorialRadiusKM = 6378.1366
|
||||||
|
lunarAstronomicalUnitKM = 1.49597870691e8
|
||||||
|
|
||||||
|
// 沿用月食常量:
|
||||||
|
// - 0.2725076 用于月亮视半径和半影几何
|
||||||
|
// - 959.63 / 8.794 分别为太阳视半径与太阳视差的常用角秒常量
|
||||||
|
lunarMoonRadiusRatio = 0.2725076
|
||||||
|
lunarMoonRadiusScale = lunarMoonRadiusRatio * lunarEarthEquatorialRadiusKM * 1.0000036
|
||||||
|
lunarSolarRadiusArcsec = 959.63
|
||||||
|
lunarSolarParallaxArcsec = 8.794
|
||||||
|
lunarLongitudeAberration = -3.4e-6
|
||||||
|
lunarFiniteDifferenceStep = 60.0 / 86400.0
|
||||||
|
|
||||||
|
// Chauvenet 体系:
|
||||||
|
// - 地球有效半径取 0.99834 * 赤道半径
|
||||||
|
// - 再统一乘 51/50 的大气放大因子
|
||||||
|
lunarChauvenetEarthScale = 0.99834
|
||||||
|
lunarChauvenetShadowGain = 51.0 / 50.0
|
||||||
|
|
||||||
|
// Danjon 体系:
|
||||||
|
// - 影半径只对月球水平视差项乘 1.01
|
||||||
|
// - 太阳视半径与太阳视差项不再统一乘 1.02
|
||||||
|
lunarDanjonParallaxScale = 1.01
|
||||||
|
)
|
||||||
|
|
||||||
|
var lunarArcsecPerRadian = 180.0 * 3600.0 / math.Pi
|
||||||
|
|
||||||
|
// LunarEclipse 计算给定近望时刻附近的一次月食,默认使用 Danjon 影半径模型。
|
||||||
|
//
|
||||||
|
// seedJDE 为力学时儒略日(TT),只需落在目标望月附近,允许相差数天。
|
||||||
|
// 返回值中的所有接触时刻也都是力学时儒略日。
|
||||||
|
func LunarEclipse(seedJDE float64) LunarEclipseResult {
|
||||||
|
return LunarEclipseDanjon(seedJDE)
|
||||||
|
}
|
||||||
|
|
||||||
|
// LunarEclipseDanjon 计算给定近望时刻附近的一次月食,使用 Danjon 影半径模型。
|
||||||
|
func LunarEclipseDanjon(seedJDE float64) LunarEclipseResult {
|
||||||
|
return lunarEclipse(seedJDE, lunarEclipseShadowDanjon)
|
||||||
|
}
|
||||||
|
|
||||||
|
// LunarEclipseChauvenet 计算给定近望时刻附近的一次月食,使用 Chauvenet 影半径模型。
|
||||||
|
func LunarEclipseChauvenet(seedJDE float64) LunarEclipseResult {
|
||||||
|
return lunarEclipse(seedJDE, lunarEclipseShadowChauvenet)
|
||||||
|
}
|
||||||
|
|
||||||
|
func lunarEclipse(seedJDE float64, shadowModel lunarEclipseShadowModel) LunarEclipseResult {
|
||||||
|
fullMoonJDE := CalcMoonSHByJDE(seedJDE, 1)
|
||||||
|
maximumJDE, state, dxdt, dydt, minimumDistance := refineLunarEclipseMaximum(fullMoonJDE, shadowModel)
|
||||||
|
|
||||||
|
result := LunarEclipseResult{
|
||||||
|
Type: LunarEclipseNone,
|
||||||
|
Maximum: maximumJDE,
|
||||||
|
MinimumDistance: minimumDistance,
|
||||||
|
PenumbralMagnitude: (state.moonRadiusRad + state.penumbraRadiusRad - minimumDistance) / (2 * state.moonRadiusRad),
|
||||||
|
}
|
||||||
|
rawUmbralMagnitude := (state.moonRadiusRad + state.umbraRadiusRad - minimumDistance) / (2 * state.moonRadiusRad)
|
||||||
|
|
||||||
|
if result.PenumbralMagnitude < 0 {
|
||||||
|
result.PenumbralMagnitude = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
if minimumDistance <= state.moonRadiusRad+state.penumbraRadiusRad {
|
||||||
|
result.Type = LunarEclipsePenumbral
|
||||||
|
result.HasPenumbral = true
|
||||||
|
result.Magnitude = rawUmbralMagnitude
|
||||||
|
result.PenumbralStart = refineLunarEclipseContact(
|
||||||
|
state, dxdt, dydt, state.moonRadiusRad+state.penumbraRadiusRad, false, shadowModel,
|
||||||
|
)
|
||||||
|
result.PenumbralEnd = refineLunarEclipseContact(
|
||||||
|
state, dxdt, dydt, state.moonRadiusRad+state.penumbraRadiusRad, true, shadowModel,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if minimumDistance <= state.moonRadiusRad+state.umbraRadiusRad {
|
||||||
|
result.Type = LunarEclipsePartial
|
||||||
|
result.HasPartial = true
|
||||||
|
result.Magnitude = rawUmbralMagnitude
|
||||||
|
result.PartialStart = refineLunarEclipseContact(
|
||||||
|
state, dxdt, dydt, state.moonRadiusRad+state.umbraRadiusRad, false, shadowModel,
|
||||||
|
)
|
||||||
|
result.PartialEnd = refineLunarEclipseContact(
|
||||||
|
state, dxdt, dydt, state.moonRadiusRad+state.umbraRadiusRad, true, shadowModel,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if minimumDistance <= state.umbraRadiusRad-state.moonRadiusRad {
|
||||||
|
result.Type = LunarEclipseTotal
|
||||||
|
result.HasTotal = true
|
||||||
|
result.TotalStart = refineLunarEclipseContact(
|
||||||
|
state, dxdt, dydt, state.umbraRadiusRad-state.moonRadiusRad, false, shadowModel,
|
||||||
|
)
|
||||||
|
result.TotalEnd = refineLunarEclipseContact(
|
||||||
|
state, dxdt, dydt, state.umbraRadiusRad-state.moonRadiusRad, true, shadowModel,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
// refineLunarEclipseMaximum 从近望初值出发,用有限差分速度做两轮几何极值修正。
|
||||||
|
//
|
||||||
|
// 这里直接在月心相对地影中心的二维平面上求 |r| 的极小值,
|
||||||
|
func refineLunarEclipseMaximum(
|
||||||
|
seedJDE float64,
|
||||||
|
shadowModel lunarEclipseShadowModel,
|
||||||
|
) (float64, lunarShadowState, float64, float64, float64) {
|
||||||
|
currentJDE := seedJDE
|
||||||
|
|
||||||
|
for i := 0; i < 2; i++ {
|
||||||
|
state := computeLunarShadowState(currentJDE, shadowModel)
|
||||||
|
nextState := computeLunarShadowState(currentJDE+lunarFiniteDifferenceStep, shadowModel)
|
||||||
|
dxdt := (nextState.x - state.x) / lunarFiniteDifferenceStep
|
||||||
|
dydt := (nextState.y - state.y) / lunarFiniteDifferenceStep
|
||||||
|
|
||||||
|
denominator := dxdt*dxdt + dydt*dydt
|
||||||
|
if denominator == 0 {
|
||||||
|
finalState := computeLunarShadowState(currentJDE, shadowModel)
|
||||||
|
return currentJDE, finalState, 0, 0, math.Hypot(finalState.x, finalState.y)
|
||||||
|
}
|
||||||
|
|
||||||
|
correction := -(state.x*dxdt + state.y*dydt) / denominator
|
||||||
|
currentJDE += correction
|
||||||
|
}
|
||||||
|
|
||||||
|
linearState := computeLunarShadowState(currentJDE, shadowModel)
|
||||||
|
nextLinearState := computeLunarShadowState(currentJDE+lunarFiniteDifferenceStep, shadowModel)
|
||||||
|
dxdt := (nextLinearState.x - linearState.x) / lunarFiniteDifferenceStep
|
||||||
|
dydt := (nextLinearState.y - linearState.y) / lunarFiniteDifferenceStep
|
||||||
|
|
||||||
|
denominator := dxdt*dxdt + dydt*dydt
|
||||||
|
if denominator == 0 {
|
||||||
|
return currentJDE, linearState, dxdt, dydt, math.Hypot(linearState.x, linearState.y)
|
||||||
|
}
|
||||||
|
|
||||||
|
correction := -(linearState.x*dxdt + linearState.y*dydt) / denominator
|
||||||
|
maximumJDE := currentJDE + correction
|
||||||
|
|
||||||
|
finalState := computeLunarShadowState(maximumJDE, shadowModel)
|
||||||
|
nextState := computeLunarShadowState(maximumJDE+lunarFiniteDifferenceStep, shadowModel)
|
||||||
|
dxdt = (nextState.x - finalState.x) / lunarFiniteDifferenceStep
|
||||||
|
dydt = (nextState.y - finalState.y) / lunarFiniteDifferenceStep
|
||||||
|
|
||||||
|
return maximumJDE, finalState, dxdt, dydt, math.Hypot(finalState.x, finalState.y)
|
||||||
|
}
|
||||||
|
|
||||||
|
// refineLunarEclipseContact 先用固定速度近似求一次接触时刻,
|
||||||
|
// 再在接触点重算半径并修正一次。
|
||||||
|
func refineLunarEclipseContact(
|
||||||
|
maximumState lunarShadowState,
|
||||||
|
dxdt, dydt, boundaryRadius float64,
|
||||||
|
afterMaximum bool,
|
||||||
|
shadowModel lunarEclipseShadowModel,
|
||||||
|
) float64 {
|
||||||
|
firstGuess, ok := solveLineCircleContact(maximumState, dxdt, dydt, boundaryRadius, afterMaximum)
|
||||||
|
if !ok {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
contactState := computeLunarShadowState(firstGuess, shadowModel)
|
||||||
|
refinedRadius := boundaryRadius
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case math.Abs(boundaryRadius-(maximumState.moonRadiusRad+maximumState.umbraRadiusRad)) < 1e-18:
|
||||||
|
refinedRadius = contactState.moonRadiusRad + contactState.umbraRadiusRad
|
||||||
|
case math.Abs(boundaryRadius-(maximumState.moonRadiusRad+maximumState.penumbraRadiusRad)) < 1e-18:
|
||||||
|
refinedRadius = contactState.moonRadiusRad + contactState.penumbraRadiusRad
|
||||||
|
case math.Abs(boundaryRadius-(maximumState.umbraRadiusRad-maximumState.moonRadiusRad)) < 1e-18:
|
||||||
|
refinedRadius = contactState.umbraRadiusRad - contactState.moonRadiusRad
|
||||||
|
}
|
||||||
|
|
||||||
|
refinedGuess, ok := solveLineCircleContact(contactState, dxdt, dydt, refinedRadius, afterMaximum)
|
||||||
|
if !ok {
|
||||||
|
return firstGuess
|
||||||
|
}
|
||||||
|
return refinedGuess
|
||||||
|
}
|
||||||
|
|
||||||
|
// solveLineCircleContact 求月心轨迹与某个影界圆的交点时刻。
|
||||||
|
func solveLineCircleContact(
|
||||||
|
state lunarShadowState,
|
||||||
|
dxdt, dydt, radius float64,
|
||||||
|
afterMaximum bool,
|
||||||
|
) (float64, bool) {
|
||||||
|
a := dxdt*dxdt + dydt*dydt
|
||||||
|
if a == 0 {
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
|
||||||
|
b := 2 * (state.x*dxdt + state.y*dydt)
|
||||||
|
c := state.x*state.x + state.y*state.y - radius*radius
|
||||||
|
discriminant := b*b - 4*a*c
|
||||||
|
if discriminant < 0 {
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
|
||||||
|
root := math.Sqrt(discriminant)
|
||||||
|
delta := (-b - root) / (2 * a)
|
||||||
|
if afterMaximum {
|
||||||
|
delta = (-b + root) / (2 * a)
|
||||||
|
}
|
||||||
|
|
||||||
|
return state.jde + delta, true
|
||||||
|
}
|
||||||
|
|
||||||
|
// computeLunarShadowState 计算某一力学时刻下,月心相对地影中心的二维几何状态。
|
||||||
|
//
|
||||||
|
// 所有内部角量统一使用弧度。影半径模型允许在 Danjon 与 Chauvenet 之间切换,
|
||||||
|
// 其余月心轨迹与几何求交框架保持一致。
|
||||||
|
func computeLunarShadowState(jde float64, shadowModel lunarEclipseShadowModel) lunarShadowState {
|
||||||
|
julianCentury := (jde - 2451545.0) / 36525.0
|
||||||
|
|
||||||
|
sunLongitude := HSunTrueLo(jde)*rad + sunLongitudeAberrationRad(julianCentury)
|
||||||
|
sunLatitude := HSunTrueBo(jde) * rad
|
||||||
|
moonLongitude := HMoonTrueLo(jde)*rad + lunarLongitudeAberration
|
||||||
|
moonLatitude := HMoonTrueBo(jde)*rad + moonLatitudeAberrationRad(julianCentury)
|
||||||
|
|
||||||
|
moonDistanceKM := HMoonAway(jde)
|
||||||
|
sunDistanceAU := EarthAway(jde)
|
||||||
|
|
||||||
|
moonRadiusArcsec := lunarMoonRadiusScale * lunarArcsecPerRadian / moonDistanceKM
|
||||||
|
earthParallaxArcsec := lunarEarthEquatorialRadiusKM / moonDistanceKM * lunarArcsecPerRadian
|
||||||
|
solarRadiusArcsec := lunarSolarRadiusArcsec / sunDistanceAU
|
||||||
|
solarParallaxArcsec := lunarSolarParallaxArcsec / sunDistanceAU
|
||||||
|
umbraRadiusArcsec, penumbraRadiusArcsec := lunarEclipseShadowRadiiArcsec(
|
||||||
|
earthParallaxArcsec,
|
||||||
|
solarRadiusArcsec,
|
||||||
|
solarParallaxArcsec,
|
||||||
|
shadowModel,
|
||||||
|
)
|
||||||
|
|
||||||
|
return lunarShadowState{
|
||||||
|
jde: jde,
|
||||||
|
x: normalizeRadians(moonLongitude+math.Pi-sunLongitude) * math.Cos((moonLatitude-sunLatitude)/2),
|
||||||
|
y: moonLatitude + sunLatitude,
|
||||||
|
moonRadiusRad: moonRadiusArcsec / lunarArcsecPerRadian,
|
||||||
|
umbraRadiusRad: umbraRadiusArcsec / lunarArcsecPerRadian,
|
||||||
|
penumbraRadiusRad: penumbraRadiusArcsec / lunarArcsecPerRadian,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func lunarEclipseShadowRadiiArcsec(
|
||||||
|
earthParallaxArcsec, solarRadiusArcsec, solarParallaxArcsec float64,
|
||||||
|
shadowModel lunarEclipseShadowModel,
|
||||||
|
) (float64, float64) {
|
||||||
|
switch shadowModel {
|
||||||
|
case lunarEclipseShadowDanjon:
|
||||||
|
earthTerm := lunarDanjonParallaxScale * earthParallaxArcsec
|
||||||
|
return earthTerm - solarRadiusArcsec + solarParallaxArcsec,
|
||||||
|
earthTerm + solarRadiusArcsec + solarParallaxArcsec
|
||||||
|
default:
|
||||||
|
earthTerm := lunarChauvenetEarthScale * earthParallaxArcsec
|
||||||
|
return (earthTerm - solarRadiusArcsec + solarParallaxArcsec) * lunarChauvenetShadowGain,
|
||||||
|
(earthTerm + solarRadiusArcsec + solarParallaxArcsec) * lunarChauvenetShadowGain
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func normalizeRadians(angle float64) float64 {
|
||||||
|
angle = math.Mod(angle, 2*math.Pi)
|
||||||
|
if angle > math.Pi {
|
||||||
|
angle -= 2 * math.Pi
|
||||||
|
}
|
||||||
|
if angle <= -math.Pi {
|
||||||
|
angle += 2 * math.Pi
|
||||||
|
}
|
||||||
|
return angle
|
||||||
|
}
|
||||||
|
|
||||||
|
func sunLongitudeAberrationRad(julianCentury float64) float64 {
|
||||||
|
meanAnomaly := -0.043126 + 628.301955*julianCentury - 0.000002732*julianCentury*julianCentury
|
||||||
|
eccentricity := 0.016708634 - 0.000042037*julianCentury - 0.0000001267*julianCentury*julianCentury
|
||||||
|
return -20.49552 * (1 + eccentricity*math.Cos(meanAnomaly)) / lunarArcsecPerRadian
|
||||||
|
}
|
||||||
|
|
||||||
|
func moonLatitudeAberrationRad(julianCentury float64) float64 {
|
||||||
|
argument := 0.057 + 8433.4662*julianCentury + 0.000064*julianCentury*julianCentury
|
||||||
|
return 0.063 * math.Sin(argument) / lunarArcsecPerRadian
|
||||||
|
}
|
||||||
@@ -0,0 +1,253 @@
|
|||||||
|
package basic
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math"
|
||||||
|
"sort"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
lunarEclipseDiagramDefaultStepDays = 5.0 / 1440.0
|
||||||
|
lunarEclipseDiagramMinStepDays = 1.0 / 86400.0
|
||||||
|
lunarEclipseDiagramMaxSamples = 2000
|
||||||
|
lunarEclipseDiagramDuplicateDays = 1e-10
|
||||||
|
)
|
||||||
|
|
||||||
|
// LunarEclipseDiagramOptions 控制月食穿影图采样。
|
||||||
|
// LunarEclipseDiagramOptions controls lunar eclipse shadow-path diagram sampling.
|
||||||
|
type LunarEclipseDiagramOptions struct {
|
||||||
|
// StepDays 是路径采样步长,单位为日;<=0 时使用 5 分钟。
|
||||||
|
// StepDays is the path sampling step in days; values <= 0 use five minutes.
|
||||||
|
StepDays float64
|
||||||
|
}
|
||||||
|
|
||||||
|
// LunarEclipseDiagramPoint 表示月食穿影图上的一个月心位置。
|
||||||
|
// LunarEclipseDiagramPoint is one Moon-center point in a lunar eclipse diagram.
|
||||||
|
type LunarEclipseDiagramPoint struct {
|
||||||
|
// JDE 是力学时儒略日, TT Julian ephemeris day.
|
||||||
|
JDE float64
|
||||||
|
// X / Y 是以月球半径为单位的月心相对地影中心坐标。
|
||||||
|
// X/Y are Moon-center coordinates relative to the shadow center, in Moon-radius units.
|
||||||
|
X float64
|
||||||
|
Y float64
|
||||||
|
// Label 是关键接触标签,如 P1/U1/U2/Greatest/U3/U4/P4。
|
||||||
|
// Label is a key contact label such as P1/U1/U2/Greatest/U3/U4/P4.
|
||||||
|
Label string
|
||||||
|
// Labels 是该点对应的全部关键接触标签;若事件重合,这里会有多个值。
|
||||||
|
// Labels contains all contact labels attached to this point.
|
||||||
|
Labels []string
|
||||||
|
}
|
||||||
|
|
||||||
|
// LunarEclipseDiagramResult 表示月食穿影图几何结果。
|
||||||
|
// LunarEclipseDiagramResult is the geometry result for a lunar eclipse diagram.
|
||||||
|
type LunarEclipseDiagramResult struct {
|
||||||
|
// Eclipse 是对应的月食结果。
|
||||||
|
// Eclipse is the eclipse result used for the diagram.
|
||||||
|
Eclipse LunarEclipseResult
|
||||||
|
// MoonRadius 是月球半径,单位为图上月球半径;固定为 1。
|
||||||
|
// MoonRadius is the Moon radius in diagram Moon-radius units; always 1.
|
||||||
|
MoonRadius float64
|
||||||
|
// UmbraRadius 是本影半径,单位为图上月球半径。
|
||||||
|
// UmbraRadius is the umbral shadow radius in Moon-radius units.
|
||||||
|
UmbraRadius float64
|
||||||
|
// PenumbraRadius 是半影半径,单位为图上月球半径。
|
||||||
|
// PenumbraRadius is the penumbral shadow radius in Moon-radius units.
|
||||||
|
PenumbraRadius float64
|
||||||
|
// Points 是月心路径点,包含接触点与采样点。
|
||||||
|
// Points are Moon-center path points, including contact and sampled points.
|
||||||
|
Points []LunarEclipseDiagramPoint
|
||||||
|
// StepDays 是实际采用的路径采样步长,单位为日。
|
||||||
|
// StepDays is the effective path sampling step in days.
|
||||||
|
StepDays float64
|
||||||
|
}
|
||||||
|
|
||||||
|
type lunarEclipseDiagramTime struct {
|
||||||
|
jde float64
|
||||||
|
labels []string
|
||||||
|
}
|
||||||
|
|
||||||
|
// LunarEclipseDiagram 计算月食穿影图几何数据,默认使用 Danjon 影半径模型。
|
||||||
|
// LunarEclipseDiagram computes lunar eclipse diagram geometry, using the Danjon shadow model by default.
|
||||||
|
func LunarEclipseDiagram(seedJDE float64, options LunarEclipseDiagramOptions) LunarEclipseDiagramResult {
|
||||||
|
return LunarEclipseDiagramDanjon(seedJDE, options)
|
||||||
|
}
|
||||||
|
|
||||||
|
// LunarEclipseDiagramDanjon 计算月食穿影图几何数据,使用 Danjon 影半径模型。
|
||||||
|
// LunarEclipseDiagramDanjon computes lunar eclipse diagram geometry with the Danjon shadow model.
|
||||||
|
func LunarEclipseDiagramDanjon(seedJDE float64, options LunarEclipseDiagramOptions) LunarEclipseDiagramResult {
|
||||||
|
return lunarEclipseDiagram(seedJDE, lunarEclipseShadowDanjon, options)
|
||||||
|
}
|
||||||
|
|
||||||
|
// LunarEclipseDiagramChauvenet 计算月食穿影图几何数据,使用 Chauvenet 影半径模型。
|
||||||
|
// LunarEclipseDiagramChauvenet computes lunar eclipse diagram geometry with the Chauvenet shadow model.
|
||||||
|
func LunarEclipseDiagramChauvenet(seedJDE float64, options LunarEclipseDiagramOptions) LunarEclipseDiagramResult {
|
||||||
|
return lunarEclipseDiagram(seedJDE, lunarEclipseShadowChauvenet, options)
|
||||||
|
}
|
||||||
|
|
||||||
|
func lunarEclipseDiagram(
|
||||||
|
seedJDE float64,
|
||||||
|
shadowModel lunarEclipseShadowModel,
|
||||||
|
options LunarEclipseDiagramOptions,
|
||||||
|
) LunarEclipseDiagramResult {
|
||||||
|
options = normalizeLunarEclipseDiagramOptions(options)
|
||||||
|
eclipse := lunarEclipse(seedJDE, shadowModel)
|
||||||
|
result := LunarEclipseDiagramResult{
|
||||||
|
Eclipse: eclipse,
|
||||||
|
StepDays: options.StepDays,
|
||||||
|
}
|
||||||
|
if !eclipse.HasPenumbral {
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
maximumState := computeLunarShadowState(eclipse.Maximum, shadowModel)
|
||||||
|
if maximumState.moonRadiusRad <= 0 {
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
result.MoonRadius = 1
|
||||||
|
result.UmbraRadius = maximumState.umbraRadiusRad / maximumState.moonRadiusRad
|
||||||
|
result.PenumbraRadius = maximumState.penumbraRadiusRad / maximumState.moonRadiusRad
|
||||||
|
|
||||||
|
times, stepDays := lunarEclipseDiagramTimes(eclipse, options.StepDays)
|
||||||
|
result.StepDays = stepDays
|
||||||
|
result.Points = make([]LunarEclipseDiagramPoint, 0, len(times))
|
||||||
|
for _, item := range times {
|
||||||
|
state := computeLunarShadowState(item.jde, shadowModel)
|
||||||
|
result.Points = append(result.Points, LunarEclipseDiagramPoint{
|
||||||
|
JDE: item.jde,
|
||||||
|
X: state.x / maximumState.moonRadiusRad,
|
||||||
|
Y: state.y / maximumState.moonRadiusRad,
|
||||||
|
Label: lunarEclipseDiagramPrimaryLabel(item.labels),
|
||||||
|
Labels: append([]string(nil), item.labels...),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func normalizeLunarEclipseDiagramOptions(options LunarEclipseDiagramOptions) LunarEclipseDiagramOptions {
|
||||||
|
if options.StepDays <= 0 || math.IsNaN(options.StepDays) || math.IsInf(options.StepDays, 0) {
|
||||||
|
options.StepDays = lunarEclipseDiagramDefaultStepDays
|
||||||
|
}
|
||||||
|
if options.StepDays < lunarEclipseDiagramMinStepDays {
|
||||||
|
options.StepDays = lunarEclipseDiagramMinStepDays
|
||||||
|
}
|
||||||
|
return options
|
||||||
|
}
|
||||||
|
|
||||||
|
func lunarEclipseDiagramTimes(eclipse LunarEclipseResult, stepDays float64) ([]lunarEclipseDiagramTime, float64) {
|
||||||
|
startJDE := eclipse.PenumbralStart
|
||||||
|
endJDE := eclipse.PenumbralEnd
|
||||||
|
if startJDE == 0 || endJDE == 0 || endJDE <= startJDE {
|
||||||
|
return nil, stepDays
|
||||||
|
}
|
||||||
|
|
||||||
|
if sampleCount := int(math.Ceil((endJDE-startJDE)/stepDays)) + 1; sampleCount > lunarEclipseDiagramMaxSamples {
|
||||||
|
stepDays = (endJDE - startJDE) / float64(lunarEclipseDiagramMaxSamples-1)
|
||||||
|
}
|
||||||
|
|
||||||
|
times := []lunarEclipseDiagramTime{
|
||||||
|
{jde: startJDE, labels: []string{"P1"}},
|
||||||
|
{jde: eclipse.Maximum, labels: []string{"Greatest"}},
|
||||||
|
{jde: endJDE, labels: []string{"P4"}},
|
||||||
|
}
|
||||||
|
if eclipse.HasPartial {
|
||||||
|
times = append(times,
|
||||||
|
lunarEclipseDiagramTime{jde: eclipse.PartialStart, labels: []string{"U1"}},
|
||||||
|
lunarEclipseDiagramTime{jde: eclipse.PartialEnd, labels: []string{"U4"}},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if eclipse.HasTotal {
|
||||||
|
times = append(times,
|
||||||
|
lunarEclipseDiagramTime{jde: eclipse.TotalStart, labels: []string{"U2"}},
|
||||||
|
lunarEclipseDiagramTime{jde: eclipse.TotalEnd, labels: []string{"U3"}},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
for jde := startJDE + stepDays; jde < endJDE; jde += stepDays {
|
||||||
|
times = append(times, lunarEclipseDiagramTime{jde: jde})
|
||||||
|
}
|
||||||
|
|
||||||
|
sort.SliceStable(times, func(i, j int) bool {
|
||||||
|
if times[i].jde == times[j].jde {
|
||||||
|
return lunarEclipseDiagramLabelPriority(times[i].labels) < lunarEclipseDiagramLabelPriority(times[j].labels)
|
||||||
|
}
|
||||||
|
return times[i].jde < times[j].jde
|
||||||
|
})
|
||||||
|
return uniqueLunarEclipseDiagramTimes(times), stepDays
|
||||||
|
}
|
||||||
|
|
||||||
|
func uniqueLunarEclipseDiagramTimes(times []lunarEclipseDiagramTime) []lunarEclipseDiagramTime {
|
||||||
|
if len(times) < 2 {
|
||||||
|
return times
|
||||||
|
}
|
||||||
|
|
||||||
|
unique := times[:0]
|
||||||
|
for _, item := range times {
|
||||||
|
if item.jde == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if len(unique) == 0 || math.Abs(item.jde-unique[len(unique)-1].jde) > lunarEclipseDiagramDuplicateDays {
|
||||||
|
item.labels = append([]string(nil), item.labels...)
|
||||||
|
unique = append(unique, item)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
unique[len(unique)-1].labels = mergeLunarEclipseDiagramLabels(unique[len(unique)-1].labels, item.labels)
|
||||||
|
}
|
||||||
|
return unique
|
||||||
|
}
|
||||||
|
|
||||||
|
func mergeLunarEclipseDiagramLabels(existing, incoming []string) []string {
|
||||||
|
if len(incoming) == 0 {
|
||||||
|
return existing
|
||||||
|
}
|
||||||
|
if len(existing) == 0 {
|
||||||
|
return append([]string(nil), incoming...)
|
||||||
|
}
|
||||||
|
for _, label := range incoming {
|
||||||
|
found := false
|
||||||
|
for _, current := range existing {
|
||||||
|
if current == label {
|
||||||
|
found = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !found {
|
||||||
|
existing = append(existing, label)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return existing
|
||||||
|
}
|
||||||
|
|
||||||
|
func lunarEclipseDiagramPrimaryLabel(labels []string) string {
|
||||||
|
for _, label := range labels {
|
||||||
|
if label == "Greatest" {
|
||||||
|
return label
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(labels) == 0 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return labels[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
func lunarEclipseDiagramLabelPriority(labels []string) int {
|
||||||
|
if len(labels) == 0 {
|
||||||
|
return 99
|
||||||
|
}
|
||||||
|
switch labels[0] {
|
||||||
|
case "P1":
|
||||||
|
return 0
|
||||||
|
case "U1":
|
||||||
|
return 1
|
||||||
|
case "U2":
|
||||||
|
return 2
|
||||||
|
case "Greatest":
|
||||||
|
return 3
|
||||||
|
case "U3":
|
||||||
|
return 4
|
||||||
|
case "U4":
|
||||||
|
return 5
|
||||||
|
case "P4":
|
||||||
|
return 6
|
||||||
|
default:
|
||||||
|
return 99
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,308 @@
|
|||||||
|
package basic
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
type lunarEclipseBaseline struct {
|
||||||
|
name string
|
||||||
|
jde float64
|
||||||
|
|
||||||
|
expectedType LunarEclipseType
|
||||||
|
expectedMax float64
|
||||||
|
expectedMag float64
|
||||||
|
|
||||||
|
expectedPenumbralStart float64
|
||||||
|
expectedPenumbralEnd float64
|
||||||
|
expectedPartialStart float64
|
||||||
|
expectedPartialEnd float64
|
||||||
|
expectedTotalStart float64
|
||||||
|
expectedTotalEnd float64
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLunarEclipseChauvenetAgainstLegacyBaseline(t *testing.T) {
|
||||||
|
// 这些基准值来自历史本地月食基线,
|
||||||
|
// 其阴影口径对应当前保留的 Chauvenet 模型。
|
||||||
|
testCases := []lunarEclipseBaseline{
|
||||||
|
{
|
||||||
|
name: "2022-11-08 total",
|
||||||
|
jde: JDECalc(2022, 11, 8),
|
||||||
|
expectedType: LunarEclipseTotal,
|
||||||
|
expectedMax: 2459891.9585873615,
|
||||||
|
expectedMag: 1.3635170051692678,
|
||||||
|
expectedPenumbralStart: 2459891.8346063416,
|
||||||
|
expectedPenumbralEnd: 2459892.0826413140,
|
||||||
|
expectedPartialStart: 2459891.8820205650,
|
||||||
|
expectedPartialEnd: 2459892.0351211606,
|
||||||
|
expectedTotalStart: 2459891.9288277230,
|
||||||
|
expectedTotalEnd: 2459891.9883249460,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "2023-05-05 penumbral",
|
||||||
|
jde: JDECalc(2023, 5, 5),
|
||||||
|
expectedType: LunarEclipsePenumbral,
|
||||||
|
expectedPenumbralStart: 2460070.1342392800,
|
||||||
|
expectedPenumbralEnd: 2460070.3159191823,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "2023-10-28 partial",
|
||||||
|
jde: JDECalc(2023, 10, 28),
|
||||||
|
expectedType: LunarEclipsePartial,
|
||||||
|
expectedMax: 2460246.3439460830,
|
||||||
|
expectedMag: 0.12723850274626405,
|
||||||
|
expectedPenumbralStart: 2460246.2507697106,
|
||||||
|
expectedPenumbralEnd: 2460246.4370874465,
|
||||||
|
expectedPartialStart: 2460246.3164327650,
|
||||||
|
expectedPartialEnd: 2460246.3713359070,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "2024-03-25 penumbral",
|
||||||
|
jde: JDECalc(2024, 3, 25),
|
||||||
|
expectedType: LunarEclipsePenumbral,
|
||||||
|
expectedPenumbralStart: 2460394.7028870000,
|
||||||
|
expectedPenumbralEnd: 2460394.8999071894,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "2024-09-18 partial",
|
||||||
|
jde: JDECalc(2024, 9, 18),
|
||||||
|
expectedType: LunarEclipsePartial,
|
||||||
|
expectedMax: 2460571.6148748010,
|
||||||
|
expectedMag: 0.09042791952817894,
|
||||||
|
expectedPenumbralStart: 2460571.5281155687,
|
||||||
|
expectedPenumbralEnd: 2460571.7016473800,
|
||||||
|
expectedPartialStart: 2460571.5923644140,
|
||||||
|
expectedPartialEnd: 2460571.6374154520,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "2025-03-14 total",
|
||||||
|
jde: JDECalc(2025, 3, 14),
|
||||||
|
expectedType: LunarEclipseTotal,
|
||||||
|
expectedMax: 2460748.7916214615,
|
||||||
|
expectedMag: 1.1828107517800281,
|
||||||
|
expectedPenumbralStart: 2460748.6645233813,
|
||||||
|
expectedPenumbralEnd: 2460748.9187600957,
|
||||||
|
expectedPartialStart: 2460748.7156107454,
|
||||||
|
expectedPartialEnd: 2460748.8676076555,
|
||||||
|
expectedTotalStart: 2460748.7685903380,
|
||||||
|
expectedTotalEnd: 2460748.8146345600,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "2025-09-07 total",
|
||||||
|
jde: JDECalc(2025, 9, 7),
|
||||||
|
expectedType: LunarEclipseTotal,
|
||||||
|
expectedMax: 2460926.2590034613,
|
||||||
|
expectedMag: 1.3672329695760280,
|
||||||
|
expectedPenumbralStart: 2460926.1445036167,
|
||||||
|
expectedPenumbralEnd: 2460926.3734498024,
|
||||||
|
expectedPartialStart: 2460926.1860739910,
|
||||||
|
expectedPartialEnd: 2460926.3319619163,
|
||||||
|
expectedTotalStart: 2460926.2302397094,
|
||||||
|
expectedTotalEnd: 2460926.2877871464,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "2026-03-03 total",
|
||||||
|
jde: JDECalc(2026, 3, 3),
|
||||||
|
expectedType: LunarEclipseTotal,
|
||||||
|
expectedMax: 2461102.9825476190,
|
||||||
|
expectedMag: 1.1556387222746651,
|
||||||
|
expectedPenumbralStart: 2461102.8638708987,
|
||||||
|
expectedPenumbralEnd: 2461103.1012840020,
|
||||||
|
expectedPartialStart: 2461102.9103626400,
|
||||||
|
expectedPartialEnd: 2461103.0546894810,
|
||||||
|
expectedTotalStart: 2461102.9619182530,
|
||||||
|
expectedTotalEnd: 2461103.0031447060,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
const timeTolerance = 1e-6
|
||||||
|
const magnitudeTolerance = 2e-5
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
result := LunarEclipseChauvenet(tc.jde)
|
||||||
|
|
||||||
|
if result.Type != tc.expectedType {
|
||||||
|
t.Fatalf("Type mismatch: got %s want %s", result.Type, tc.expectedType)
|
||||||
|
}
|
||||||
|
|
||||||
|
if tc.expectedMax != 0 && math.Abs(result.Maximum-tc.expectedMax) > timeTolerance {
|
||||||
|
t.Fatalf("Maximum mismatch: got %.12f want %.12f", result.Maximum, tc.expectedMax)
|
||||||
|
}
|
||||||
|
if tc.expectedMag != 0 && math.Abs(result.Magnitude-tc.expectedMag) > magnitudeTolerance {
|
||||||
|
t.Fatalf("Magnitude mismatch: got %.12f want %.12f", result.Magnitude, tc.expectedMag)
|
||||||
|
}
|
||||||
|
|
||||||
|
assertCloseJD(t, "PenumbralStart", result.PenumbralStart, tc.expectedPenumbralStart, timeTolerance)
|
||||||
|
assertCloseJD(t, "PenumbralEnd", result.PenumbralEnd, tc.expectedPenumbralEnd, timeTolerance)
|
||||||
|
assertCloseJD(t, "PartialStart", result.PartialStart, tc.expectedPartialStart, timeTolerance)
|
||||||
|
assertCloseJD(t, "PartialEnd", result.PartialEnd, tc.expectedPartialEnd, timeTolerance)
|
||||||
|
assertCloseJD(t, "TotalStart", result.TotalStart, tc.expectedTotalStart, timeTolerance)
|
||||||
|
assertCloseJD(t, "TotalEnd", result.TotalEnd, tc.expectedTotalEnd, timeTolerance)
|
||||||
|
|
||||||
|
if result.HasTotal && !(result.TotalStart < result.Maximum && result.Maximum < result.TotalEnd) {
|
||||||
|
t.Fatalf("total contact order invalid: start=%.12f max=%.12f end=%.12f", result.TotalStart, result.Maximum, result.TotalEnd)
|
||||||
|
}
|
||||||
|
if result.HasPartial && !(result.PartialStart < result.Maximum && result.Maximum < result.PartialEnd) {
|
||||||
|
t.Fatalf("partial contact order invalid: start=%.12f max=%.12f end=%.12f", result.PartialStart, result.Maximum, result.PartialEnd)
|
||||||
|
}
|
||||||
|
if result.HasPenumbral && !(result.PenumbralStart < result.Maximum && result.Maximum < result.PenumbralEnd) {
|
||||||
|
t.Fatalf("penumbral contact order invalid: start=%.12f max=%.12f end=%.12f", result.PenumbralStart, result.Maximum, result.PenumbralEnd)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLunarEclipseDefaultUsesDanjon(t *testing.T) {
|
||||||
|
jde := JDECalc(2025, 3, 14)
|
||||||
|
defaultResult := LunarEclipse(jde)
|
||||||
|
danjonResult := LunarEclipseDanjon(jde)
|
||||||
|
chauvenetResult := LunarEclipseChauvenet(jde)
|
||||||
|
|
||||||
|
assertCloseJD(t, "Maximum", defaultResult.Maximum, danjonResult.Maximum, 1e-12)
|
||||||
|
assertCloseJD(t, "PenumbralStart", defaultResult.PenumbralStart, danjonResult.PenumbralStart, 1e-12)
|
||||||
|
assertCloseJD(t, "PenumbralEnd", defaultResult.PenumbralEnd, danjonResult.PenumbralEnd, 1e-12)
|
||||||
|
if math.Abs(defaultResult.PenumbralMagnitude-danjonResult.PenumbralMagnitude) > 1e-12 {
|
||||||
|
t.Fatalf("default penumbral magnitude mismatch: got %.12f want %.12f", defaultResult.PenumbralMagnitude, danjonResult.PenumbralMagnitude)
|
||||||
|
}
|
||||||
|
if math.Abs(defaultResult.PenumbralMagnitude-chauvenetResult.PenumbralMagnitude) < 1e-4 {
|
||||||
|
t.Fatalf("default model should not collapse to Chauvenet: default=%.12f chauvenet=%.12f", defaultResult.PenumbralMagnitude, chauvenetResult.PenumbralMagnitude)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPenumbralLunarEclipseKeepsNegativeUmbralMagnitude(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
jde float64
|
||||||
|
calc func(float64) LunarEclipseResult
|
||||||
|
}{
|
||||||
|
{name: "default 2024-03-25", jde: JDECalc(2024, 3, 25), calc: LunarEclipse},
|
||||||
|
{name: "danjon 2024-03-25", jde: JDECalc(2024, 3, 25), calc: LunarEclipseDanjon},
|
||||||
|
{name: "chauvenet 2023-05-05", jde: JDECalc(2023, 5, 5), calc: LunarEclipseChauvenet},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
result := tc.calc(tc.jde)
|
||||||
|
if result.Type != LunarEclipsePenumbral {
|
||||||
|
t.Fatalf("type mismatch: got %s want %s", result.Type, LunarEclipsePenumbral)
|
||||||
|
}
|
||||||
|
if !result.HasPenumbral || result.HasPartial || result.HasTotal {
|
||||||
|
t.Fatalf("unexpected eclipse flags: %+v", result)
|
||||||
|
}
|
||||||
|
if !(result.Magnitude < 0) {
|
||||||
|
t.Fatalf("expected negative umbral magnitude for penumbral eclipse, got %.12f", result.Magnitude)
|
||||||
|
}
|
||||||
|
if !(result.PenumbralMagnitude > 0) {
|
||||||
|
t.Fatalf("expected positive penumbral magnitude, got %.12f", result.PenumbralMagnitude)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLunarEclipseDanjonMagnitudesCloserToNASA(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
jde float64
|
||||||
|
expectedType LunarEclipseType
|
||||||
|
nasaPenumbralMagnitude float64
|
||||||
|
nasaUmbralMagnitude float64
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "2023-10-28 partial",
|
||||||
|
jde: JDECalc(2023, 10, 28),
|
||||||
|
expectedType: LunarEclipsePartial,
|
||||||
|
nasaPenumbralMagnitude: 1.1181,
|
||||||
|
nasaUmbralMagnitude: 0.1220,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "2025-03-14 total",
|
||||||
|
jde: JDECalc(2025, 3, 14),
|
||||||
|
expectedType: LunarEclipseTotal,
|
||||||
|
nasaPenumbralMagnitude: 2.2595,
|
||||||
|
nasaUmbralMagnitude: 1.1784,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "2026-03-03 total",
|
||||||
|
jde: JDECalc(2026, 3, 3),
|
||||||
|
expectedType: LunarEclipseTotal,
|
||||||
|
nasaPenumbralMagnitude: 2.1838,
|
||||||
|
nasaUmbralMagnitude: 1.1507,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "2026-08-28 partial",
|
||||||
|
jde: JDECalc(2026, 8, 28),
|
||||||
|
expectedType: LunarEclipsePartial,
|
||||||
|
nasaPenumbralMagnitude: 1.9645,
|
||||||
|
nasaUmbralMagnitude: 0.9299,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
danjonResult := LunarEclipseDanjon(tc.jde)
|
||||||
|
chauvenetResult := LunarEclipseChauvenet(tc.jde)
|
||||||
|
|
||||||
|
if danjonResult.Type != tc.expectedType {
|
||||||
|
t.Fatalf("Danjon type mismatch: got %s want %s", danjonResult.Type, tc.expectedType)
|
||||||
|
}
|
||||||
|
if chauvenetResult.Type != tc.expectedType {
|
||||||
|
t.Fatalf("Chauvenet type mismatch: got %s want %s", chauvenetResult.Type, tc.expectedType)
|
||||||
|
}
|
||||||
|
|
||||||
|
danjonPenumbralError := math.Abs(danjonResult.PenumbralMagnitude - tc.nasaPenumbralMagnitude)
|
||||||
|
chauvenetPenumbralError := math.Abs(chauvenetResult.PenumbralMagnitude - tc.nasaPenumbralMagnitude)
|
||||||
|
if !(danjonPenumbralError < chauvenetPenumbralError) {
|
||||||
|
t.Fatalf("Danjon penumbral magnitude should be closer to NASA: danjon=%.6f chauvenet=%.6f nasa=%.6f", danjonResult.PenumbralMagnitude, chauvenetResult.PenumbralMagnitude, tc.nasaPenumbralMagnitude)
|
||||||
|
}
|
||||||
|
|
||||||
|
danjonUmbralError := math.Abs(danjonResult.Magnitude - tc.nasaUmbralMagnitude)
|
||||||
|
chauvenetUmbralError := math.Abs(chauvenetResult.Magnitude - tc.nasaUmbralMagnitude)
|
||||||
|
if !(danjonUmbralError < chauvenetUmbralError) {
|
||||||
|
t.Fatalf("Danjon umbral magnitude should be closer to NASA: danjon=%.6f chauvenet=%.6f nasa=%.6f", danjonResult.Magnitude, chauvenetResult.Magnitude, tc.nasaUmbralMagnitude)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLunarEclipseNoEvent(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
calc func(float64) LunarEclipseResult
|
||||||
|
}{
|
||||||
|
{name: "default", calc: LunarEclipse},
|
||||||
|
{name: "danjon", calc: LunarEclipseDanjon},
|
||||||
|
{name: "chauvenet", calc: LunarEclipseChauvenet},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
result := tc.calc(JDECalc(2023, 6, 4))
|
||||||
|
if result.Type != LunarEclipseNone {
|
||||||
|
t.Fatalf("Type mismatch: got %s want %s", result.Type, LunarEclipseNone)
|
||||||
|
}
|
||||||
|
if result.HasPenumbral || result.HasPartial || result.HasTotal {
|
||||||
|
t.Fatalf("unexpected contacts: %+v", result)
|
||||||
|
}
|
||||||
|
if result.PenumbralStart != 0 || result.PenumbralEnd != 0 || result.PartialStart != 0 || result.PartialEnd != 0 || result.TotalStart != 0 || result.TotalEnd != 0 {
|
||||||
|
t.Fatalf("expected no contact times, got %+v", result)
|
||||||
|
}
|
||||||
|
if result.Magnitude != 0 || result.PenumbralMagnitude != 0 {
|
||||||
|
t.Fatalf("expected zero magnitudes for non-eclipse, got %+v", result)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func assertCloseJD(t *testing.T, name string, got, want, tolerance float64) {
|
||||||
|
t.Helper()
|
||||||
|
if want == 0 {
|
||||||
|
if got != 0 {
|
||||||
|
t.Fatalf("%s mismatch: got %.12f want 0", name, got)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if math.Abs(got-want) > tolerance {
|
||||||
|
t.Fatalf("%s mismatch: got %.12f want %.12f", name, got, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,219 @@
|
|||||||
|
package basic
|
||||||
|
|
||||||
|
import "math"
|
||||||
|
|
||||||
|
func GetLunar(year, month, day int, tz float64) (lyear, lmonth, lday int, leap bool, result string) {
|
||||||
|
julianDayEpoch := JDECalc(year, month, float64(day))
|
||||||
|
// 确定农历年份
|
||||||
|
lyear = year
|
||||||
|
adjustedYear := year
|
||||||
|
if month == 11 || month == 12 {
|
||||||
|
winterSolsticeDay := GetJQTime(year, 270) + tz
|
||||||
|
//firstNewMoonDay := TD2UT(CalcMoonS(float64(year)+11.0/12.0+5.0/30.0/12.0, 0), true) + tz
|
||||||
|
//nextNewMoonDay := TD2UT(CalcMoonS(float64(year)+1.0, 0), true) + tz
|
||||||
|
firstNewMoonDay := TD2UT(CalcMoonSHByJDE(winterSolsticeDay-16, 0), false) + tz
|
||||||
|
nextNewMoonDay := TD2UT(CalcMoonSHByJDE(firstNewMoonDay+28, 0), false) + tz
|
||||||
|
|
||||||
|
firstNewMoonDay = normalizeTimePoint(firstNewMoonDay)
|
||||||
|
nextNewMoonDay = normalizeTimePoint(nextNewMoonDay)
|
||||||
|
|
||||||
|
if winterSolsticeDay >= firstNewMoonDay && winterSolsticeDay < nextNewMoonDay && julianDayEpoch < firstNewMoonDay {
|
||||||
|
adjustedYear--
|
||||||
|
}
|
||||||
|
if winterSolsticeDay >= nextNewMoonDay && julianDayEpoch < nextNewMoonDay {
|
||||||
|
adjustedYear--
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
adjustedYear--
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取节气和朔望月数据
|
||||||
|
solarTerms := GetJieqiLoops(adjustedYear, 25)
|
||||||
|
newMoonDays := GetMoonLoops(float64(adjustedYear), 17)
|
||||||
|
|
||||||
|
// 计算冬至日期
|
||||||
|
winterSolsticeFirst := normalizeTimePoint(solarTerms[0] - 8.0/24 + tz)
|
||||||
|
winterSolsticeSecond := normalizeTimePoint(solarTerms[24] - 8.0/24 + tz)
|
||||||
|
|
||||||
|
// 规范化时间点
|
||||||
|
normalizeTimeArray(newMoonDays, tz)
|
||||||
|
normalizeTimeArray(solarTerms, tz)
|
||||||
|
|
||||||
|
// 计算朔望月范围
|
||||||
|
minMoonIndex, maxMoonIndex := 20, 0
|
||||||
|
moonCount := 0
|
||||||
|
for i := 0; i < len(newMoonDays)-1; i++ {
|
||||||
|
if (newMoonDays[i] <= winterSolsticeFirst && newMoonDays[i+1] > winterSolsticeFirst) ||
|
||||||
|
(newMoonDays[i] > winterSolsticeFirst && newMoonDays[i] < winterSolsticeSecond && newMoonDays[i+1] <= winterSolsticeSecond) {
|
||||||
|
if i <= minMoonIndex {
|
||||||
|
minMoonIndex = i
|
||||||
|
}
|
||||||
|
if i >= maxMoonIndex {
|
||||||
|
maxMoonIndex = i
|
||||||
|
}
|
||||||
|
moonCount++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 确定闰月位置
|
||||||
|
leapMonthPos := 20
|
||||||
|
if moonCount >= 13 {
|
||||||
|
solarTermIndex, i := 0, 0
|
||||||
|
for i = minMoonIndex; i <= maxMoonIndex; i++ {
|
||||||
|
if !(newMoonDays[i] <= solarTerms[solarTermIndex] && newMoonDays[i+1] > solarTerms[solarTermIndex]) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
solarTermIndex += 2
|
||||||
|
}
|
||||||
|
leapMonthPos = i - minMoonIndex
|
||||||
|
}
|
||||||
|
|
||||||
|
// 找到当前月相索引
|
||||||
|
currentMoonIndex := 0
|
||||||
|
for currentMoonIndex = minMoonIndex; currentMoonIndex <= maxMoonIndex; currentMoonIndex++ {
|
||||||
|
if newMoonDays[currentMoonIndex] > julianDayEpoch {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 计算农历月份
|
||||||
|
lmonth = currentMoonIndex - minMoonIndex - 1
|
||||||
|
shouldAdjustLeap := false
|
||||||
|
leap = false
|
||||||
|
|
||||||
|
if lmonth >= leapMonthPos {
|
||||||
|
shouldAdjustLeap = true
|
||||||
|
}
|
||||||
|
if lmonth == leapMonthPos {
|
||||||
|
leap = true
|
||||||
|
}
|
||||||
|
if lmonth < 2 {
|
||||||
|
lmonth += 11
|
||||||
|
} else {
|
||||||
|
lmonth--
|
||||||
|
}
|
||||||
|
if shouldAdjustLeap {
|
||||||
|
lmonth--
|
||||||
|
}
|
||||||
|
if lmonth <= 0 {
|
||||||
|
lmonth += 12
|
||||||
|
}
|
||||||
|
|
||||||
|
// 计算农历日期
|
||||||
|
lday = int(julianDayEpoch-newMoonDays[currentMoonIndex-1]) + 1
|
||||||
|
|
||||||
|
// 生成农历日期字符串
|
||||||
|
result = formatLunarDateString(lmonth, lday, leap)
|
||||||
|
if lmonth >= 10 && month < 3 {
|
||||||
|
lyear--
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetSolar(year, month, day int, leap bool, tz float64) float64 {
|
||||||
|
adjustedYear := year
|
||||||
|
if month < 11 {
|
||||||
|
adjustedYear--
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取节气和朔望月数据
|
||||||
|
solarTerms := GetJieqiLoops(adjustedYear, 25)
|
||||||
|
newMoonDays := GetMoonLoops(float64(adjustedYear), 17)
|
||||||
|
|
||||||
|
// 计算冬至日期
|
||||||
|
winterSolsticeFirst := normalizeTimePoint(solarTerms[0] - 8.0/24 + tz)
|
||||||
|
winterSolsticeSecond := normalizeTimePoint(solarTerms[24] - 8.0/24 + tz)
|
||||||
|
|
||||||
|
// 规范化时间点
|
||||||
|
normalizeTimeArray(newMoonDays, tz)
|
||||||
|
normalizeTimeArray(solarTerms, tz)
|
||||||
|
|
||||||
|
// 计算朔望月范围
|
||||||
|
minMoonIndex, maxMoonIndex := 20, 0
|
||||||
|
moonCount := 0
|
||||||
|
for i := 0; i < 15; i++ {
|
||||||
|
if (newMoonDays[i] <= winterSolsticeFirst && newMoonDays[i+1] > winterSolsticeFirst) ||
|
||||||
|
(newMoonDays[i] > winterSolsticeFirst && newMoonDays[i] < winterSolsticeSecond && newMoonDays[i+1] <= winterSolsticeSecond) {
|
||||||
|
if i <= minMoonIndex {
|
||||||
|
minMoonIndex = i
|
||||||
|
}
|
||||||
|
if i >= maxMoonIndex {
|
||||||
|
maxMoonIndex = i
|
||||||
|
}
|
||||||
|
moonCount++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 确定闰月位置
|
||||||
|
leapMonthPos := 20
|
||||||
|
if moonCount >= 13 {
|
||||||
|
solarTermIndex, i := 0, 0
|
||||||
|
for i = minMoonIndex; i <= maxMoonIndex; i++ {
|
||||||
|
if !(newMoonDays[i] <= solarTerms[solarTermIndex] && newMoonDays[i+1] > solarTerms[solarTermIndex]) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
solarTermIndex += 2
|
||||||
|
}
|
||||||
|
leapMonthPos = i - minMoonIndex
|
||||||
|
}
|
||||||
|
actualMonth := month
|
||||||
|
if actualMonth > 10 {
|
||||||
|
actualMonth -= 11
|
||||||
|
} else {
|
||||||
|
actualMonth++
|
||||||
|
}
|
||||||
|
// 计算实际月份索引
|
||||||
|
if leap {
|
||||||
|
actualMonth++
|
||||||
|
}
|
||||||
|
|
||||||
|
if actualMonth >= leapMonthPos && !leap {
|
||||||
|
actualMonth++
|
||||||
|
}
|
||||||
|
|
||||||
|
return newMoonDays[minMoonIndex+actualMonth] + float64(day) - 1
|
||||||
|
}
|
||||||
|
|
||||||
|
func normalizeTimeArray(timeArray []float64, tz float64) {
|
||||||
|
for idx, timeValue := range timeArray {
|
||||||
|
adjustedTime := timeValue
|
||||||
|
if tz != 8.0/24 {
|
||||||
|
adjustedTime = timeValue - 8.0/24 + tz
|
||||||
|
}
|
||||||
|
timeArray[idx] = normalizeTimePoint(adjustedTime)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func normalizeTimePoint(timePoint float64) float64 {
|
||||||
|
if timePoint-math.Floor(timePoint) > 0.5 {
|
||||||
|
return math.Floor(timePoint) + 0.5
|
||||||
|
}
|
||||||
|
return math.Floor(timePoint) - 0.5
|
||||||
|
}
|
||||||
|
|
||||||
|
func formatLunarDateString(lunarMonth, lunarDay int, isLeap bool) string {
|
||||||
|
monthNames := []string{"十", "一", "二", "三", "四", "五", "六", "七", "八", "九", "十", "冬", "腊"}
|
||||||
|
dayPrefixes := []string{"初", "十", "廿", "三"}
|
||||||
|
|
||||||
|
var dateString string
|
||||||
|
|
||||||
|
if isLeap {
|
||||||
|
dateString += "闰"
|
||||||
|
}
|
||||||
|
|
||||||
|
if lunarMonth == 1 {
|
||||||
|
dateString += "正月"
|
||||||
|
} else {
|
||||||
|
dateString += monthNames[lunarMonth] + "月"
|
||||||
|
}
|
||||||
|
|
||||||
|
if lunarDay == 20 {
|
||||||
|
dateString += "二十"
|
||||||
|
} else if lunarDay == 10 {
|
||||||
|
dateString += "初十"
|
||||||
|
} else {
|
||||||
|
dateString += dayPrefixes[lunarDay/10] + monthNames[lunarDay%10]
|
||||||
|
}
|
||||||
|
|
||||||
|
return dateString
|
||||||
|
}
|
||||||
+108
-356
@@ -7,162 +7,122 @@ import (
|
|||||||
. "b612.me/astro/tools"
|
. "b612.me/astro/tools"
|
||||||
)
|
)
|
||||||
|
|
||||||
func MarsL(JD float64) float64 {
|
func MarsL(jd float64) float64 {
|
||||||
return planet.WherePlanet(3, 0, JD)
|
return planet.WherePlanet(3, 0, jd)
|
||||||
}
|
}
|
||||||
|
|
||||||
func MarsB(JD float64) float64 {
|
func MarsB(jd float64) float64 {
|
||||||
return planet.WherePlanet(3, 1, JD)
|
return planet.WherePlanet(3, 1, jd)
|
||||||
}
|
}
|
||||||
func MarsR(JD float64) float64 {
|
func MarsR(jd float64) float64 {
|
||||||
return planet.WherePlanet(3, 2, JD)
|
return planet.WherePlanet(3, 2, jd)
|
||||||
}
|
}
|
||||||
func AMarsX(JD float64) float64 {
|
func AMarsX(jd float64) float64 {
|
||||||
l := MarsL(JD)
|
l := MarsL(jd)
|
||||||
b := MarsB(JD)
|
b := MarsB(jd)
|
||||||
r := MarsR(JD)
|
r := MarsR(jd)
|
||||||
el := planet.WherePlanet(-1, 0, JD)
|
el := planet.WherePlanet(-1, 0, jd)
|
||||||
eb := planet.WherePlanet(-1, 1, JD)
|
eb := planet.WherePlanet(-1, 1, jd)
|
||||||
er := planet.WherePlanet(-1, 2, JD)
|
er := planet.WherePlanet(-1, 2, jd)
|
||||||
x := r*Cos(b)*Cos(l) - er*Cos(eb)*Cos(el)
|
x := r*Cos(b)*Cos(l) - er*Cos(eb)*Cos(el)
|
||||||
return x
|
return x
|
||||||
}
|
}
|
||||||
|
|
||||||
func AMarsY(JD float64) float64 {
|
func AMarsY(jd float64) float64 {
|
||||||
|
|
||||||
l := MarsL(JD)
|
l := MarsL(jd)
|
||||||
b := MarsB(JD)
|
b := MarsB(jd)
|
||||||
r := MarsR(JD)
|
r := MarsR(jd)
|
||||||
el := planet.WherePlanet(-1, 0, JD)
|
el := planet.WherePlanet(-1, 0, jd)
|
||||||
eb := planet.WherePlanet(-1, 1, JD)
|
eb := planet.WherePlanet(-1, 1, jd)
|
||||||
er := planet.WherePlanet(-1, 2, JD)
|
er := planet.WherePlanet(-1, 2, jd)
|
||||||
y := r*Cos(b)*Sin(l) - er*Cos(eb)*Sin(el)
|
y := r*Cos(b)*Sin(l) - er*Cos(eb)*Sin(el)
|
||||||
return y
|
return y
|
||||||
}
|
}
|
||||||
func AMarsZ(JD float64) float64 {
|
func AMarsZ(jd float64) float64 {
|
||||||
//l := MarsL(JD)
|
//l := MarsL(jd)
|
||||||
b := MarsB(JD)
|
b := MarsB(jd)
|
||||||
r := MarsR(JD)
|
r := MarsR(jd)
|
||||||
// el := planet.WherePlanet(-1, 0, JD)
|
// el := planet.WherePlanet(-1, 0, jd)
|
||||||
eb := planet.WherePlanet(-1, 1, JD)
|
eb := planet.WherePlanet(-1, 1, jd)
|
||||||
er := planet.WherePlanet(-1, 2, JD)
|
er := planet.WherePlanet(-1, 2, jd)
|
||||||
z := r*Sin(b) - er*Sin(eb)
|
z := r*Sin(b) - er*Sin(eb)
|
||||||
return z
|
return z
|
||||||
}
|
}
|
||||||
|
|
||||||
func AMarsXYZ(JD float64) (float64, float64, float64) {
|
func AMarsXYZ(jd float64) (float64, float64, float64) {
|
||||||
l := MarsL(JD)
|
l := MarsL(jd)
|
||||||
b := MarsB(JD)
|
b := MarsB(jd)
|
||||||
r := MarsR(JD)
|
r := MarsR(jd)
|
||||||
el := planet.WherePlanet(-1, 0, JD)
|
el := planet.WherePlanet(-1, 0, jd)
|
||||||
eb := planet.WherePlanet(-1, 1, JD)
|
eb := planet.WherePlanet(-1, 1, jd)
|
||||||
er := planet.WherePlanet(-1, 2, JD)
|
er := planet.WherePlanet(-1, 2, jd)
|
||||||
x := r*Cos(b)*Cos(l) - er*Cos(eb)*Cos(el)
|
x := r*Cos(b)*Cos(l) - er*Cos(eb)*Cos(el)
|
||||||
y := r*Cos(b)*Sin(l) - er*Cos(eb)*Sin(el)
|
y := r*Cos(b)*Sin(l) - er*Cos(eb)*Sin(el)
|
||||||
z := r*Sin(b) - er*Sin(eb)
|
z := r*Sin(b) - er*Sin(eb)
|
||||||
return x, y, z
|
return x, y, z
|
||||||
}
|
}
|
||||||
|
|
||||||
func MarsApparentRa(JD float64) float64 {
|
func MarsApparentRa(jd float64) float64 {
|
||||||
lo, bo := MarsApparentLoBo(JD)
|
lo, bo := MarsApparentLoBo(jd)
|
||||||
sita := Sita(JD)
|
eps := TrueObliquity(jd)
|
||||||
ra := math.Atan2((Sin(lo)*Cos(sita) - Tan(bo)*Sin(sita)), Cos(lo))
|
ra := math.Atan2((Sin(lo)*Cos(eps) - Tan(bo)*Sin(eps)), Cos(lo))
|
||||||
ra = ra * 180 / math.Pi
|
ra = ra * 180 / math.Pi
|
||||||
return Limit360(ra)
|
return Limit360(ra)
|
||||||
}
|
}
|
||||||
func MarsApparentDec(JD float64) float64 {
|
func MarsApparentDec(jd float64) float64 {
|
||||||
lo, bo := MarsApparentLoBo(JD)
|
lo, bo := MarsApparentLoBo(jd)
|
||||||
sita := Sita(JD)
|
eps := TrueObliquity(jd)
|
||||||
dec := ArcSin(Sin(bo)*Cos(sita) + Cos(bo)*Sin(sita)*Sin(lo))
|
dec := ArcSin(Sin(bo)*Cos(eps) + Cos(bo)*Sin(eps)*Sin(lo))
|
||||||
return dec
|
return dec
|
||||||
}
|
}
|
||||||
|
|
||||||
func MarsApparentRaDec(JD float64) (float64, float64) {
|
func MarsApparentRaDec(jd float64) (float64, float64) {
|
||||||
lo, bo := MarsApparentLoBo(JD)
|
lo, bo := MarsApparentLoBo(jd)
|
||||||
sita := Sita(JD)
|
eps := TrueObliquity(jd)
|
||||||
ra := math.Atan2((Sin(lo)*Cos(sita) - Tan(bo)*Sin(sita)), Cos(lo))
|
ra := math.Atan2((Sin(lo)*Cos(eps) - Tan(bo)*Sin(eps)), Cos(lo))
|
||||||
ra = ra * 180 / math.Pi
|
ra = ra * 180 / math.Pi
|
||||||
dec := ArcSin(Sin(bo)*Cos(sita) + Cos(bo)*Sin(sita)*Sin(lo))
|
dec := ArcSin(Sin(bo)*Cos(eps) + Cos(bo)*Sin(eps)*Sin(lo))
|
||||||
return Limit360(ra), dec
|
return Limit360(ra), dec
|
||||||
}
|
}
|
||||||
|
|
||||||
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) + HJZD(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+=HJZD(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 += HJZD(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 {
|
||||||
AwaySun := MarsR(JD)
|
sunDistance := MarsR(jd)
|
||||||
AwayEarth := EarthMarsAway(JD)
|
earthDistance := EarthMarsAway(jd)
|
||||||
Away := planet.WherePlanet(-1, 2, JD)
|
earthSunDistance := planet.WherePlanet(-1, 2, jd)
|
||||||
i := (AwaySun*AwaySun + AwayEarth*AwayEarth - Away*Away) / (2 * AwaySun * AwayEarth)
|
i := (sunDistance*sunDistance + earthDistance*earthDistance - earthSunDistance*earthSunDistance) / (2 * sunDistance * earthDistance)
|
||||||
i = ArcCos(i)
|
i = ArcCos(i)
|
||||||
Mag := -1.52 + 5*math.Log10(AwaySun*AwayEarth) + 0.016*i
|
mag := -1.52 + 5*math.Log10(sunDistance*earthDistance) + 0.016*i
|
||||||
return FloatRound(Mag, 2)
|
return FloatRound(mag, 2)
|
||||||
}
|
}
|
||||||
|
|
||||||
func MarsHeight(jde, lon, lat, timezone float64) float64 {
|
func MarsHeight(jde, lon, lat, timezone float64) float64 {
|
||||||
@@ -172,10 +132,10 @@ func MarsHeight(jde, lon, lat, timezone float64) float64 {
|
|||||||
ra, dec := MarsApparentRaDec(TD2UT(utcJde, true))
|
ra, dec := MarsApparentRaDec(TD2UT(utcJde, true))
|
||||||
st := Limit360(ApparentSiderealTime(utcJde)*15 + lon)
|
st := Limit360(ApparentSiderealTime(utcJde)*15 + lon)
|
||||||
// 计算时角
|
// 计算时角
|
||||||
H := Limit360(st - ra)
|
hourAngle := Limit360(st - ra)
|
||||||
// 高度角、时角与天球座标三角转换公式
|
// 高度角、时角与天球座标三角转换公式
|
||||||
// sin(h)=sin(lat)*sin(dec)+cos(dec)*cos(lat)*cos(H)
|
// sin(h)=sin(lat)*sin(dec)+cos(dec)*cos(lat)*cos(hourAngle)
|
||||||
sinHeight := Sin(lat)*Sin(dec) + Cos(dec)*Cos(lat)*Cos(H)
|
sinHeight := Sin(lat)*Sin(dec) + Cos(dec)*Cos(lat)*Cos(hourAngle)
|
||||||
return ArcSin(sinHeight)
|
return ArcSin(sinHeight)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -186,271 +146,63 @@ func MarsAzimuth(jde, lon, lat, timezone float64) float64 {
|
|||||||
ra, dec := MarsApparentRaDec(TD2UT(utcJde, true))
|
ra, dec := MarsApparentRaDec(TD2UT(utcJde, true))
|
||||||
st := Limit360(ApparentSiderealTime(utcJde)*15 + lon)
|
st := Limit360(ApparentSiderealTime(utcJde)*15 + lon)
|
||||||
// 计算时角
|
// 计算时角
|
||||||
H := Limit360(st - ra)
|
hourAngle := Limit360(st - ra)
|
||||||
// 三角转换公式
|
// 三角转换公式
|
||||||
tanAzimuth := Sin(H) / (Cos(H)*Sin(lat) - Tan(dec)*Cos(lat))
|
tanAzimuth := Sin(hourAngle) / (Cos(hourAngle)*Sin(lat) - Tan(dec)*Cos(lat))
|
||||||
Azimuth := ArcTan(tanAzimuth)
|
azimuth := ArcTan(tanAzimuth)
|
||||||
if Azimuth < 0 {
|
if azimuth < 0 {
|
||||||
if H/15 < 12 {
|
if hourAngle/15 < 12 {
|
||||||
return Azimuth + 360
|
return azimuth + 360
|
||||||
}
|
}
|
||||||
return Azimuth + 180
|
return azimuth + 180
|
||||||
}
|
}
|
||||||
if H/15 < 12 {
|
if hourAngle/15 < 12 {
|
||||||
return Azimuth + 180
|
return azimuth + 180
|
||||||
}
|
}
|
||||||
return Azimuth
|
return azimuth
|
||||||
}
|
}
|
||||||
|
|
||||||
func MarsHourAngle(JD, Lon, TZ float64) float64 {
|
func MarsHourAngle(jd, lon, timezone float64) float64 {
|
||||||
startime := Limit360(ApparentSiderealTime(JD-TZ/24)*15 + Lon)
|
siderealLongitude := Limit360(ApparentSiderealTime(jd-timezone/24)*15 + lon)
|
||||||
timeangle := startime - MarsApparentRa(TD2UT(JD-TZ/24.0, true))
|
hourAngle := siderealLongitude - MarsApparentRa(TD2UT(jd-timezone/24.0, true))
|
||||||
if timeangle < 0 {
|
if hourAngle < 0 {
|
||||||
timeangle += 360
|
hourAngle += 360
|
||||||
}
|
}
|
||||||
return timeangle
|
return hourAngle
|
||||||
}
|
}
|
||||||
|
|
||||||
func MarsCulminationTime(jde, lon, timezone float64) float64 {
|
func MarsCulminationTime(jde, lon, timezone float64) float64 {
|
||||||
//jde 世界时,非力学时,当地时区 0时,无需转换力学时
|
//jde 世界时,非力学时,当地时区 0时,无需转换力学时
|
||||||
//ra,dec 瞬时天球座标,非J2000等时间天球坐标
|
//ra,dec 瞬时天球座标,非J2000等时间天球坐标
|
||||||
jde = math.Floor(jde) + 0.5
|
jde = math.Floor(jde) + 0.5
|
||||||
JD1 := jde + Limit360(360-MarsHourAngle(jde, lon, timezone))/15.0/24.0*0.99726851851851851851
|
estimateJD := jde + Limit360(360-MarsHourAngle(jde, lon, timezone))/15.0/24.0*0.99726851851851851851
|
||||||
limitHA := func(jde, lon, timezone float64) float64 {
|
normalizedHourAngle := func(jde, lon, timezone float64) float64 {
|
||||||
ha := MarsHourAngle(jde, lon, timezone)
|
currentHourAngle := MarsHourAngle(jde, lon, timezone)
|
||||||
if ha < 180 {
|
if currentHourAngle < 180 {
|
||||||
ha += 360
|
currentHourAngle += 360
|
||||||
}
|
}
|
||||||
return ha
|
return currentHourAngle
|
||||||
}
|
}
|
||||||
for {
|
for {
|
||||||
JD0 := JD1
|
prevJD := estimateJD
|
||||||
stDegree := limitHA(JD0, lon, timezone) - 360
|
hourAngleDelta := normalizedHourAngle(prevJD, lon, timezone) - 360
|
||||||
stDegreep := (limitHA(JD0+0.000005, lon, timezone) - limitHA(JD0-0.000005, lon, timezone)) / 0.00001
|
hourAngleSlope := (normalizedHourAngle(prevJD+0.000005, lon, timezone) - normalizedHourAngle(prevJD-0.000005, lon, timezone)) / 0.00001
|
||||||
JD1 = JD0 - stDegree/stDegreep
|
estimateJD = prevJD - hourAngleDelta/hourAngleSlope
|
||||||
if math.Abs(JD1-JD0) <= 0.00001 {
|
if math.Abs(estimateJD-prevJD) <= 0.00001 {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return JD1
|
return estimateJD
|
||||||
}
|
}
|
||||||
|
|
||||||
func MarsRiseTime(JD, Lon, Lat, TZ, ZS, HEI float64) float64 {
|
func MarsRiseTime(jd, lon, lat, timezone, aeroCorrection, observerHeight float64) (float64, error) {
|
||||||
return marsRiseDown(JD, Lon, Lat, TZ, ZS, HEI, true)
|
return marsRiseDown(jd, lon, lat, timezone, aeroCorrection, observerHeight, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
func MarsDownTime(JD, Lon, Lat, TZ, ZS, HEI float64) float64 {
|
func MarsSetTime(jd, lon, lat, timezone, aeroCorrection, observerHeight float64) (float64, error) {
|
||||||
return marsRiseDown(JD, Lon, Lat, TZ, ZS, HEI, false)
|
return marsRiseDown(jd, lon, lat, timezone, aeroCorrection, observerHeight, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
func marsRiseDown(JD, Lon, Lat, TZ, ZS, HEI float64, isRise bool) float64 {
|
func marsRiseDown(jd, lon, lat, timezone, aeroCorrection, observerHeight float64, isRise bool) (float64, error) {
|
||||||
var An float64
|
return planetRiseDown(jd, lon, lat, timezone, aeroCorrection, observerHeight, isRise, MarsCulminationTime, MarsHeight, MarsApparentDec)
|
||||||
JD = math.Floor(JD) + 0.5
|
|
||||||
ntz := math.Round(Lon / 15)
|
|
||||||
if ZS != 0 {
|
|
||||||
An = -0.8333
|
|
||||||
}
|
|
||||||
An = An - HeightDegreeByLat(HEI, Lat)
|
|
||||||
tztime := MarsCulminationTime(JD, Lon, ntz)
|
|
||||||
if MarsHeight(tztime, Lon, Lat, ntz) < An {
|
|
||||||
return -2 //极夜
|
|
||||||
}
|
|
||||||
if MarsHeight(tztime-0.5, Lon, Lat, ntz) > An {
|
|
||||||
return -1 //极昼
|
|
||||||
}
|
|
||||||
dec := HSunApparentDec(TD2UT(tztime-ntz/24, true))
|
|
||||||
//(sin(ho)-sin(φ)*sin(δ2))/(cos(φ)*cos(δ2))
|
|
||||||
tmp := (Sin(An) - Sin(dec)*Sin(Lat)) / (Cos(dec) * Cos(Lat))
|
|
||||||
var rise float64
|
|
||||||
if math.Abs(tmp) <= 1 {
|
|
||||||
rzsc := ArcCos(tmp) / 15
|
|
||||||
if isRise {
|
|
||||||
rise = tztime - rzsc/24 - 25.0/24.0/60.0
|
|
||||||
} else {
|
|
||||||
rise = tztime + rzsc/24 - 25.0/24.0/60.0
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
rise = tztime
|
|
||||||
i := 0
|
|
||||||
//TODO:使用二分法计算
|
|
||||||
for MarsHeight(rise, Lon, Lat, ntz) > An {
|
|
||||||
i++
|
|
||||||
if isRise {
|
|
||||||
rise -= 15.0 / 60.0 / 24.0
|
|
||||||
} else {
|
|
||||||
rise += 15.0 / 60.0 / 24.0
|
|
||||||
}
|
|
||||||
if i > 48 {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
JD1 := rise
|
|
||||||
for {
|
|
||||||
JD0 := JD1
|
|
||||||
stDegree := MarsHeight(JD0, Lon, Lat, ntz) - An
|
|
||||||
stDegreep := (MarsHeight(JD0+0.000005, Lon, Lat, ntz) - MarsHeight(JD0-0.000005, Lon, Lat, ntz)) / 0.00001
|
|
||||||
JD1 = JD0 - stDegree/stDegreep
|
|
||||||
if math.Abs(JD1-JD0) <= 0.00001 {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return JD1 - ntz/24 + TZ/24
|
|
||||||
}
|
|
||||||
|
|
||||||
// Pos
|
|
||||||
|
|
||||||
const MARS_S_PERIOD = 1 / ((1 / 365.256363004) - (1 / 686.98))
|
|
||||||
|
|
||||||
func marsConjunction(jde, degree float64, next uint8) float64 {
|
|
||||||
//0=last 1=next
|
|
||||||
decSub := func(jde float64, degree float64, filter bool) float64 {
|
|
||||||
sub := Limit360(Limit360(MarsApparentLo(jde)-HSunApparentLo(jde)) - degree)
|
|
||||||
if filter {
|
|
||||||
if sub > 180 {
|
|
||||||
sub -= 360
|
|
||||||
}
|
|
||||||
if sub < -180 {
|
|
||||||
sub += 360
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return sub
|
|
||||||
}
|
|
||||||
dayCost := MARS_S_PERIOD / 360
|
|
||||||
nowSub := decSub(jde, degree, false)
|
|
||||||
if next == 0 {
|
|
||||||
jde -= (360 - nowSub) * dayCost
|
|
||||||
} else {
|
|
||||||
jde += dayCost * nowSub
|
|
||||||
}
|
|
||||||
JD1 := jde
|
|
||||||
for {
|
|
||||||
JD0 := JD1
|
|
||||||
stDegree := decSub(JD0, degree, true)
|
|
||||||
stDegreep := (decSub(JD0+0.000005, degree, true) - decSub(JD0-0.000005, degree, true)) / 0.00001
|
|
||||||
JD1 = JD0 - stDegree/stDegreep
|
|
||||||
if math.Abs(JD1-JD0) <= 0.00001 {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return TD2UT(JD1, false)
|
|
||||||
}
|
|
||||||
|
|
||||||
func LastMarsConjunction(jde float64) float64 {
|
|
||||||
return marsConjunction(jde, 0, 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
func NextMarsConjunction(jde float64) float64 {
|
|
||||||
return marsConjunction(jde, 0, 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
func LastMarsOpposition(jde float64) float64 {
|
|
||||||
return marsConjunction(jde, 180, 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
func NextMarsOpposition(jde float64) float64 {
|
|
||||||
return marsConjunction(jde, 180, 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
func NextMarsEasternQuadrature(jde float64) float64 {
|
|
||||||
return marsConjunction(jde, 90, 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
func LastMarsEasternQuadrature(jde float64) float64 {
|
|
||||||
return marsConjunction(jde, 90, 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
func NextMarsWesternQuadrature(jde float64) float64 {
|
|
||||||
return marsConjunction(jde, 270, 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
func LastMarsWesternQuadrature(jde float64) float64 {
|
|
||||||
return marsConjunction(jde, 270, 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
func marsRetrograde(jde float64, isLeft bool) float64 {
|
|
||||||
//0=last 1=next
|
|
||||||
decSub := func(jde float64, val float64) float64 {
|
|
||||||
sub := MarsApparentRa(jde+val) - MarsApparentRa(jde-val)
|
|
||||||
if sub > 180 {
|
|
||||||
sub -= 360
|
|
||||||
}
|
|
||||||
if sub < -180 {
|
|
||||||
sub += 360
|
|
||||||
}
|
|
||||||
return sub / (2 * val)
|
|
||||||
}
|
|
||||||
jde = NextMarsOpposition(jde)
|
|
||||||
if isLeft {
|
|
||||||
jde -= 60
|
|
||||||
} else {
|
|
||||||
jde += 60
|
|
||||||
}
|
|
||||||
for {
|
|
||||||
nowSub := decSub(jde, 1.0/86400.0)
|
|
||||||
if math.Abs(nowSub) > 0.55 {
|
|
||||||
jde += 2
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
break
|
|
||||||
}
|
|
||||||
JD1 := jde
|
|
||||||
for {
|
|
||||||
JD0 := JD1
|
|
||||||
stDegree := decSub(JD0, 2.0/86400.0)
|
|
||||||
stDegreep := (decSub(JD0+15.0/86400.0, 2.0/86400.0) - decSub(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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
JD1 = JD1 - 15.0/86400.0
|
|
||||||
min := JD1
|
|
||||||
minRa := 100.0
|
|
||||||
for i := 0.0; i < 60.0; i++ {
|
|
||||||
tmp := decSub(JD1+i*0.5/86400.0, 0.5/86400.0)
|
|
||||||
if math.Abs(tmp) < math.Abs(minRa) {
|
|
||||||
minRa = tmp
|
|
||||||
min = JD1 + i*0.5/86400.0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return TD2UT(min, false)
|
|
||||||
}
|
|
||||||
|
|
||||||
func NextMarsRetrogradeToPrograde(jde float64) float64 {
|
|
||||||
date := marsRetrograde(jde, false)
|
|
||||||
if date < jde {
|
|
||||||
op := NextMarsOpposition(jde)
|
|
||||||
return marsRetrograde(op+10, false)
|
|
||||||
}
|
|
||||||
return date
|
|
||||||
}
|
|
||||||
|
|
||||||
func LastMarsRetrogradeToPrograde(jde float64) float64 {
|
|
||||||
jde = LastMarsOpposition(jde) - 10
|
|
||||||
date := marsRetrograde(jde, false)
|
|
||||||
if date > jde {
|
|
||||||
op := LastMarsOpposition(jde)
|
|
||||||
return marsRetrograde(op-10, false)
|
|
||||||
}
|
|
||||||
return date
|
|
||||||
}
|
|
||||||
|
|
||||||
func NextMarsProgradeToRetrograde(jde float64) float64 {
|
|
||||||
date := marsRetrograde(jde, true)
|
|
||||||
if date < jde {
|
|
||||||
op := NextMarsOpposition(jde)
|
|
||||||
return marsRetrograde(op+10, true)
|
|
||||||
}
|
|
||||||
return date
|
|
||||||
}
|
|
||||||
|
|
||||||
func LastMarsProgradeToRetrograde(jde float64) float64 {
|
|
||||||
jde = LastMarsOpposition(jde) - 10
|
|
||||||
date := marsRetrograde(jde, true)
|
|
||||||
if date > jde {
|
|
||||||
op := LastMarsOpposition(jde)
|
|
||||||
return marsRetrograde(op-10, true)
|
|
||||||
}
|
|
||||||
return date
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,35 @@
|
|||||||
|
package basic
|
||||||
|
|
||||||
|
import "testing"
|
||||||
|
|
||||||
|
func BenchmarkMarsPhaseFamily(b *testing.B) {
|
||||||
|
cases := marsEventCases()[:8]
|
||||||
|
samples := marsEventSamples()
|
||||||
|
var sink float64
|
||||||
|
b.ReportAllocs()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
for _, sample := range samples {
|
||||||
|
jd := marsEventSampleTTJD(sample)
|
||||||
|
for _, event := range cases {
|
||||||
|
sink += event.fn(jd)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ = sink
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkMarsRetrogradeFamily(b *testing.B) {
|
||||||
|
cases := marsEventCases()[8:]
|
||||||
|
samples := marsEventSamples()
|
||||||
|
var sink float64
|
||||||
|
b.ReportAllocs()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
for _, sample := range samples {
|
||||||
|
jd := marsEventSampleTTJD(sample)
|
||||||
|
for _, event := range cases {
|
||||||
|
sink += event.fn(jd)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ = sink
|
||||||
|
}
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
package basic
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
|
type marsEventFunc func(float64) float64
|
||||||
|
|
||||||
|
type marsEventCase struct {
|
||||||
|
name string
|
||||||
|
tolerance float64
|
||||||
|
fn marsEventFunc
|
||||||
|
}
|
||||||
|
|
||||||
|
func marsEventSamples() []time.Time {
|
||||||
|
start := time.Date(1992, 8, 17, 9, 21, 45, 678000000, time.UTC)
|
||||||
|
samples := make([]time.Time, 0, 96)
|
||||||
|
for i := 0; i < 48; i++ {
|
||||||
|
d := start.AddDate(0, 0, i*311)
|
||||||
|
d = d.Add(time.Duration((i%8)*4)*time.Hour + time.Duration((i%10)*9)*time.Minute + time.Duration((i%12)*17)*time.Second)
|
||||||
|
samples = append(samples, d)
|
||||||
|
}
|
||||||
|
extraStart := start.AddDate(0, 0, 53)
|
||||||
|
for i := 0; i < 48; i++ {
|
||||||
|
d := extraStart.AddDate(0, 0, i*197)
|
||||||
|
d = d.Add(time.Duration((i%7)*5)*time.Hour + time.Duration((i%9)*14)*time.Minute + time.Duration((i%16)*23)*time.Second)
|
||||||
|
samples = append(samples, d)
|
||||||
|
}
|
||||||
|
return samples
|
||||||
|
}
|
||||||
|
|
||||||
|
func marsEventSampleTTJD(date time.Time) float64 {
|
||||||
|
return TD2UT(Date2JDE(date.UTC()), true)
|
||||||
|
}
|
||||||
|
|
||||||
|
func marsEventCases() []marsEventCase {
|
||||||
|
const (
|
||||||
|
conjunctionTolerance = 0.00001
|
||||||
|
searchTolerance = 30.0 / 86400.0
|
||||||
|
)
|
||||||
|
return []marsEventCase{
|
||||||
|
{name: "LastMarsConjunction", tolerance: conjunctionTolerance, fn: LastMarsConjunction},
|
||||||
|
{name: "NextMarsConjunction", tolerance: conjunctionTolerance, fn: NextMarsConjunction},
|
||||||
|
{name: "LastMarsOpposition", tolerance: conjunctionTolerance, fn: LastMarsOpposition},
|
||||||
|
{name: "NextMarsOpposition", tolerance: conjunctionTolerance, fn: NextMarsOpposition},
|
||||||
|
{name: "LastMarsEasternQuadrature", tolerance: conjunctionTolerance, fn: LastMarsEasternQuadrature},
|
||||||
|
{name: "NextMarsEasternQuadrature", tolerance: conjunctionTolerance, fn: NextMarsEasternQuadrature},
|
||||||
|
{name: "LastMarsWesternQuadrature", tolerance: conjunctionTolerance, fn: LastMarsWesternQuadrature},
|
||||||
|
{name: "NextMarsWesternQuadrature", tolerance: conjunctionTolerance, fn: NextMarsWesternQuadrature},
|
||||||
|
{name: "LastMarsProgradeToRetrograde", tolerance: searchTolerance, fn: LastMarsProgradeToRetrograde},
|
||||||
|
{name: "NextMarsProgradeToRetrograde", tolerance: searchTolerance, fn: NextMarsProgradeToRetrograde},
|
||||||
|
{name: "LastMarsRetrogradeToPrograde", tolerance: searchTolerance, fn: LastMarsRetrogradeToPrograde},
|
||||||
|
{name: "NextMarsRetrogradeToPrograde", tolerance: searchTolerance, fn: NextMarsRetrogradeToPrograde},
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
package basic
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"math"
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
type marsEventBaselineSample struct {
|
||||||
|
InputUTC string `json:"input_utc"`
|
||||||
|
TTJDBits uint64 `json:"tt_jd_bits"`
|
||||||
|
Events map[string]uint64 `json:"events"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type marsEventBaseline struct {
|
||||||
|
Samples []marsEventBaselineSample `json:"samples"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadMarsEventBaseline(t *testing.T) marsEventBaseline {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
data, err := os.ReadFile("testdata/mars_event_baseline.json")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var baseline marsEventBaseline
|
||||||
|
if err := json.Unmarshal(data, &baseline); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if len(baseline.Samples) == 0 {
|
||||||
|
t.Fatal("empty mars event baseline")
|
||||||
|
}
|
||||||
|
return baseline
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMarsEventBaselineRegression(t *testing.T) {
|
||||||
|
baseline := loadMarsEventBaseline(t)
|
||||||
|
cases := marsEventCases()
|
||||||
|
|
||||||
|
for _, sample := range baseline.Samples {
|
||||||
|
jd := math.Float64frombits(sample.TTJDBits)
|
||||||
|
for _, event := range cases {
|
||||||
|
wantBits, ok := sample.Events[event.name]
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("%s missing baseline event %s", sample.InputUTC, event.name)
|
||||||
|
}
|
||||||
|
want := math.Float64frombits(wantBits)
|
||||||
|
got := event.fn(jd)
|
||||||
|
diff := math.Abs(got - want)
|
||||||
|
if diff > event.tolerance {
|
||||||
|
t.Fatalf("%s %s diff %.12f > tolerance %.12f", sample.InputUTC, event.name, diff, event.tolerance)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,269 @@
|
|||||||
|
package basic
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math"
|
||||||
|
|
||||||
|
. "b612.me/astro/tools"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Pos
|
||||||
|
|
||||||
|
const (
|
||||||
|
MARS_S_PERIOD = 1 / ((1 / 365.256363004) - (1 / 686.98))
|
||||||
|
marsEventSearchN = 16
|
||||||
|
marsPhaseCoarseTolerance = 30.0 / 86400.0
|
||||||
|
)
|
||||||
|
|
||||||
|
func marsSunLongitudeDelta(jde, degree float64, filter bool) float64 {
|
||||||
|
sub := Limit360(Limit360(MarsApparentLo(jde)-HSunApparentLo(jde)) - degree)
|
||||||
|
if filter {
|
||||||
|
if sub > 180 {
|
||||||
|
sub -= 360
|
||||||
|
}
|
||||||
|
if sub < -180 {
|
||||||
|
sub += 360
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return sub
|
||||||
|
}
|
||||||
|
|
||||||
|
func marsSunLongitudeDeltaN(jde, degree float64, filter bool, n int) float64 {
|
||||||
|
sub := Limit360(Limit360(MarsApparentLoN(jde, n)-HSunApparentLoN(jde, n)) - degree)
|
||||||
|
if filter {
|
||||||
|
if sub > 180 {
|
||||||
|
sub -= 360
|
||||||
|
}
|
||||||
|
if sub < -180 {
|
||||||
|
sub += 360
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return sub
|
||||||
|
}
|
||||||
|
|
||||||
|
func marsRADerivative(jde, val float64) float64 {
|
||||||
|
sub := MarsApparentRa(jde+val) - MarsApparentRa(jde-val)
|
||||||
|
if sub > 180 {
|
||||||
|
sub -= 360
|
||||||
|
}
|
||||||
|
if sub < -180 {
|
||||||
|
sub += 360
|
||||||
|
}
|
||||||
|
return sub / (2 * val)
|
||||||
|
}
|
||||||
|
|
||||||
|
func marsRADerivativeN(jde, val float64, n int) float64 {
|
||||||
|
sub := MarsApparentRaN(jde+val, n) - MarsApparentRaN(jde-val, n)
|
||||||
|
if sub > 180 {
|
||||||
|
sub -= 360
|
||||||
|
}
|
||||||
|
if sub < -180 {
|
||||||
|
sub += 360
|
||||||
|
}
|
||||||
|
return sub / (2 * val)
|
||||||
|
}
|
||||||
|
|
||||||
|
func marsConjunctionFull(jde, degree float64, next uint8) float64 {
|
||||||
|
//0=last 1=next
|
||||||
|
daysPerDegree := MARS_S_PERIOD / 360
|
||||||
|
currentDelta := marsSunLongitudeDelta(jde, degree, false)
|
||||||
|
if next == 0 {
|
||||||
|
jde -= (360 - currentDelta) * daysPerDegree
|
||||||
|
} else {
|
||||||
|
jde += daysPerDegree * currentDelta
|
||||||
|
}
|
||||||
|
estimateJD := jde
|
||||||
|
for {
|
||||||
|
prevJD := estimateJD
|
||||||
|
longitudeDelta := marsSunLongitudeDelta(prevJD, degree, true)
|
||||||
|
longitudeSlope := (marsSunLongitudeDelta(prevJD+0.000005, degree, true) - marsSunLongitudeDelta(prevJD-0.000005, degree, true)) / 0.00001
|
||||||
|
estimateJD = prevJD - longitudeDelta/longitudeSlope
|
||||||
|
if math.Abs(estimateJD-prevJD) <= 0.00001 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return TD2UT(estimateJD, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
func marsConjunction(jde, degree float64, next uint8) float64 {
|
||||||
|
//0=last 1=next
|
||||||
|
daysPerDegree := MARS_S_PERIOD / 360
|
||||||
|
currentDelta := marsSunLongitudeDelta(jde, degree, false)
|
||||||
|
if next == 0 {
|
||||||
|
jde -= (360 - currentDelta) * daysPerDegree
|
||||||
|
} else {
|
||||||
|
jde += daysPerDegree * currentDelta
|
||||||
|
}
|
||||||
|
estimateJD := jde
|
||||||
|
for {
|
||||||
|
prevJD := estimateJD
|
||||||
|
longitudeDelta := marsSunLongitudeDeltaN(prevJD, degree, true, marsEventSearchN)
|
||||||
|
longitudeSlope := (marsSunLongitudeDeltaN(prevJD+0.000005, degree, true, marsEventSearchN) - marsSunLongitudeDeltaN(prevJD-0.000005, degree, true, marsEventSearchN)) / 0.00001
|
||||||
|
estimateJD = prevJD - longitudeDelta/longitudeSlope
|
||||||
|
if math.Abs(estimateJD-prevJD) <= marsPhaseCoarseTolerance {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for {
|
||||||
|
prevJD := estimateJD
|
||||||
|
longitudeDelta := marsSunLongitudeDelta(prevJD, degree, true)
|
||||||
|
longitudeSlope := (marsSunLongitudeDelta(prevJD+0.000005, degree, true) - marsSunLongitudeDelta(prevJD-0.000005, degree, true)) / 0.00001
|
||||||
|
estimateJD = prevJD - longitudeDelta/longitudeSlope
|
||||||
|
if math.Abs(estimateJD-prevJD) <= 0.00001 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return TD2UT(estimateJD, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
func LastMarsConjunction(jde float64) float64 {
|
||||||
|
return inclusiveLastPhaseEvent(jde, 0, marsConjunction)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NextMarsConjunction(jde float64) float64 {
|
||||||
|
return inclusiveNextPhaseEvent(jde, 0, marsConjunction)
|
||||||
|
}
|
||||||
|
|
||||||
|
func LastMarsOpposition(jde float64) float64 {
|
||||||
|
return inclusiveLastPhaseEvent(jde, 180, marsConjunction)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NextMarsOpposition(jde float64) float64 {
|
||||||
|
return inclusiveNextPhaseEvent(jde, 180, marsConjunction)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NextMarsEasternQuadrature(jde float64) float64 {
|
||||||
|
return inclusiveNextPhaseEvent(jde, 90, marsConjunction)
|
||||||
|
}
|
||||||
|
|
||||||
|
func LastMarsEasternQuadrature(jde float64) float64 {
|
||||||
|
return inclusiveLastPhaseEvent(jde, 90, marsConjunction)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NextMarsWesternQuadrature(jde float64) float64 {
|
||||||
|
return inclusiveNextPhaseEvent(jde, 270, marsConjunction)
|
||||||
|
}
|
||||||
|
|
||||||
|
func LastMarsWesternQuadrature(jde float64) float64 {
|
||||||
|
return inclusiveLastPhaseEvent(jde, 270, marsConjunction)
|
||||||
|
}
|
||||||
|
|
||||||
|
func marsRetrogradeAroundOpposition(oppositionJD float64, searchBeforeOpposition bool) float64 {
|
||||||
|
jde := oppositionJD
|
||||||
|
if searchBeforeOpposition {
|
||||||
|
jde -= 60
|
||||||
|
} else {
|
||||||
|
jde += 60
|
||||||
|
}
|
||||||
|
for {
|
||||||
|
currentRate := marsRADerivative(jde, 1.0/86400.0)
|
||||||
|
if math.Abs(currentRate) > 0.55 {
|
||||||
|
jde += 2
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
estimateJD := jde
|
||||||
|
for {
|
||||||
|
prevJD := estimateJD
|
||||||
|
rateValue := marsRADerivative(prevJD, 2.0/86400.0)
|
||||||
|
rateSlope := (marsRADerivative(prevJD+15.0/86400.0, 2.0/86400.0) - marsRADerivative(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 marsRADerivative(jd, 0.5/86400.0)
|
||||||
|
})
|
||||||
|
return TD2UT(bestJD, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
func marsOppositionFromBefore(oppositionJD float64) float64 {
|
||||||
|
return marsConjunctionFull(eventUTLastQueryTT(oppositionJD), 180, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
func marsOppositionFromAfter(oppositionJD float64) float64 {
|
||||||
|
return marsConjunctionFull(eventUTNextQueryTT(oppositionJD), 180, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
func stabilizeMarsStationNearQuery(jde, date float64, searchBeforeOpposition bool) float64 {
|
||||||
|
if math.Abs(eventUTQueryTTDelta(date, jde)) > exactEventTolerance {
|
||||||
|
return date
|
||||||
|
}
|
||||||
|
if searchBeforeOpposition {
|
||||||
|
stableOppositionJD := NextMarsOpposition(jde)
|
||||||
|
sameOppositionJD := marsOppositionFromAfter(stableOppositionJD)
|
||||||
|
return closestEventUTToQueryTT(jde, date, marsRetrogradeAroundOpposition(stableOppositionJD, true), marsRetrogradeAroundOpposition(sameOppositionJD, true))
|
||||||
|
}
|
||||||
|
stableOppositionJD := LastMarsOpposition(jde)
|
||||||
|
sameOppositionJD := marsOppositionFromBefore(stableOppositionJD)
|
||||||
|
return closestEventUTToQueryTT(jde, date, marsRetrogradeAroundOpposition(stableOppositionJD, false), marsRetrogradeAroundOpposition(sameOppositionJD, false))
|
||||||
|
}
|
||||||
|
|
||||||
|
func NextMarsRetrogradeToPrograde(jde float64) float64 {
|
||||||
|
lastOppositionJD := marsConjunctionFull(jde, 180, 0)
|
||||||
|
date := marsRetrogradeAroundOpposition(lastOppositionJD, false)
|
||||||
|
date = stabilizeMarsStationNearQuery(jde, date, false)
|
||||||
|
if sameEventUTQueryTT(date, jde) {
|
||||||
|
stableOppositionJD := LastMarsOpposition(jde)
|
||||||
|
stableDate := marsRetrogradeAroundOpposition(stableOppositionJD, false)
|
||||||
|
sameOppositionJD := marsOppositionFromBefore(stableOppositionJD)
|
||||||
|
return closestEventUTToQueryTT(jde, date, stableDate, marsRetrogradeAroundOpposition(sameOppositionJD, false))
|
||||||
|
}
|
||||||
|
if !eventUTQueryAfterOrEqual(date, jde) {
|
||||||
|
nextOppositionJD := marsConjunctionFull(jde, 180, 1)
|
||||||
|
return marsRetrogradeAroundOpposition(nextOppositionJD, false)
|
||||||
|
}
|
||||||
|
return date
|
||||||
|
}
|
||||||
|
|
||||||
|
func LastMarsRetrogradeToPrograde(jde float64) float64 {
|
||||||
|
lastOppositionJD := marsConjunctionFull(jde, 180, 0)
|
||||||
|
date := marsRetrogradeAroundOpposition(lastOppositionJD, false)
|
||||||
|
date = stabilizeMarsStationNearQuery(jde, date, false)
|
||||||
|
if sameEventUTQueryTT(date, jde) {
|
||||||
|
stableOppositionJD := LastMarsOpposition(jde)
|
||||||
|
stableDate := marsRetrogradeAroundOpposition(stableOppositionJD, false)
|
||||||
|
sameOppositionJD := marsOppositionFromBefore(stableOppositionJD)
|
||||||
|
return closestEventUTToQueryTT(jde, date, stableDate, marsRetrogradeAroundOpposition(sameOppositionJD, false))
|
||||||
|
}
|
||||||
|
if !eventUTQueryBeforeOrEqual(date, jde) {
|
||||||
|
previousOppositionJD := marsConjunctionFull(eventUTLastQueryTT(lastOppositionJD), 180, 0)
|
||||||
|
return marsRetrogradeAroundOpposition(previousOppositionJD, false)
|
||||||
|
}
|
||||||
|
return date
|
||||||
|
}
|
||||||
|
|
||||||
|
func NextMarsProgradeToRetrograde(jde float64) float64 {
|
||||||
|
nextOppositionJD := marsConjunctionFull(jde, 180, 1)
|
||||||
|
date := marsRetrogradeAroundOpposition(nextOppositionJD, true)
|
||||||
|
date = stabilizeMarsStationNearQuery(jde, date, true)
|
||||||
|
if sameEventUTQueryTT(date, jde) {
|
||||||
|
stableOppositionJD := NextMarsOpposition(jde)
|
||||||
|
stableDate := marsRetrogradeAroundOpposition(stableOppositionJD, true)
|
||||||
|
sameOppositionJD := marsOppositionFromAfter(stableOppositionJD)
|
||||||
|
return closestEventUTToQueryTT(jde, date, stableDate, marsRetrogradeAroundOpposition(sameOppositionJD, true))
|
||||||
|
}
|
||||||
|
if !eventUTQueryAfterOrEqual(date, jde) {
|
||||||
|
followingOppositionJD := marsConjunctionFull(eventUTNextQueryTT(nextOppositionJD), 180, 1)
|
||||||
|
return marsRetrogradeAroundOpposition(followingOppositionJD, true)
|
||||||
|
}
|
||||||
|
return date
|
||||||
|
}
|
||||||
|
|
||||||
|
func LastMarsProgradeToRetrograde(jde float64) float64 {
|
||||||
|
nextOppositionJD := marsConjunctionFull(jde, 180, 1)
|
||||||
|
date := marsRetrogradeAroundOpposition(nextOppositionJD, true)
|
||||||
|
date = stabilizeMarsStationNearQuery(jde, date, true)
|
||||||
|
if sameEventUTQueryTT(date, jde) {
|
||||||
|
stableOppositionJD := NextMarsOpposition(jde)
|
||||||
|
stableDate := marsRetrogradeAroundOpposition(stableOppositionJD, true)
|
||||||
|
sameOppositionJD := marsOppositionFromAfter(stableOppositionJD)
|
||||||
|
return closestEventUTToQueryTT(jde, date, stableDate, marsRetrogradeAroundOpposition(sameOppositionJD, true))
|
||||||
|
}
|
||||||
|
if !eventUTQueryBeforeOrEqual(date, jde) {
|
||||||
|
lastOppositionJD := marsConjunctionFull(jde, 180, 0)
|
||||||
|
return marsRetrogradeAroundOpposition(lastOppositionJD, true)
|
||||||
|
}
|
||||||
|
return date
|
||||||
|
}
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
package basic
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestMars(t *testing.T) {
|
|
||||||
jde := GetNowJDE() - 6000
|
|
||||||
/*
|
|
||||||
fmt.Println(JDE2Date(VenusCulminationTime(jde, 115, 8)))
|
|
||||||
fmt.Println(JDE2Date(VenusRiseTime(jde, 115, 23, 8, 0, 0)))
|
|
||||||
fmt.Println(JDE2Date(VenusDownTime(jde, 115, 23, 8, 0, 0)))
|
|
||||||
fmt.Println("----------------")
|
|
||||||
*/
|
|
||||||
//LastVenusConjunction(2.4596600340162036e+06)
|
|
||||||
//fmt.Println(2.4590359532407406e+06, JDE2Date(2.4590359532407406e+06), JDE2Date(NextVenusRetrograde(2.4590359532407406e+06)))
|
|
||||||
for i := 0.00; i < 1; i++ {
|
|
||||||
fmt.Println(jde+i*740, JDE2Date(jde+i*740), JDE2Date(LastMarsProgradeToRetrograde(jde+i*740)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+99
-516
@@ -7,135 +7,105 @@ import (
|
|||||||
. "b612.me/astro/tools"
|
. "b612.me/astro/tools"
|
||||||
)
|
)
|
||||||
|
|
||||||
func MercuryL(JD float64) float64 {
|
func MercuryL(jd float64) float64 {
|
||||||
return planet.WherePlanet(1, 0, JD)
|
return planet.WherePlanet(1, 0, jd)
|
||||||
}
|
}
|
||||||
|
|
||||||
func MercuryB(JD float64) float64 {
|
func MercuryB(jd float64) float64 {
|
||||||
return planet.WherePlanet(1, 1, JD)
|
return planet.WherePlanet(1, 1, jd)
|
||||||
}
|
}
|
||||||
func MercuryR(JD float64) float64 {
|
func MercuryR(jd float64) float64 {
|
||||||
return planet.WherePlanet(1, 2, JD)
|
return planet.WherePlanet(1, 2, jd)
|
||||||
}
|
}
|
||||||
func AMercuryX(JD float64) float64 {
|
func AMercuryX(jd float64) float64 {
|
||||||
l := MercuryL(JD)
|
l := MercuryL(jd)
|
||||||
b := MercuryB(JD)
|
b := MercuryB(jd)
|
||||||
r := MercuryR(JD)
|
r := MercuryR(jd)
|
||||||
el := planet.WherePlanet(-1, 0, JD)
|
el := planet.WherePlanet(-1, 0, jd)
|
||||||
eb := planet.WherePlanet(-1, 1, JD)
|
eb := planet.WherePlanet(-1, 1, jd)
|
||||||
er := planet.WherePlanet(-1, 2, JD)
|
er := planet.WherePlanet(-1, 2, jd)
|
||||||
x := r*Cos(b)*Cos(l) - er*Cos(eb)*Cos(el)
|
x := r*Cos(b)*Cos(l) - er*Cos(eb)*Cos(el)
|
||||||
return x
|
return x
|
||||||
}
|
}
|
||||||
|
|
||||||
func AMercuryY(JD float64) float64 {
|
func AMercuryY(jd float64) float64 {
|
||||||
|
|
||||||
l := MercuryL(JD)
|
l := MercuryL(jd)
|
||||||
b := MercuryB(JD)
|
b := MercuryB(jd)
|
||||||
r := MercuryR(JD)
|
r := MercuryR(jd)
|
||||||
el := planet.WherePlanet(-1, 0, JD)
|
el := planet.WherePlanet(-1, 0, jd)
|
||||||
eb := planet.WherePlanet(-1, 1, JD)
|
eb := planet.WherePlanet(-1, 1, jd)
|
||||||
er := planet.WherePlanet(-1, 2, JD)
|
er := planet.WherePlanet(-1, 2, jd)
|
||||||
y := r*Cos(b)*Sin(l) - er*Cos(eb)*Sin(el)
|
y := r*Cos(b)*Sin(l) - er*Cos(eb)*Sin(el)
|
||||||
return y
|
return y
|
||||||
}
|
}
|
||||||
func AMercuryZ(JD float64) float64 {
|
func AMercuryZ(jd float64) float64 {
|
||||||
//l := MercuryL(JD)
|
//l := MercuryL(jd)
|
||||||
b := MercuryB(JD)
|
b := MercuryB(jd)
|
||||||
r := MercuryR(JD)
|
r := MercuryR(jd)
|
||||||
// el := planet.WherePlanet(-1, 0, JD)
|
// el := planet.WherePlanet(-1, 0, jd)
|
||||||
eb := planet.WherePlanet(-1, 1, JD)
|
eb := planet.WherePlanet(-1, 1, jd)
|
||||||
er := planet.WherePlanet(-1, 2, JD)
|
er := planet.WherePlanet(-1, 2, jd)
|
||||||
z := r*Sin(b) - er*Sin(eb)
|
z := r*Sin(b) - er*Sin(eb)
|
||||||
return z
|
return z
|
||||||
}
|
}
|
||||||
|
|
||||||
func AMercuryXYZ(JD float64) (float64, float64, float64) {
|
func AMercuryXYZ(jd float64) (float64, float64, float64) {
|
||||||
l := MercuryL(JD)
|
l := MercuryL(jd)
|
||||||
b := MercuryB(JD)
|
b := MercuryB(jd)
|
||||||
r := MercuryR(JD)
|
r := MercuryR(jd)
|
||||||
el := planet.WherePlanet(-1, 0, JD)
|
el := planet.WherePlanet(-1, 0, jd)
|
||||||
eb := planet.WherePlanet(-1, 1, JD)
|
eb := planet.WherePlanet(-1, 1, jd)
|
||||||
er := planet.WherePlanet(-1, 2, JD)
|
er := planet.WherePlanet(-1, 2, jd)
|
||||||
x := r*Cos(b)*Cos(l) - er*Cos(eb)*Cos(el)
|
x := r*Cos(b)*Cos(l) - er*Cos(eb)*Cos(el)
|
||||||
y := r*Cos(b)*Sin(l) - er*Cos(eb)*Sin(el)
|
y := r*Cos(b)*Sin(l) - er*Cos(eb)*Sin(el)
|
||||||
z := r*Sin(b) - er*Sin(eb)
|
z := r*Sin(b) - er*Sin(eb)
|
||||||
return x, y, z
|
return x, y, z
|
||||||
}
|
}
|
||||||
|
|
||||||
func MercuryApparentRa(JD float64) float64 {
|
func MercuryApparentRa(jd float64) float64 {
|
||||||
lo, bo := MercuryApparentLoBo(JD)
|
lo, bo := MercuryApparentLoBo(jd)
|
||||||
return LoToRa(JD, lo, bo)
|
return LoToRa(jd, lo, bo)
|
||||||
}
|
}
|
||||||
func MercuryApparentDec(JD float64) float64 {
|
func MercuryApparentDec(jd float64) float64 {
|
||||||
lo, bo := MercuryApparentLoBo(JD)
|
lo, bo := MercuryApparentLoBo(jd)
|
||||||
sita := Sita(JD)
|
eps := TrueObliquity(jd)
|
||||||
dec := ArcSin(Sin(bo)*Cos(sita) + Cos(bo)*Sin(sita)*Sin(lo))
|
dec := ArcSin(Sin(bo)*Cos(eps) + Cos(bo)*Sin(eps)*Sin(lo))
|
||||||
return dec
|
return dec
|
||||||
}
|
}
|
||||||
|
|
||||||
func MercuryApparentRaDec(JD float64) (float64, float64) {
|
func MercuryApparentRaDec(jd float64) (float64, float64) {
|
||||||
lo, bo := MercuryApparentLoBo(JD)
|
lo, bo := MercuryApparentLoBo(jd)
|
||||||
return LoBoToRaDec(JD, lo, bo)
|
return LoBoToRaDec(jd, lo, bo)
|
||||||
}
|
}
|
||||||
|
|
||||||
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 += HJZD(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+=HJZD(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) + HJZD(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 {
|
||||||
AwaySun := MercuryR(JD)
|
sunDistance := MercuryR(jd)
|
||||||
AwayEarth := EarthMercuryAway(JD)
|
earthDistance := EarthMercuryAway(jd)
|
||||||
Away := planet.WherePlanet(-1, 2, JD)
|
earthSunDistance := planet.WherePlanet(-1, 2, jd)
|
||||||
i := (AwaySun*AwaySun + AwayEarth*AwayEarth - Away*Away) / (2 * AwaySun * AwayEarth)
|
i := (sunDistance*sunDistance + earthDistance*earthDistance - earthSunDistance*earthSunDistance) / (2 * sunDistance * earthDistance)
|
||||||
i = ArcCos(i)
|
i = ArcCos(i)
|
||||||
Mag := -0.42 + 5*math.Log10(AwaySun*AwayEarth) + 0.0380*i - 0.000273*i*i + 0.000002*i*i*i
|
mag := -0.42 + 5*math.Log10(sunDistance*earthDistance) + 0.0380*i - 0.000273*i*i + 0.000002*i*i*i
|
||||||
return FloatRound(Mag, 2)
|
return FloatRound(mag, 2)
|
||||||
}
|
}
|
||||||
|
|
||||||
func MercuryHeight(jde, lon, lat, timezone float64) float64 {
|
func MercuryHeight(jde, lon, lat, timezone float64) float64 {
|
||||||
@@ -145,10 +115,10 @@ func MercuryHeight(jde, lon, lat, timezone float64) float64 {
|
|||||||
ra, dec := MercuryApparentRaDec(TD2UT(utcJde, true))
|
ra, dec := MercuryApparentRaDec(TD2UT(utcJde, true))
|
||||||
st := Limit360(ApparentSiderealTime(utcJde)*15 + lon)
|
st := Limit360(ApparentSiderealTime(utcJde)*15 + lon)
|
||||||
// 计算时角
|
// 计算时角
|
||||||
H := Limit360(st - ra)
|
hourAngle := Limit360(st - ra)
|
||||||
// 高度角、时角与天球座标三角转换公式
|
// 高度角、时角与天球座标三角转换公式
|
||||||
// sin(h)=sin(lat)*sin(dec)+cos(dec)*cos(lat)*cos(H)
|
// sin(h)=sin(lat)*sin(dec)+cos(dec)*cos(lat)*cos(hourAngle)
|
||||||
sinHeight := Sin(lat)*Sin(dec) + Cos(dec)*Cos(lat)*Cos(H)
|
sinHeight := Sin(lat)*Sin(dec) + Cos(dec)*Cos(lat)*Cos(hourAngle)
|
||||||
return ArcSin(sinHeight)
|
return ArcSin(sinHeight)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -159,450 +129,63 @@ func MercuryAzimuth(jde, lon, lat, timezone float64) float64 {
|
|||||||
ra, dec := MercuryApparentRaDec(TD2UT(utcJde, true))
|
ra, dec := MercuryApparentRaDec(TD2UT(utcJde, true))
|
||||||
st := Limit360(ApparentSiderealTime(utcJde)*15 + lon)
|
st := Limit360(ApparentSiderealTime(utcJde)*15 + lon)
|
||||||
// 计算时角
|
// 计算时角
|
||||||
H := Limit360(st - ra)
|
hourAngle := Limit360(st - ra)
|
||||||
// 三角转换公式
|
// 三角转换公式
|
||||||
tanAzimuth := Sin(H) / (Cos(H)*Sin(lat) - Tan(dec)*Cos(lat))
|
tanAzimuth := Sin(hourAngle) / (Cos(hourAngle)*Sin(lat) - Tan(dec)*Cos(lat))
|
||||||
Azimuth := ArcTan(tanAzimuth)
|
azimuth := ArcTan(tanAzimuth)
|
||||||
if Azimuth < 0 {
|
if azimuth < 0 {
|
||||||
if H/15 < 12 {
|
if hourAngle/15 < 12 {
|
||||||
return Azimuth + 360
|
return azimuth + 360
|
||||||
}
|
}
|
||||||
return Azimuth + 180
|
return azimuth + 180
|
||||||
}
|
}
|
||||||
if H/15 < 12 {
|
if hourAngle/15 < 12 {
|
||||||
return Azimuth + 180
|
return azimuth + 180
|
||||||
}
|
}
|
||||||
return Azimuth
|
return azimuth
|
||||||
}
|
}
|
||||||
|
|
||||||
func MercuryHourAngle(JD, Lon, TZ float64) float64 {
|
func MercuryHourAngle(jd, lon, timezone float64) float64 {
|
||||||
startime := Limit360(ApparentSiderealTime(JD-TZ/24)*15 + Lon)
|
siderealLongitude := Limit360(ApparentSiderealTime(jd-timezone/24)*15 + lon)
|
||||||
timeangle := startime - MercuryApparentRa(TD2UT(JD-TZ/24.0, true))
|
hourAngle := siderealLongitude - MercuryApparentRa(TD2UT(jd-timezone/24.0, true))
|
||||||
if timeangle < 0 {
|
if hourAngle < 0 {
|
||||||
timeangle += 360
|
hourAngle += 360
|
||||||
}
|
}
|
||||||
return timeangle
|
return hourAngle
|
||||||
}
|
}
|
||||||
|
|
||||||
func MercuryCulminationTime(jde, lon, timezone float64) float64 {
|
func MercuryCulminationTime(jde, lon, timezone float64) float64 {
|
||||||
//jde 世界时,非力学时,当地时区 0时,无需转换力学时
|
//jde 世界时,非力学时,当地时区 0时,无需转换力学时
|
||||||
//ra,dec 瞬时天球座标,非J2000等时间天球坐标
|
//ra,dec 瞬时天球座标,非J2000等时间天球坐标
|
||||||
jde = math.Floor(jde) + 0.5
|
jde = math.Floor(jde) + 0.5
|
||||||
JD1 := jde + Limit360(360-MercuryHourAngle(jde, lon, timezone))/15.0/24.0*0.99726851851851851851
|
estimateJD := jde + Limit360(360-MercuryHourAngle(jde, lon, timezone))/15.0/24.0*0.99726851851851851851
|
||||||
limitHA := func(jde, lon, timezone float64) float64 {
|
normalizedHourAngle := func(jde, lon, timezone float64) float64 {
|
||||||
ha := MercuryHourAngle(jde, lon, timezone)
|
currentHourAngle := MercuryHourAngle(jde, lon, timezone)
|
||||||
if ha < 180 {
|
if currentHourAngle < 180 {
|
||||||
ha += 360
|
currentHourAngle += 360
|
||||||
}
|
}
|
||||||
return ha
|
return currentHourAngle
|
||||||
}
|
}
|
||||||
for {
|
for {
|
||||||
JD0 := JD1
|
prevJD := estimateJD
|
||||||
stDegree := limitHA(JD0, lon, timezone) - 360
|
hourAngleDelta := normalizedHourAngle(prevJD, lon, timezone) - 360
|
||||||
stDegreep := (limitHA(JD0+0.000005, lon, timezone) - limitHA(JD0-0.000005, lon, timezone)) / 0.00001
|
hourAngleSlope := (normalizedHourAngle(prevJD+0.000005, lon, timezone) - normalizedHourAngle(prevJD-0.000005, lon, timezone)) / 0.00001
|
||||||
JD1 = JD0 - stDegree/stDegreep
|
estimateJD = prevJD - hourAngleDelta/hourAngleSlope
|
||||||
if math.Abs(JD1-JD0) <= 0.00001 {
|
if math.Abs(estimateJD-prevJD) <= 0.00001 {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return JD1
|
return estimateJD
|
||||||
}
|
}
|
||||||
|
|
||||||
func MercuryRiseTime(JD, Lon, Lat, TZ, ZS, HEI float64) float64 {
|
func MercuryRiseTime(jd, lon, lat, timezone, aeroCorrection, observerHeight float64) (float64, error) {
|
||||||
return mercuryRiseDown(JD, Lon, Lat, TZ, ZS, HEI, true)
|
return mercuryRiseDown(jd, lon, lat, timezone, aeroCorrection, observerHeight, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
func MercuryDownTime(JD, Lon, Lat, TZ, ZS, HEI float64) float64 {
|
func MercurySetTime(jd, lon, lat, timezone, aeroCorrection, observerHeight float64) (float64, error) {
|
||||||
return mercuryRiseDown(JD, Lon, Lat, TZ, ZS, HEI, false)
|
return mercuryRiseDown(jd, lon, lat, timezone, aeroCorrection, observerHeight, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
func mercuryRiseDown(JD, Lon, Lat, TZ, ZS, HEI float64, isRise bool) float64 {
|
func mercuryRiseDown(jd, lon, lat, timezone, aeroCorrection, observerHeight float64, isRise bool) (float64, error) {
|
||||||
var An float64
|
return planetRiseDown(jd, lon, lat, timezone, aeroCorrection, observerHeight, isRise, MercuryCulminationTime, MercuryHeight, MercuryApparentDec)
|
||||||
JD = math.Floor(JD) + 0.5
|
|
||||||
ntz := math.Round(Lon / 15)
|
|
||||||
if ZS != 0 {
|
|
||||||
An = -0.8333
|
|
||||||
}
|
|
||||||
An = An - HeightDegreeByLat(HEI, Lat)
|
|
||||||
tztime := MercuryCulminationTime(JD, Lon, ntz)
|
|
||||||
if MercuryHeight(tztime, Lon, Lat, ntz) < An {
|
|
||||||
return -2 //极夜
|
|
||||||
}
|
|
||||||
if MercuryHeight(tztime-0.5, Lon, Lat, ntz) > An {
|
|
||||||
return -1 //极昼
|
|
||||||
}
|
|
||||||
dec := HSunApparentDec(TD2UT(tztime-ntz/24, true))
|
|
||||||
//(sin(ho)-sin(φ)*sin(δ2))/(cos(φ)*cos(δ2))
|
|
||||||
tmp := (Sin(An) - Sin(dec)*Sin(Lat)) / (Cos(dec) * Cos(Lat))
|
|
||||||
var rise float64
|
|
||||||
if math.Abs(tmp) <= 1 {
|
|
||||||
rzsc := ArcCos(tmp) / 15
|
|
||||||
if isRise {
|
|
||||||
rise = tztime - rzsc/24 - 25.0/24.0/60.0
|
|
||||||
} else {
|
|
||||||
rise = tztime + rzsc/24 - 25.0/24.0/60.0
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
rise = tztime
|
|
||||||
i := 0
|
|
||||||
//TODO:使用二分法计算
|
|
||||||
for MercuryHeight(rise, Lon, Lat, ntz) > An {
|
|
||||||
i++
|
|
||||||
if isRise {
|
|
||||||
rise -= 15.0 / 60.0 / 24.0
|
|
||||||
} else {
|
|
||||||
rise += 15.0 / 60.0 / 24.0
|
|
||||||
}
|
|
||||||
if i > 48 {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
JD1 := rise
|
|
||||||
for {
|
|
||||||
JD0 := JD1
|
|
||||||
stDegree := MercuryHeight(JD0, Lon, Lat, ntz) - An
|
|
||||||
stDegreep := (MercuryHeight(JD0+0.000005, Lon, Lat, ntz) - MercuryHeight(JD0-0.000005, Lon, Lat, ntz)) / 0.00001
|
|
||||||
JD1 = JD0 - stDegree/stDegreep
|
|
||||||
if math.Abs(JD1-JD0) <= 0.00001 {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return JD1 - ntz/24 + TZ/24
|
|
||||||
}
|
|
||||||
|
|
||||||
// Pos
|
|
||||||
|
|
||||||
const MERCURY_S_PERIOD = 1 / ((1 / 87.9691) - (1 / 365.256363004))
|
|
||||||
|
|
||||||
func mercuryConjunction(jde float64, next uint8) float64 {
|
|
||||||
//0=last 1=next
|
|
||||||
decSub := func(jde float64) float64 {
|
|
||||||
sub := Limit360(MercuryApparentLo(jde) - HSunApparentLo(jde))
|
|
||||||
if sub > 180 {
|
|
||||||
sub -= 360
|
|
||||||
}
|
|
||||||
if sub < -180 {
|
|
||||||
sub += 360
|
|
||||||
}
|
|
||||||
return sub
|
|
||||||
}
|
|
||||||
nowSub := decSub(jde)
|
|
||||||
// pos 大于0:远离太阳 小于0:靠近太阳
|
|
||||||
pos := math.Abs(decSub(jde+1/86400.0)) - math.Abs(nowSub)
|
|
||||||
if pos >= 0 && next == 1 && nowSub > 0 {
|
|
||||||
jde += MERCURY_S_PERIOD/8.0 + 2
|
|
||||||
}
|
|
||||||
if pos >= 0 && next == 1 && nowSub < 0 {
|
|
||||||
jde += MERCURY_S_PERIOD/6.0 + 2
|
|
||||||
}
|
|
||||||
if pos <= 0 && next == 0 && nowSub < 0 {
|
|
||||||
jde -= MERCURY_S_PERIOD/8.0 + 2
|
|
||||||
}
|
|
||||||
if pos <= 0 && next == 0 && nowSub > 0 {
|
|
||||||
jde -= MERCURY_S_PERIOD/6.0 + 2
|
|
||||||
}
|
|
||||||
for {
|
|
||||||
nowSub := decSub(jde)
|
|
||||||
pos := math.Abs(decSub(jde+1/86400.0)) - math.Abs(nowSub)
|
|
||||||
if math.Abs(nowSub) > 12 || (pos > 0 && next == 1) || (pos < 0 && next == 0) {
|
|
||||||
if next == 1 {
|
|
||||||
jde += 2
|
|
||||||
} else {
|
|
||||||
jde -= 2
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
break
|
|
||||||
}
|
|
||||||
JD1 := jde
|
|
||||||
for {
|
|
||||||
JD0 := JD1
|
|
||||||
stDegree := decSub(JD0)
|
|
||||||
stDegreep := (decSub(JD0+0.000005) - decSub(JD0-0.000005)) / 0.00001
|
|
||||||
JD1 = JD0 - stDegree/stDegreep
|
|
||||||
if math.Abs(JD1-JD0) <= 0.00001 {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return TD2UT(JD1, false)
|
|
||||||
}
|
|
||||||
|
|
||||||
func LastMercuryConjunction(jde float64) float64 {
|
|
||||||
return mercuryConjunction(jde, 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
func NextMercuryConjunction(jde float64) float64 {
|
|
||||||
return mercuryConjunction(jde, 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
func NextMercuryInferiorConjunction(jde float64) float64 {
|
|
||||||
date := NextMercuryConjunction(jde)
|
|
||||||
if EarthMercuryAway(date) > EarthAway(date) {
|
|
||||||
return NextMercuryConjunction(date + 2)
|
|
||||||
}
|
|
||||||
return date
|
|
||||||
}
|
|
||||||
|
|
||||||
func NextMercurySuperiorConjunction(jde float64) float64 {
|
|
||||||
date := NextMercuryConjunction(jde)
|
|
||||||
if EarthMercuryAway(date) < EarthAway(date) {
|
|
||||||
return NextMercuryConjunction(date + 2)
|
|
||||||
}
|
|
||||||
return date
|
|
||||||
}
|
|
||||||
|
|
||||||
func LastMercuryInferiorConjunction(jde float64) float64 {
|
|
||||||
date := LastMercuryConjunction(jde)
|
|
||||||
if EarthMercuryAway(date) > EarthAway(date) {
|
|
||||||
return LastMercuryConjunction(date - 2)
|
|
||||||
}
|
|
||||||
return date
|
|
||||||
}
|
|
||||||
|
|
||||||
func LastMercurySuperiorConjunction(jde float64) float64 {
|
|
||||||
date := LastMercuryConjunction(jde)
|
|
||||||
if EarthMercuryAway(date) < EarthAway(date) {
|
|
||||||
return LastMercuryConjunction(date - 2)
|
|
||||||
}
|
|
||||||
return date
|
|
||||||
}
|
|
||||||
|
|
||||||
func mercuryRetrograde(jde float64) float64 {
|
|
||||||
//0=last 1=next
|
|
||||||
decSunSub := func(jde float64) float64 {
|
|
||||||
sub := Limit360(MercuryApparentRa(jde) - SunApparentRa(jde))
|
|
||||||
if sub > 180 {
|
|
||||||
sub -= 360
|
|
||||||
}
|
|
||||||
if sub < -180 {
|
|
||||||
sub += 360
|
|
||||||
}
|
|
||||||
return sub
|
|
||||||
}
|
|
||||||
decSub := func(jde float64, val float64) float64 {
|
|
||||||
sub := MercuryApparentRa(jde+val) - MercuryApparentRa(jde-val)
|
|
||||||
if sub > 180 {
|
|
||||||
sub -= 360
|
|
||||||
}
|
|
||||||
if sub < -180 {
|
|
||||||
sub += 360
|
|
||||||
}
|
|
||||||
return sub / (2 * val)
|
|
||||||
}
|
|
||||||
lastHe := LastMercuryConjunction(jde)
|
|
||||||
nextHe := NextMercuryConjunction(jde)
|
|
||||||
nowSub := decSunSub(jde)
|
|
||||||
if nowSub > 0 {
|
|
||||||
jde = lastHe + ((nextHe - lastHe) / 5.0 * 3.5)
|
|
||||||
} else {
|
|
||||||
jde = lastHe + ((nextHe - lastHe) / 5.5)
|
|
||||||
}
|
|
||||||
for {
|
|
||||||
nowSub := decSub(jde, 1.0/86400.0)
|
|
||||||
if math.Abs(nowSub) > 0.55 {
|
|
||||||
jde += 2
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
break
|
|
||||||
}
|
|
||||||
JD1 := jde
|
|
||||||
for {
|
|
||||||
JD0 := JD1
|
|
||||||
stDegree := decSub(JD0, 2.0/86400.0)
|
|
||||||
stDegreep := (decSub(JD0+15.0/86400.0, 2.0/86400.0) - decSub(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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
JD1 = JD1 - 15.0/86400.0
|
|
||||||
min := JD1
|
|
||||||
minRa := 100.0
|
|
||||||
for i := 0.0; i < 60.0; i++ {
|
|
||||||
tmp := decSub(JD1+i*0.5/86400.0, 0.5/86400.0)
|
|
||||||
if math.Abs(tmp) < math.Abs(minRa) {
|
|
||||||
minRa = tmp
|
|
||||||
min = JD1 + i*0.5/86400.0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
//fmt.Println((min - lastHe) / (nextHe - lastHe))
|
|
||||||
return TD2UT(min, false)
|
|
||||||
}
|
|
||||||
|
|
||||||
func NextMercuryRetrograde(jde float64) float64 {
|
|
||||||
date := mercuryRetrograde(jde)
|
|
||||||
if date < jde {
|
|
||||||
nextHe := NextMercuryConjunction(jde)
|
|
||||||
return mercuryRetrograde(nextHe + 2)
|
|
||||||
}
|
|
||||||
return date
|
|
||||||
}
|
|
||||||
|
|
||||||
func LastMercuryRetrograde(jde float64) float64 {
|
|
||||||
lastHe := LastMercuryConjunction(jde)
|
|
||||||
date := mercuryRetrograde(lastHe + 2)
|
|
||||||
if date > jde {
|
|
||||||
lastLastHe := LastMercuryConjunction(lastHe - 2)
|
|
||||||
return mercuryRetrograde(lastLastHe + 2)
|
|
||||||
}
|
|
||||||
return date
|
|
||||||
}
|
|
||||||
|
|
||||||
func NextMercuryProgradeToRetrograde(jde float64) float64 {
|
|
||||||
date := NextMercuryRetrograde(jde)
|
|
||||||
sub := Limit360(MercuryApparentRa(date) - SunApparentRa(date))
|
|
||||||
if sub > 180 {
|
|
||||||
return NextMercuryRetrograde(date + MERCURY_S_PERIOD/2)
|
|
||||||
}
|
|
||||||
return date
|
|
||||||
}
|
|
||||||
|
|
||||||
func NextMercuryRetrogradeToPrograde(jde float64) float64 {
|
|
||||||
date := NextMercuryRetrograde(jde)
|
|
||||||
sub := Limit360(MercuryApparentRa(date) - SunApparentRa(date))
|
|
||||||
if sub < 180 {
|
|
||||||
return NextMercuryRetrograde(date + 12)
|
|
||||||
}
|
|
||||||
return date
|
|
||||||
}
|
|
||||||
|
|
||||||
func LastMercuryProgradeToRetrograde(jde float64) float64 {
|
|
||||||
date := LastMercuryRetrograde(jde)
|
|
||||||
sub := Limit360(MercuryApparentRa(date) - SunApparentRa(date))
|
|
||||||
if sub > 180 {
|
|
||||||
return LastMercuryRetrograde(date - 12)
|
|
||||||
}
|
|
||||||
return date
|
|
||||||
}
|
|
||||||
|
|
||||||
func LastMercuryRetrogradeToPrograde(jde float64) float64 {
|
|
||||||
date := LastMercuryRetrograde(jde)
|
|
||||||
sub := Limit360(MercuryApparentRa(date) - SunApparentRa(date))
|
|
||||||
if sub < 180 {
|
|
||||||
return LastMercuryRetrograde(date - MERCURY_S_PERIOD/2)
|
|
||||||
}
|
|
||||||
return date
|
|
||||||
}
|
|
||||||
|
|
||||||
func MercurySunElongation(jde float64) float64 {
|
|
||||||
lo1, bo1 := MercuryApparentLoBo(jde)
|
|
||||||
lo2 := SunApparentLo(jde)
|
|
||||||
bo2 := HSunTrueBo(jde)
|
|
||||||
return StarAngularSeparation(lo1, bo1, lo2, bo2)
|
|
||||||
}
|
|
||||||
func mercuryGreatestElongation(jde float64) float64 {
|
|
||||||
decSunSub := func(jde float64) float64 {
|
|
||||||
sub := Limit360(MercuryApparentRa(jde) - SunApparentRa(jde))
|
|
||||||
if sub > 180 {
|
|
||||||
sub -= 360
|
|
||||||
}
|
|
||||||
if sub < -180 {
|
|
||||||
sub += 360
|
|
||||||
}
|
|
||||||
return sub
|
|
||||||
}
|
|
||||||
decSub := func(jde float64, val float64) float64 {
|
|
||||||
sub := MercurySunElongation(jde+val) - MercurySunElongation(jde-val)
|
|
||||||
if sub > 180 {
|
|
||||||
sub -= 360
|
|
||||||
}
|
|
||||||
if sub < -180 {
|
|
||||||
sub += 360
|
|
||||||
}
|
|
||||||
return sub / (2 * val)
|
|
||||||
}
|
|
||||||
lastHe := LastMercuryConjunction(jde)
|
|
||||||
nextHe := NextMercuryConjunction(jde)
|
|
||||||
nowSub := decSunSub(jde)
|
|
||||||
if nowSub > 0 {
|
|
||||||
jde = lastHe + ((nextHe - lastHe) / 5.0 * 2.0)
|
|
||||||
} else {
|
|
||||||
jde = lastHe + ((nextHe - lastHe) / 6.0)
|
|
||||||
}
|
|
||||||
for {
|
|
||||||
nowSub := decSub(jde, 1.0/86400.0)
|
|
||||||
if math.Abs(nowSub) > 0.4 {
|
|
||||||
jde += 2
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
break
|
|
||||||
}
|
|
||||||
JD1 := jde
|
|
||||||
for {
|
|
||||||
JD0 := JD1
|
|
||||||
stDegree := decSub(JD0, 2.0/86400.0)
|
|
||||||
stDegreep := (decSub(JD0+15.0/86400.0, 2.0/86400.0) - decSub(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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
JD1 = JD1 - 15.0/86400.0
|
|
||||||
min := JD1
|
|
||||||
minRa := 100.0
|
|
||||||
for i := 0.0; i < 60.0; i++ {
|
|
||||||
tmp := decSub(JD1+i*0.5/86400.0, 0.5/86400.0)
|
|
||||||
if math.Abs(tmp) < math.Abs(minRa) {
|
|
||||||
minRa = tmp
|
|
||||||
min = JD1 + i*0.5/86400.0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
//fmt.Println((min - lastHe) / (nextHe - lastHe))
|
|
||||||
return TD2UT(min, false)
|
|
||||||
}
|
|
||||||
|
|
||||||
func NextMercuryGreatestElongation(jde float64) float64 {
|
|
||||||
date := mercuryGreatestElongation(jde)
|
|
||||||
if date < jde {
|
|
||||||
nextHe := NextMercuryConjunction(jde)
|
|
||||||
return mercuryGreatestElongation(nextHe + 2)
|
|
||||||
}
|
|
||||||
return date
|
|
||||||
}
|
|
||||||
|
|
||||||
func LastMercuryGreatestElongation(jde float64) float64 {
|
|
||||||
lastHe := LastMercuryConjunction(jde)
|
|
||||||
date := mercuryGreatestElongation(lastHe + 2)
|
|
||||||
if date > jde {
|
|
||||||
lastLastHe := LastMercuryConjunction(lastHe - 2)
|
|
||||||
return mercuryGreatestElongation(lastLastHe + 2)
|
|
||||||
}
|
|
||||||
return date
|
|
||||||
}
|
|
||||||
|
|
||||||
func NextMercuryGreatestElongationEast(jde float64) float64 {
|
|
||||||
date := NextMercuryGreatestElongation(jde)
|
|
||||||
sub := Limit360(MercuryApparentRa(date) - SunApparentRa(date))
|
|
||||||
if sub > 180 {
|
|
||||||
return NextMercuryGreatestElongation(date + 1)
|
|
||||||
}
|
|
||||||
return date
|
|
||||||
}
|
|
||||||
|
|
||||||
func NextMercuryGreatestElongationWest(jde float64) float64 {
|
|
||||||
date := NextMercuryGreatestElongation(jde)
|
|
||||||
sub := Limit360(MercuryApparentRa(date) - SunApparentRa(date))
|
|
||||||
if sub < 180 {
|
|
||||||
return NextMercuryGreatestElongation(date + 1)
|
|
||||||
}
|
|
||||||
return date
|
|
||||||
}
|
|
||||||
|
|
||||||
func LastMercuryGreatestElongationEast(jde float64) float64 {
|
|
||||||
date := LastMercuryGreatestElongation(jde)
|
|
||||||
sub := Limit360(MercuryApparentRa(date) - SunApparentRa(date))
|
|
||||||
if sub > 180 {
|
|
||||||
return LastMercuryGreatestElongation(date - 1)
|
|
||||||
}
|
|
||||||
return date
|
|
||||||
}
|
|
||||||
|
|
||||||
func LastMercuryGreatestElongationWest(jde float64) float64 {
|
|
||||||
date := LastMercuryGreatestElongation(jde)
|
|
||||||
sub := Limit360(MercuryApparentRa(date) - SunApparentRa(date))
|
|
||||||
if sub < 180 {
|
|
||||||
return LastMercuryGreatestElongation(date - 1)
|
|
||||||
}
|
|
||||||
return date
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,51 @@
|
|||||||
|
package basic
|
||||||
|
|
||||||
|
import "testing"
|
||||||
|
|
||||||
|
func BenchmarkMercuryConjunctionFamily(b *testing.B) {
|
||||||
|
cases := mercuryEventCases()[:6]
|
||||||
|
samples := mercuryEventSamples()
|
||||||
|
var sink float64
|
||||||
|
b.ReportAllocs()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
for _, sample := range samples {
|
||||||
|
jd := mercuryEventSampleTTJD(sample)
|
||||||
|
for _, event := range cases {
|
||||||
|
sink += event.fn(jd)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ = sink
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkMercuryRetrogradeFamily(b *testing.B) {
|
||||||
|
cases := mercuryEventCases()[6:12]
|
||||||
|
samples := mercuryEventSamples()
|
||||||
|
var sink float64
|
||||||
|
b.ReportAllocs()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
for _, sample := range samples {
|
||||||
|
jd := mercuryEventSampleTTJD(sample)
|
||||||
|
for _, event := range cases {
|
||||||
|
sink += event.fn(jd)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ = sink
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkMercuryGreatestElongationFamily(b *testing.B) {
|
||||||
|
cases := mercuryEventCases()[12:]
|
||||||
|
samples := mercuryEventSamples()
|
||||||
|
var sink float64
|
||||||
|
b.ReportAllocs()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
for _, sample := range samples {
|
||||||
|
jd := mercuryEventSampleTTJD(sample)
|
||||||
|
for _, event := range cases {
|
||||||
|
sink += event.fn(jd)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ = sink
|
||||||
|
}
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
package basic
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type mercuryEventFunc func(float64) float64
|
||||||
|
|
||||||
|
type mercuryEventCase struct {
|
||||||
|
name string
|
||||||
|
tolerance float64
|
||||||
|
fn mercuryEventFunc
|
||||||
|
}
|
||||||
|
|
||||||
|
func mercuryEventSamples() []time.Time {
|
||||||
|
start := time.Date(1995, 1, 15, 12, 34, 56, 789000000, time.UTC)
|
||||||
|
samples := make([]time.Time, 0, 96)
|
||||||
|
for i := 0; i < 48; i++ {
|
||||||
|
d := start.AddDate(0, 0, i*137)
|
||||||
|
d = d.Add(time.Duration((i%7)*3)*time.Hour + time.Duration((i%11)*7)*time.Minute + time.Duration((i%13)*11)*time.Second)
|
||||||
|
samples = append(samples, d)
|
||||||
|
}
|
||||||
|
extraStart := start.AddDate(0, 0, 41)
|
||||||
|
for i := 0; i < 48; i++ {
|
||||||
|
d := extraStart.AddDate(0, 0, i*89)
|
||||||
|
d = d.Add(time.Duration((i%5)*7)*time.Hour + time.Duration((i%13)*5)*time.Minute + time.Duration((i%17)*19)*time.Second)
|
||||||
|
samples = append(samples, d)
|
||||||
|
}
|
||||||
|
return samples
|
||||||
|
}
|
||||||
|
|
||||||
|
func mercuryEventSampleTTJD(date time.Time) float64 {
|
||||||
|
return TD2UT(Date2JDE(date.UTC()), true)
|
||||||
|
}
|
||||||
|
|
||||||
|
func mercuryEventCases() []mercuryEventCase {
|
||||||
|
const (
|
||||||
|
conjunctionTolerance = 0.00001
|
||||||
|
searchTolerance = 30.0 / 86400.0
|
||||||
|
)
|
||||||
|
return []mercuryEventCase{
|
||||||
|
{name: "LastMercuryConjunction", tolerance: conjunctionTolerance, fn: LastMercuryConjunction},
|
||||||
|
{name: "NextMercuryConjunction", tolerance: conjunctionTolerance, fn: NextMercuryConjunction},
|
||||||
|
{name: "LastMercuryInferiorConjunction", tolerance: conjunctionTolerance, fn: LastMercuryInferiorConjunction},
|
||||||
|
{name: "NextMercuryInferiorConjunction", tolerance: conjunctionTolerance, fn: NextMercuryInferiorConjunction},
|
||||||
|
{name: "LastMercurySuperiorConjunction", tolerance: conjunctionTolerance, fn: LastMercurySuperiorConjunction},
|
||||||
|
{name: "NextMercurySuperiorConjunction", tolerance: conjunctionTolerance, fn: NextMercurySuperiorConjunction},
|
||||||
|
{name: "LastMercuryRetrograde", tolerance: searchTolerance, fn: LastMercuryRetrograde},
|
||||||
|
{name: "NextMercuryRetrograde", tolerance: searchTolerance, fn: NextMercuryRetrograde},
|
||||||
|
{name: "LastMercuryProgradeToRetrograde", tolerance: searchTolerance, fn: LastMercuryProgradeToRetrograde},
|
||||||
|
{name: "NextMercuryProgradeToRetrograde", tolerance: searchTolerance, fn: NextMercuryProgradeToRetrograde},
|
||||||
|
{name: "LastMercuryRetrogradeToPrograde", tolerance: searchTolerance, fn: LastMercuryRetrogradeToPrograde},
|
||||||
|
{name: "NextMercuryRetrogradeToPrograde", tolerance: searchTolerance, fn: NextMercuryRetrogradeToPrograde},
|
||||||
|
{name: "LastMercuryGreatestElongation", tolerance: searchTolerance, fn: LastMercuryGreatestElongation},
|
||||||
|
{name: "NextMercuryGreatestElongation", tolerance: searchTolerance, fn: NextMercuryGreatestElongation},
|
||||||
|
{name: "LastMercuryGreatestElongationEast", tolerance: searchTolerance, fn: LastMercuryGreatestElongationEast},
|
||||||
|
{name: "NextMercuryGreatestElongationEast", tolerance: searchTolerance, fn: NextMercuryGreatestElongationEast},
|
||||||
|
{name: "LastMercuryGreatestElongationWest", tolerance: searchTolerance, fn: LastMercuryGreatestElongationWest},
|
||||||
|
{name: "NextMercuryGreatestElongationWest", tolerance: searchTolerance, fn: NextMercuryGreatestElongationWest},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func almostEqualWithinDays(got, want, tolerance float64) bool {
|
||||||
|
return math.Abs(got-want) <= tolerance
|
||||||
|
}
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
package basic
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"math"
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
type mercuryEventBaselineSample struct {
|
||||||
|
InputUTC string `json:"input_utc"`
|
||||||
|
TTJDBits uint64 `json:"tt_jd_bits"`
|
||||||
|
Events map[string]uint64 `json:"events"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type mercuryEventBaseline struct {
|
||||||
|
Samples []mercuryEventBaselineSample `json:"samples"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadMercuryEventBaseline(t *testing.T) mercuryEventBaseline {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
data, err := os.ReadFile("testdata/mercury_event_baseline.json")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var baseline mercuryEventBaseline
|
||||||
|
if err := json.Unmarshal(data, &baseline); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if len(baseline.Samples) == 0 {
|
||||||
|
t.Fatal("empty mercury event baseline")
|
||||||
|
}
|
||||||
|
return baseline
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMercuryEventBaselineRegression(t *testing.T) {
|
||||||
|
baseline := loadMercuryEventBaseline(t)
|
||||||
|
cases := mercuryEventCases()
|
||||||
|
|
||||||
|
for _, sample := range baseline.Samples {
|
||||||
|
jd := math.Float64frombits(sample.TTJDBits)
|
||||||
|
for _, event := range cases {
|
||||||
|
wantBits, ok := sample.Events[event.name]
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("%s missing baseline event %s", sample.InputUTC, event.name)
|
||||||
|
}
|
||||||
|
want := math.Float64frombits(wantBits)
|
||||||
|
got := event.fn(jd)
|
||||||
|
diff := math.Abs(got - want)
|
||||||
|
if diff > event.tolerance {
|
||||||
|
t.Fatalf("%s %s diff %.12f > tolerance %.12f", sample.InputUTC, event.name, diff, event.tolerance)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,670 @@
|
|||||||
|
package basic
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math"
|
||||||
|
|
||||||
|
"b612.me/astro/planet"
|
||||||
|
. "b612.me/astro/tools"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
MERCURY_S_PERIOD = 1 / ((1 / 87.9691) - (1 / 365.256363004))
|
||||||
|
mercuryConjunctionDerivativeStepDay = 2e-5 * 36525.0
|
||||||
|
mercuryLightTimeDaysPerAU = 0.0057755183
|
||||||
|
mercuryEventSearchN = 16
|
||||||
|
)
|
||||||
|
|
||||||
|
type mercuryConjunctionLBR struct {
|
||||||
|
lo float64
|
||||||
|
bo float64
|
||||||
|
r float64
|
||||||
|
}
|
||||||
|
|
||||||
|
type mercuryConjunctionGeo struct {
|
||||||
|
lo float64
|
||||||
|
bo float64
|
||||||
|
dist float64
|
||||||
|
}
|
||||||
|
|
||||||
|
type mercuryConjunctionResult struct {
|
||||||
|
diff float64
|
||||||
|
sunLightDays float64
|
||||||
|
geoLightDays float64
|
||||||
|
}
|
||||||
|
|
||||||
|
func mercuryHelioN(planetIndex int, jd float64, n int) mercuryConjunctionLBR {
|
||||||
|
return mercuryConjunctionLBR{
|
||||||
|
lo: planet.WherePlanetN(planetIndex, 0, jd, n),
|
||||||
|
bo: planet.WherePlanetN(planetIndex, 1, jd, n),
|
||||||
|
r: planet.WherePlanetN(planetIndex, 2, jd, n),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func mercuryGeocentric(planetPos, earthPos mercuryConjunctionLBR) mercuryConjunctionGeo {
|
||||||
|
x := planetPos.r*Cos(planetPos.bo)*Cos(planetPos.lo) - earthPos.r*Cos(earthPos.bo)*Cos(earthPos.lo)
|
||||||
|
y := planetPos.r*Cos(planetPos.bo)*Sin(planetPos.lo) - earthPos.r*Cos(earthPos.bo)*Sin(earthPos.lo)
|
||||||
|
z := planetPos.r*Sin(planetPos.bo) - earthPos.r*Sin(earthPos.bo)
|
||||||
|
dist := math.Sqrt(x*x + y*y + z*z)
|
||||||
|
return mercuryConjunctionGeo{
|
||||||
|
lo: Limit360(math.Atan2(y, x) * 180 / math.Pi),
|
||||||
|
bo: math.Atan2(z, math.Sqrt(x*x+y*y)) * 180 / math.Pi,
|
||||||
|
dist: dist,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func mercuryConjunctionAngleDelta(diff float64) float64 {
|
||||||
|
diff = Limit360(diff)
|
||||||
|
if diff > 180 {
|
||||||
|
diff -= 360
|
||||||
|
}
|
||||||
|
if diff < -180 {
|
||||||
|
diff += 360
|
||||||
|
}
|
||||||
|
return diff
|
||||||
|
}
|
||||||
|
|
||||||
|
func mercuryConjunctionHeliocentricDelta(jd, targetDeg float64, n int) float64 {
|
||||||
|
planetLo := planet.WherePlanetN(1, 0, jd, n)
|
||||||
|
earthLo := planet.WherePlanetN(-1, 0, jd, n)
|
||||||
|
return mercuryConjunctionAngleDelta(planetLo - earthLo - targetDeg)
|
||||||
|
}
|
||||||
|
|
||||||
|
func mercuryConjunctionDifference(jd float64, n int, targetDeg, sunLightDays, geoLightDays float64) mercuryConjunctionResult {
|
||||||
|
earthForSun := mercuryHelioN(-1, jd-sunLightDays, n)
|
||||||
|
sunLo := Limit360(earthForSun.lo + 180)
|
||||||
|
earth := mercuryHelioN(-1, jd-geoLightDays, n)
|
||||||
|
planetPos := mercuryHelioN(1, jd-geoLightDays, n)
|
||||||
|
geo := mercuryGeocentric(planetPos, earth)
|
||||||
|
return mercuryConjunctionResult{
|
||||||
|
diff: mercuryConjunctionAngleDelta(geo.lo - sunLo - targetDeg),
|
||||||
|
sunLightDays: earthForSun.r * mercuryLightTimeDaysPerAU,
|
||||||
|
geoLightDays: geo.dist * mercuryLightTimeDaysPerAU,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func mercuryConjunctionExactDelta(jd float64) float64 {
|
||||||
|
return mercuryConjunctionAngleDelta(MercuryApparentLo(jd) - HSunApparentLo(jd))
|
||||||
|
}
|
||||||
|
|
||||||
|
func mercuryConjunctionApproxTT(seed float64, inferior bool) float64 {
|
||||||
|
heliocentricTarget := 180.0
|
||||||
|
if inferior {
|
||||||
|
heliocentricTarget = 0
|
||||||
|
}
|
||||||
|
jd := seed
|
||||||
|
for i := 0; i < 6; i++ {
|
||||||
|
jd -= mercuryConjunctionHeliocentricDelta(jd, heliocentricTarget, 8) / (360.0 / MERCURY_S_PERIOD)
|
||||||
|
}
|
||||||
|
|
||||||
|
startSample := mercuryConjunctionDifference(jd, 8, 0, 0, 0)
|
||||||
|
nextSample := mercuryConjunctionDifference(jd+mercuryConjunctionDerivativeStepDay, 8, 0, 0, 0)
|
||||||
|
diffSlope := mercuryConjunctionAngleDelta(nextSample.diff-startSample.diff) / mercuryConjunctionDerivativeStepDay
|
||||||
|
|
||||||
|
refined := mercuryConjunctionDifference(jd, 40, 0, startSample.sunLightDays, startSample.geoLightDays)
|
||||||
|
jd -= refined.diff / diffSlope
|
||||||
|
final := mercuryConjunctionDifference(jd, -1, 0, refined.sunLightDays, refined.geoLightDays)
|
||||||
|
jd -= final.diff / diffSlope
|
||||||
|
return jd
|
||||||
|
}
|
||||||
|
|
||||||
|
func mercuryConjunctionExactTT(seed float64, inferior bool) float64 {
|
||||||
|
estimateJD := mercuryConjunctionApproxTT(seed, inferior)
|
||||||
|
for {
|
||||||
|
prevJD := estimateJD
|
||||||
|
longitudeDelta := mercuryConjunctionExactDelta(prevJD)
|
||||||
|
longitudeSlope := (mercuryConjunctionExactDelta(prevJD+0.000005) - mercuryConjunctionExactDelta(prevJD-0.000005)) / 0.00001
|
||||||
|
estimateJD = prevJD - longitudeDelta/longitudeSlope
|
||||||
|
if math.Abs(estimateJD-prevJD) <= 0.00001 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return estimateJD
|
||||||
|
}
|
||||||
|
|
||||||
|
func mercuryConjunctionLegacy(jde float64, next uint8) float64 {
|
||||||
|
//0=last 1=next
|
||||||
|
longitudeDeltaAt := func(jde float64) float64 {
|
||||||
|
return mercuryConjunctionExactDelta(jde)
|
||||||
|
}
|
||||||
|
currentDelta := longitudeDeltaAt(jde)
|
||||||
|
distanceTrend := math.Abs(longitudeDeltaAt(jde+1/86400.0)) - math.Abs(currentDelta)
|
||||||
|
if distanceTrend >= 0 && next == 1 && currentDelta > 0 {
|
||||||
|
jde += MERCURY_S_PERIOD/8.0 + 2
|
||||||
|
}
|
||||||
|
if distanceTrend >= 0 && next == 1 && currentDelta < 0 {
|
||||||
|
jde += MERCURY_S_PERIOD/6.0 + 2
|
||||||
|
}
|
||||||
|
if distanceTrend <= 0 && next == 0 && currentDelta < 0 {
|
||||||
|
jde -= MERCURY_S_PERIOD/8.0 + 2
|
||||||
|
}
|
||||||
|
if distanceTrend <= 0 && next == 0 && currentDelta > 0 {
|
||||||
|
jde -= MERCURY_S_PERIOD/6.0 + 2
|
||||||
|
}
|
||||||
|
for {
|
||||||
|
currentDelta := longitudeDeltaAt(jde)
|
||||||
|
distanceTrend := math.Abs(longitudeDeltaAt(jde+1/86400.0)) - math.Abs(currentDelta)
|
||||||
|
if math.Abs(currentDelta) > 12 || (distanceTrend > 0 && next == 1) || (distanceTrend < 0 && next == 0) {
|
||||||
|
if next == 1 {
|
||||||
|
jde += 2
|
||||||
|
} else {
|
||||||
|
jde -= 2
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
estimateJD := jde
|
||||||
|
for {
|
||||||
|
prevJD := estimateJD
|
||||||
|
longitudeDelta := longitudeDeltaAt(prevJD)
|
||||||
|
longitudeSlope := (longitudeDeltaAt(prevJD+0.000005) - longitudeDeltaAt(prevJD-0.000005)) / 0.00001
|
||||||
|
estimateJD = prevJD - longitudeDelta/longitudeSlope
|
||||||
|
if math.Abs(estimateJD-prevJD) <= 0.00001 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return TD2UT(estimateJD, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
func mercuryConjunction(jde float64, next uint8) float64 {
|
||||||
|
//0=last 1=next
|
||||||
|
if math.Abs(mercuryConjunctionExactDelta(jde)) <= 30.0/86400.0 {
|
||||||
|
best := math.NaN()
|
||||||
|
consider := func(inferior bool) {
|
||||||
|
eventUT := TD2UT(mercuryConjunctionExactTT(jde, inferior), false)
|
||||||
|
if next == 0 && !eventUTQueryBeforeOrEqual(eventUT, jde) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if next == 1 && !eventUTQueryAfterOrEqual(eventUT, jde) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if math.IsNaN(best) || math.Abs(eventUTQueryTTDelta(eventUT, jde)) < math.Abs(eventUTQueryTTDelta(best, jde)) {
|
||||||
|
best = eventUT
|
||||||
|
}
|
||||||
|
}
|
||||||
|
consider(true)
|
||||||
|
consider(false)
|
||||||
|
if !math.IsNaN(best) {
|
||||||
|
return best
|
||||||
|
}
|
||||||
|
}
|
||||||
|
currentDelta := mercuryConjunctionExactDelta(jde)
|
||||||
|
// pos 大于0:远离太阳 小于0:靠近太阳
|
||||||
|
distanceTrend := math.Abs(mercuryConjunctionExactDelta(jde+1/86400.0)) - math.Abs(currentDelta)
|
||||||
|
if distanceTrend >= 0 && next == 1 && currentDelta > 0 {
|
||||||
|
jde += MERCURY_S_PERIOD/8.0 + 2
|
||||||
|
}
|
||||||
|
if distanceTrend >= 0 && next == 1 && currentDelta < 0 {
|
||||||
|
jde += MERCURY_S_PERIOD/6.0 + 2
|
||||||
|
}
|
||||||
|
if distanceTrend <= 0 && next == 0 && currentDelta < 0 {
|
||||||
|
jde -= MERCURY_S_PERIOD/8.0 + 2
|
||||||
|
}
|
||||||
|
if distanceTrend <= 0 && next == 0 && currentDelta > 0 {
|
||||||
|
jde -= MERCURY_S_PERIOD/6.0 + 2
|
||||||
|
}
|
||||||
|
for {
|
||||||
|
currentDelta := mercuryConjunctionExactDelta(jde)
|
||||||
|
distanceTrend := math.Abs(mercuryConjunctionExactDelta(jde+1/86400.0)) - math.Abs(currentDelta)
|
||||||
|
if math.Abs(currentDelta) > 12 || (distanceTrend > 0 && next == 1) || (distanceTrend < 0 && next == 0) {
|
||||||
|
if next == 1 {
|
||||||
|
jde += 2
|
||||||
|
} else {
|
||||||
|
jde -= 2
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
inferior := mercuryConjunctionExactTT(jde, true)
|
||||||
|
superior := mercuryConjunctionExactTT(jde, false)
|
||||||
|
best := inferior
|
||||||
|
if math.Abs(superior-jde) < math.Abs(inferior-jde) {
|
||||||
|
best = superior
|
||||||
|
}
|
||||||
|
return TD2UT(best, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
func LastMercuryConjunction(jde float64) float64 {
|
||||||
|
return inclusiveLastSimpleEvent(jde, LastMercuryConjunctionStrict, NextMercuryConjunctionStrict)
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NextMercuryInferiorConjunction(jde float64) float64 {
|
||||||
|
date := NextMercuryConjunctionStrict(jde)
|
||||||
|
if EarthMercuryAway(date) > EarthAway(date) {
|
||||||
|
return NextMercuryConjunctionStrict(date + 2)
|
||||||
|
}
|
||||||
|
return date
|
||||||
|
}
|
||||||
|
|
||||||
|
func NextMercurySuperiorConjunction(jde float64) float64 {
|
||||||
|
date := NextMercuryConjunctionStrict(jde)
|
||||||
|
if EarthMercuryAway(date) < EarthAway(date) {
|
||||||
|
return NextMercuryConjunctionStrict(date + 2)
|
||||||
|
}
|
||||||
|
return date
|
||||||
|
}
|
||||||
|
|
||||||
|
func LastMercuryInferiorConjunction(jde float64) float64 {
|
||||||
|
date := LastMercuryConjunctionStrict(jde)
|
||||||
|
if EarthMercuryAway(date) > EarthAway(date) {
|
||||||
|
return LastMercuryConjunctionStrict(date - 2)
|
||||||
|
}
|
||||||
|
return date
|
||||||
|
}
|
||||||
|
|
||||||
|
func LastMercurySuperiorConjunction(jde float64) float64 {
|
||||||
|
date := LastMercuryConjunctionStrict(jde)
|
||||||
|
if EarthMercuryAway(date) < EarthAway(date) {
|
||||||
|
return LastMercuryConjunctionStrict(date - 2)
|
||||||
|
}
|
||||||
|
return date
|
||||||
|
}
|
||||||
|
|
||||||
|
func mercuryRetrograde(jde float64) float64 {
|
||||||
|
//0=last 1=next
|
||||||
|
solarRADelta := func(jde float64) float64 {
|
||||||
|
sub := Limit360(MercuryApparentRa(jde) - SunApparentRa(jde))
|
||||||
|
if sub > 180 {
|
||||||
|
sub -= 360
|
||||||
|
}
|
||||||
|
if sub < -180 {
|
||||||
|
sub += 360
|
||||||
|
}
|
||||||
|
return sub
|
||||||
|
}
|
||||||
|
lastConjunction := mercuryConjunctionLegacy(jde, 0)
|
||||||
|
nextConjunction := mercuryConjunctionLegacy(jde, 1)
|
||||||
|
currentRADelta := solarRADelta(jde)
|
||||||
|
if currentRADelta > 0 {
|
||||||
|
jde = lastConjunction + ((nextConjunction - lastConjunction) / 5.0 * 3.5)
|
||||||
|
} else {
|
||||||
|
jde = lastConjunction + ((nextConjunction - lastConjunction) / 5.5)
|
||||||
|
}
|
||||||
|
for {
|
||||||
|
currentRate := mercuryRADerivative(jde, 1.0/86400.0)
|
||||||
|
if math.Abs(currentRate) > 0.55 {
|
||||||
|
jde += 2
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
estimateJD := jde
|
||||||
|
for {
|
||||||
|
prevJD := estimateJD
|
||||||
|
rateValue := mercuryRADerivative(prevJD, 2.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
|
||||||
|
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 mercuryRADerivative(jd, 0.5/86400.0)
|
||||||
|
})
|
||||||
|
//fmt.Println((bestJD - lastConjunction) / (nextConjunction - lastConjunction))
|
||||||
|
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 {
|
||||||
|
date := mercuryRetrograde(jde)
|
||||||
|
if !eventUTQueryAfterOrEqual(date, jde) {
|
||||||
|
nextConjunction := NextMercuryConjunctionStrict(jde)
|
||||||
|
return mercuryRetrograde(nextConjunction + 2)
|
||||||
|
}
|
||||||
|
return date
|
||||||
|
}
|
||||||
|
|
||||||
|
func LastMercuryRetrograde(jde float64) float64 {
|
||||||
|
lastConjunction := LastMercuryConjunctionStrict(jde)
|
||||||
|
date := mercuryRetrograde(lastConjunction + 2)
|
||||||
|
if !eventUTQueryBeforeOrEqual(date, jde) {
|
||||||
|
previousConjunction := LastMercuryConjunctionStrict(eventUTLastQueryTT(lastConjunction))
|
||||||
|
return mercuryRetrograde(previousConjunction + 2)
|
||||||
|
}
|
||||||
|
return date
|
||||||
|
}
|
||||||
|
|
||||||
|
func LastMercuryRetrogradeStrict(jde float64) float64 {
|
||||||
|
return LastMercuryRetrograde(jde)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NextMercuryRetrogradeStrict(jde float64) float64 {
|
||||||
|
return NextMercuryRetrograde(jde)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NextMercuryProgradeToRetrograde(jde float64) float64 {
|
||||||
|
return nextMercuryTypedStation(jde, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NextMercuryRetrogradeToPrograde(jde float64) float64 {
|
||||||
|
return nextMercuryTypedStation(jde, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
func LastMercuryProgradeToRetrograde(jde float64) float64 {
|
||||||
|
return lastMercuryTypedStation(jde, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
func LastMercuryRetrogradeToPrograde(jde float64) float64 {
|
||||||
|
return lastMercuryTypedStation(jde, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
func MercurySunElongation(jde float64) float64 {
|
||||||
|
lo1, bo1 := MercuryApparentLoBo(jde)
|
||||||
|
lo2 := HSunApparentLo(jde)
|
||||||
|
bo2 := HSunTrueBo(jde)
|
||||||
|
return StarAngularSeparation(lo1, bo1, lo2, bo2)
|
||||||
|
}
|
||||||
|
|
||||||
|
func mercurySunElongationN(jde float64, n int) float64 {
|
||||||
|
lo1, bo1 := MercuryApparentLoBoN(jde, n)
|
||||||
|
lo2 := HSunApparentLoN(jde, n)
|
||||||
|
bo2 := HSunTrueBoN(jde, n)
|
||||||
|
return StarAngularSeparation(lo1, bo1, lo2, bo2)
|
||||||
|
}
|
||||||
|
|
||||||
|
func mercuryTrueElongationN(jde float64, n int) float64 {
|
||||||
|
earth := mercuryHelioN(-1, jde, n)
|
||||||
|
planetPos := mercuryHelioN(1, jde, n)
|
||||||
|
geo := mercuryGeocentric(planetPos, earth)
|
||||||
|
return StarAngularSeparation(geo.lo, geo.bo, HSunTrueLoN(jde, n), HSunTrueBoN(jde, n))
|
||||||
|
}
|
||||||
|
|
||||||
|
func mercuryGreatestElongationInWindow(start, end float64) float64 {
|
||||||
|
best := maximizeInWindow(start, end, 2.0, func(jd float64) float64 {
|
||||||
|
return mercuryTrueElongationN(jd, mercuryEventSearchN)
|
||||||
|
}, func(jd float64) float64 {
|
||||||
|
return mercuryTrueElongationN(jd, -1)
|
||||||
|
})
|
||||||
|
return TD2UT(best, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
func mercuryEastElongationWindowEndingAt(inferior float64) (float64, float64) {
|
||||||
|
lastSuperior := LastMercurySuperiorConjunction(eventUTLastQueryTT(inferior))
|
||||||
|
return lastSuperior + innerEventWindowPadding, inferior - innerEventWindowPadding
|
||||||
|
}
|
||||||
|
|
||||||
|
func mercuryWestElongationWindowEndingAt(superior float64) (float64, float64) {
|
||||||
|
lastInferior := LastMercuryInferiorConjunction(eventUTLastQueryTT(superior))
|
||||||
|
return lastInferior + innerEventWindowPadding, superior - innerEventWindowPadding
|
||||||
|
}
|
||||||
|
|
||||||
|
func mercuryEastElongationWindowContaining(jde float64) (float64, float64) {
|
||||||
|
nextInferior := NextMercuryInferiorConjunction(jde)
|
||||||
|
start, end := mercuryEastElongationWindowEndingAt(nextInferior)
|
||||||
|
if eventUTQueryBeforeOrEqual(start, jde) {
|
||||||
|
return start, end
|
||||||
|
}
|
||||||
|
currentInferior := LastMercuryInferiorConjunction(jde)
|
||||||
|
return mercuryEastElongationWindowEndingAt(currentInferior)
|
||||||
|
}
|
||||||
|
|
||||||
|
func mercuryWestElongationWindowContaining(jde float64) (float64, float64) {
|
||||||
|
nextSuperior := NextMercurySuperiorConjunction(jde)
|
||||||
|
start, end := mercuryWestElongationWindowEndingAt(nextSuperior)
|
||||||
|
if eventUTQueryBeforeOrEqual(start, jde) {
|
||||||
|
return start, end
|
||||||
|
}
|
||||||
|
currentSuperior := LastMercurySuperiorConjunction(jde)
|
||||||
|
return mercuryWestElongationWindowEndingAt(currentSuperior)
|
||||||
|
}
|
||||||
|
|
||||||
|
func nextMercuryGreatestElongationTyped(jde float64, east bool) float64 {
|
||||||
|
if east {
|
||||||
|
start, windowEnd := mercuryEastElongationWindowContaining(jde)
|
||||||
|
for {
|
||||||
|
date := mercuryGreatestElongationInWindow(start, windowEnd)
|
||||||
|
if eventUTQueryAfterOrEqual(date, jde) {
|
||||||
|
return date
|
||||||
|
}
|
||||||
|
nextInferior := NextMercuryInferiorConjunction(eventUTNextQueryTT(windowEnd))
|
||||||
|
start, windowEnd = mercuryEastElongationWindowEndingAt(nextInferior)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
start, windowEnd := mercuryWestElongationWindowContaining(jde)
|
||||||
|
for {
|
||||||
|
date := mercuryGreatestElongationInWindow(start, windowEnd)
|
||||||
|
if eventUTQueryAfterOrEqual(date, jde) {
|
||||||
|
return date
|
||||||
|
}
|
||||||
|
nextSuperior := NextMercurySuperiorConjunction(eventUTNextQueryTT(windowEnd))
|
||||||
|
start, windowEnd = mercuryWestElongationWindowEndingAt(nextSuperior)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func lastMercuryGreatestElongationTyped(jde float64, east bool) float64 {
|
||||||
|
if east {
|
||||||
|
start, windowEnd := mercuryEastElongationWindowContaining(jde)
|
||||||
|
for {
|
||||||
|
date := mercuryGreatestElongationInWindow(start, windowEnd)
|
||||||
|
if eventUTQueryBeforeOrEqual(date, jde) {
|
||||||
|
return date
|
||||||
|
}
|
||||||
|
prevInferior := LastMercuryInferiorConjunction(eventUTLastQueryTT(start))
|
||||||
|
start, windowEnd = mercuryEastElongationWindowEndingAt(prevInferior)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
start, windowEnd := mercuryWestElongationWindowContaining(jde)
|
||||||
|
for {
|
||||||
|
date := mercuryGreatestElongationInWindow(start, windowEnd)
|
||||||
|
if eventUTQueryBeforeOrEqual(date, jde) {
|
||||||
|
return date
|
||||||
|
}
|
||||||
|
prevSuperior := LastMercurySuperiorConjunction(eventUTLastQueryTT(start))
|
||||||
|
start, windowEnd = mercuryWestElongationWindowEndingAt(prevSuperior)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func mercuryGreatestElongation(jde float64) float64 {
|
||||||
|
solarRADelta := func(jde float64) float64 {
|
||||||
|
sub := Limit360(MercuryApparentRa(jde) - SunApparentRa(jde))
|
||||||
|
if sub > 180 {
|
||||||
|
sub -= 360
|
||||||
|
}
|
||||||
|
if sub < -180 {
|
||||||
|
sub += 360
|
||||||
|
}
|
||||||
|
return sub
|
||||||
|
}
|
||||||
|
elongationRate := func(jde float64, delta float64) float64 {
|
||||||
|
sub := MercurySunElongation(jde+delta) - MercurySunElongation(jde-delta)
|
||||||
|
if sub > 180 {
|
||||||
|
sub -= 360
|
||||||
|
}
|
||||||
|
if sub < -180 {
|
||||||
|
sub += 360
|
||||||
|
}
|
||||||
|
return sub / (2 * delta)
|
||||||
|
}
|
||||||
|
lastConjunction := LastMercuryConjunctionStrict(jde)
|
||||||
|
nextConjunction := NextMercuryConjunctionStrict(jde)
|
||||||
|
currentRADelta := solarRADelta(jde)
|
||||||
|
if currentRADelta > 0 {
|
||||||
|
jde = lastConjunction + ((nextConjunction - lastConjunction) / 5.0 * 2.0)
|
||||||
|
} else {
|
||||||
|
jde = lastConjunction + ((nextConjunction - lastConjunction) / 6.0)
|
||||||
|
}
|
||||||
|
for {
|
||||||
|
currentRate := elongationRate(jde, 1.0/86400.0)
|
||||||
|
if math.Abs(currentRate) > 0.4 {
|
||||||
|
jde += 2
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
estimateJD := jde
|
||||||
|
for {
|
||||||
|
prevJD := estimateJD
|
||||||
|
rateValue := elongationRate(prevJD, 2.0/86400.0)
|
||||||
|
rateSlope := (elongationRate(prevJD+15.0/86400.0, 2.0/86400.0) - elongationRate(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 elongationRate(jd, 0.5/86400.0)
|
||||||
|
})
|
||||||
|
//fmt.Println((bestJD - lastConjunction) / (nextConjunction - lastConjunction))
|
||||||
|
return TD2UT(bestJD, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NextMercuryGreatestElongation(jde float64) float64 {
|
||||||
|
east := NextMercuryGreatestElongationEast(jde)
|
||||||
|
west := NextMercuryGreatestElongationWest(jde)
|
||||||
|
if sameEventJD(east, west) {
|
||||||
|
return east
|
||||||
|
}
|
||||||
|
if east < west {
|
||||||
|
return east
|
||||||
|
}
|
||||||
|
return west
|
||||||
|
}
|
||||||
|
|
||||||
|
func LastMercuryGreatestElongation(jde float64) float64 {
|
||||||
|
east := LastMercuryGreatestElongationEast(jde)
|
||||||
|
west := LastMercuryGreatestElongationWest(jde)
|
||||||
|
if sameEventJD(east, west) {
|
||||||
|
return east
|
||||||
|
}
|
||||||
|
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 {
|
||||||
|
return nextMercuryGreatestElongationTyped(jde, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NextMercuryGreatestElongationWest(jde float64) float64 {
|
||||||
|
return nextMercuryGreatestElongationTyped(jde, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
func LastMercuryGreatestElongationEast(jde float64) float64 {
|
||||||
|
return lastMercuryGreatestElongationTyped(jde, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
func LastMercuryGreatestElongationWest(jde float64) float64 {
|
||||||
|
return lastMercuryGreatestElongationTyped(jde, false)
|
||||||
|
}
|
||||||
@@ -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))
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
package basic
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestMercury(t *testing.T) {
|
|
||||||
jde := GetNowJDE()
|
|
||||||
fmt.Println(2.459941309513889e+06, JDE2Date(2.459941309513889e+06), JDE2Date(8.0/24.0+LastMercuryGreatestElongation(2.459941309513889e+06)))
|
|
||||||
fmt.Println("-------------for------------")
|
|
||||||
for i := 0.00; i < 700.0; i += 5 {
|
|
||||||
fmt.Println(jde+i, JDE2Date(jde+i), JDE2Date(8.0/24.0+LastMercuryGreatestElongationWest(jde+i)))
|
|
||||||
// fmt.Println("")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+50
-1684
File diff suppressed because one or more lines are too long
@@ -0,0 +1,45 @@
|
|||||||
|
package basic
|
||||||
|
|
||||||
|
import . "b612.me/astro/tools"
|
||||||
|
|
||||||
|
// MoonBrightLimbPositionAngle 月亮明亮边缘位置角 / position angle of the Moon's bright limb.
|
||||||
|
func MoonBrightLimbPositionAngle(jd float64) float64 {
|
||||||
|
return MoonBrightLimbPositionAngleN(jd, -1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MoonBrightLimbPositionAngleN 月亮明亮边缘位置角(截断版) / truncated position angle of the Moon's bright limb.
|
||||||
|
func MoonBrightLimbPositionAngleN(jd float64, n int) float64 {
|
||||||
|
sunRA, sunDec := HSunApparentRaDecN(jd, n)
|
||||||
|
moonRA, moonDec := HMoonTrueRaDecN(jd, n)
|
||||||
|
return brightLimbPositionAngleFromRaDec(sunRA, sunDec, moonRA, moonDec)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MoonTopocentricBrightLimbPositionAngle 月亮站心明亮边缘位置角 / topocentric position angle of the Moon's bright limb.
|
||||||
|
func MoonTopocentricBrightLimbPositionAngle(jd, observerLon, observerLat, height float64) float64 {
|
||||||
|
return MoonTopocentricBrightLimbPositionAngleN(jd, observerLon, observerLat, height, -1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MoonTopocentricBrightLimbPositionAngleN 月亮站心明亮边缘位置角(截断版) / truncated topocentric position angle of the Moon's bright limb.
|
||||||
|
func MoonTopocentricBrightLimbPositionAngleN(jd, observerLon, observerLat, height float64, n int) float64 {
|
||||||
|
sunRA, sunDec := sunTopocentricApparentRaDecN(jd, observerLon, observerLat, height, n)
|
||||||
|
moonRA, moonDec := moonTopocentricApparentRaDecN(jd, observerLon, observerLat, height, n)
|
||||||
|
return brightLimbPositionAngleFromRaDec(sunRA, sunDec, moonRA, moonDec)
|
||||||
|
}
|
||||||
|
|
||||||
|
func moonTopocentricApparentRaDecN(jd, observerLon, observerLat, height float64, n int) (float64, float64) {
|
||||||
|
geocentricRA := HMoonTrueRaN(jd, n)
|
||||||
|
geocentricDec := HMoonTrueDecN(jd, n)
|
||||||
|
distanceAU := HMoonAwayN(jd, n) / moonPhysicalAstronomicalUnitKM
|
||||||
|
return TopocentricRaDec(geocentricRA, geocentricDec, observerLat, observerLon, TD2UT(jd, false), distanceAU, height)
|
||||||
|
}
|
||||||
|
|
||||||
|
func sunTopocentricApparentRaDecN(jd, observerLon, observerLat, height float64, n int) (float64, float64) {
|
||||||
|
geocentricRA, geocentricDec := HSunApparentRaDecN(jd, n)
|
||||||
|
return TopocentricRaDec(geocentricRA, geocentricDec, observerLat, observerLon, TD2UT(jd, false), EarthAwayN(jd, n), height)
|
||||||
|
}
|
||||||
|
|
||||||
|
func brightLimbPositionAngleFromRaDec(sunRA, sunDec, bodyRA, bodyDec float64) float64 {
|
||||||
|
y := Cos(sunDec) * Sin(sunRA-bodyRA)
|
||||||
|
x := Sin(sunDec)*Cos(bodyDec) - Cos(sunDec)*Sin(bodyDec)*Cos(sunRA-bodyRA)
|
||||||
|
return ArcTan2(y, x)
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
package basic
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestMoonBrightLimbPositionAngleMeeusExample(t *testing.T) {
|
||||||
|
assertPlanetPhaseClose(t, "MoonBrightLimbPositionAngle", MoonBrightLimbPositionAngle(2448724.5), 285.0, 0.1)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMoonBrightLimbPositionAngleNFullMatchesDefault(t *testing.T) {
|
||||||
|
jd := TD2UT(Date2JDE(time.Date(2026, 4, 28, 9, 30, 45, 0, time.UTC)), true)
|
||||||
|
|
||||||
|
got := MoonBrightLimbPositionAngle(jd)
|
||||||
|
gotN := MoonBrightLimbPositionAngleN(jd, -1)
|
||||||
|
if math.Float64bits(got) != math.Float64bits(gotN) {
|
||||||
|
t.Fatalf("MoonBrightLimbPositionAngle full-n mismatch: got %.18f want %.18f", got, gotN)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMoonTopocentricBrightLimbPositionAngleSampleFiniteAndInRange(t *testing.T) {
|
||||||
|
jd := TD2UT(Date2JDE(time.Date(2026, 4, 28, 9, 30, 45, 0, time.UTC)), true)
|
||||||
|
got := MoonTopocentricBrightLimbPositionAngle(jd, 121.4737, 31.2304, 4)
|
||||||
|
|
||||||
|
assertFiniteRange(t, "MoonTopocentricBrightLimbPositionAngle", got, 0, 360, true)
|
||||||
|
if angleDiffAbs(got, MoonBrightLimbPositionAngle(jd)) == 0 {
|
||||||
|
t.Fatalf("expected topocentric bright limb angle to differ from geocentric value")
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
package basic
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type moonGeocentricApparentSample struct {
|
||||||
|
InputUTC string `json:"input_utc"`
|
||||||
|
RightAscension float64 `json:"right_ascension"`
|
||||||
|
Declination float64 `json:"declination"`
|
||||||
|
EclipticLongitude float64 `json:"ecliptic_longitude"`
|
||||||
|
EclipticLatitude float64 `json:"ecliptic_latitude"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMoonGeocentricApparentCoordinatesMatchHorizonsBaseline(t *testing.T) {
|
||||||
|
data, err := os.ReadFile("testdata/moon_geocentric_apparent_baseline.json")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("read baseline: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var samples []moonGeocentricApparentSample
|
||||||
|
if err := json.Unmarshal(data, &samples); err != nil {
|
||||||
|
t.Fatalf("decode baseline: %v", err)
|
||||||
|
}
|
||||||
|
if len(samples) == 0 {
|
||||||
|
t.Fatal("empty moon apparent baseline")
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, sample := range samples {
|
||||||
|
date, err := time.Parse(time.RFC3339, sample.InputUTC)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("parse sample time %q: %v", sample.InputUTC, err)
|
||||||
|
}
|
||||||
|
jd := TD2UT(Date2JDE(date.UTC()), true)
|
||||||
|
prefix := "moon." + sample.InputUTC
|
||||||
|
|
||||||
|
assertPlanetApparentAngleClose(t, prefix+".RightAscension", HMoonGeocentricApparentRa(jd), sample.RightAscension, 0.001)
|
||||||
|
assertPlanetPhaseClose(t, prefix+".Declination", HMoonGeocentricApparentDec(jd), sample.Declination, 0.001)
|
||||||
|
assertPlanetApparentAngleClose(t, prefix+".EclipticLongitude", HMoonApparentLo(jd), sample.EclipticLongitude, 0.001)
|
||||||
|
assertPlanetPhaseClose(t, prefix+".EclipticLatitude", HMoonTrueBo(jd), sample.EclipticLatitude, 0.001)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMoonGeocentricTrueCoordinatesFollowDefinition(t *testing.T) {
|
||||||
|
samples := []time.Time{
|
||||||
|
time.Date(1900, 1, 14, 12, 0, 0, 0, time.UTC),
|
||||||
|
time.Date(1950, 6, 3, 0, 0, 0, 0, time.UTC),
|
||||||
|
time.Date(2000, 2, 29, 18, 0, 0, 0, time.UTC),
|
||||||
|
time.Date(2026, 1, 1, 6, 0, 0, 0, time.UTC),
|
||||||
|
time.Date(2100, 8, 17, 9, 0, 0, 0, time.UTC),
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, sample := range samples {
|
||||||
|
jd := TD2UT(Date2JDE(sample.UTC()), true)
|
||||||
|
wantRA, wantDec := LoBoToRaDec(jd, HMoonTrueLo(jd), HMoonTrueBo(jd))
|
||||||
|
gotRA, gotDec := HMoonGeocentricTrueRaDec(jd)
|
||||||
|
|
||||||
|
assertPlanetApparentAngleClose(t, sample.Format(time.RFC3339)+".TrueRightAscension", gotRA, wantRA, 1e-12)
|
||||||
|
assertPlanetPhaseClose(t, sample.Format(time.RFC3339)+".TrueDeclination", gotDec, wantDec, 1e-12)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
package basic
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestHMoonGeocentricApparentRaDecComponentsMatch(t *testing.T) {
|
||||||
|
jd := TD2UT(JDECalc(2026, 1, 1.25), true)
|
||||||
|
|
||||||
|
ra, dec := HMoonGeocentricApparentRaDec(jd)
|
||||||
|
if diff := math.Abs(ra - HMoonGeocentricApparentRa(jd)); diff > 1e-12 {
|
||||||
|
t.Fatalf("RA pair mismatch: got %.15f want %.15f", ra, HMoonGeocentricApparentRa(jd))
|
||||||
|
}
|
||||||
|
if diff := math.Abs(dec - HMoonGeocentricApparentDec(jd)); diff > 1e-12 {
|
||||||
|
t.Fatalf("Dec pair mismatch: got %.15f want %.15f", dec, HMoonGeocentricApparentDec(jd))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHMoonGeocentricTrueRaDecComponentsMatch(t *testing.T) {
|
||||||
|
jd := TD2UT(JDECalc(2026, 1, 1.25), true)
|
||||||
|
|
||||||
|
ra, dec := HMoonGeocentricTrueRaDec(jd)
|
||||||
|
if diff := math.Abs(ra - HMoonGeocentricTrueRa(jd)); diff > 1e-12 {
|
||||||
|
t.Fatalf("RA pair mismatch: got %.15f want %.15f", ra, HMoonGeocentricTrueRa(jd))
|
||||||
|
}
|
||||||
|
if diff := math.Abs(dec - HMoonGeocentricTrueDec(jd)); diff > 1e-12 {
|
||||||
|
t.Fatalf("Dec pair mismatch: got %.15f want %.15f", dec, HMoonGeocentricTrueDec(jd))
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,289 @@
|
|||||||
|
package basic
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math"
|
||||||
|
"sort"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
. "b612.me/astro/tools"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
moonMaxDeclinationMeanMonthDays = 27.321582247
|
||||||
|
moonMaxDeclinationBaseCycle = 1336.86
|
||||||
|
moonMaxDeclinationSearchSpan = 3
|
||||||
|
)
|
||||||
|
|
||||||
|
// DeclinationEvent 赤纬极值事件 / declination extremum event.
|
||||||
|
type DeclinationEvent struct {
|
||||||
|
// JDE 是事件发生时刻对应的世界时儒略日 / event time as UTC-based Julian day.
|
||||||
|
JDE float64
|
||||||
|
// Declination 是该时刻月心地心赤纬,单位度 / geocentric lunar declination at the event, in degrees.
|
||||||
|
Declination float64
|
||||||
|
}
|
||||||
|
|
||||||
|
type moonMaxDeclinationCoefficients struct {
|
||||||
|
D0 float64
|
||||||
|
M0 float64
|
||||||
|
MP0 float64
|
||||||
|
F0 float64
|
||||||
|
JDE0 float64
|
||||||
|
sign float64
|
||||||
|
tc [44]float64
|
||||||
|
dc [37]float64
|
||||||
|
}
|
||||||
|
|
||||||
|
var moonMaxDeclinationNorthCoefficients = moonMaxDeclinationCoefficients{
|
||||||
|
D0: 152.2029,
|
||||||
|
M0: 14.8591,
|
||||||
|
MP0: 4.6881,
|
||||||
|
F0: 325.8867,
|
||||||
|
JDE0: 2451562.5897,
|
||||||
|
sign: 1,
|
||||||
|
tc: [44]float64{
|
||||||
|
0.8975, -0.4726, -0.1030, -0.0976, -0.0462, -0.0461, -0.0438, 0.0162,
|
||||||
|
-0.0157, 0.0145, 0.0136, -0.0095, -0.0091, -0.0089, 0.0075, -0.0068,
|
||||||
|
0.0061, -0.0047, -0.0043, -0.0040, -0.0037, 0.0031, 0.0030, -0.0029,
|
||||||
|
-0.0029, -0.0027, 0.0024, -0.0021, 0.0019, 0.0018, 0.0018, 0.0017,
|
||||||
|
0.0017, -0.0014, 0.0013, 0.0013, 0.0012, 0.0011, -0.0011, 0.0010,
|
||||||
|
0.0010, -0.0009, 0.0007, -0.0007,
|
||||||
|
},
|
||||||
|
dc: [37]float64{
|
||||||
|
5.1093, 0.2658, 0.1448, -0.0322, 0.0133, 0.0125, -0.0124, -0.0101,
|
||||||
|
0.0097, -0.0087, 0.0074, 0.0067, 0.0063, 0.0060, -0.0057, -0.0056,
|
||||||
|
0.0052, 0.0041, -0.0040, 0.0038, -0.0034, -0.0029, 0.0029, -0.0028,
|
||||||
|
-0.0028, -0.0023, -0.0021, 0.0019, 0.0018, 0.0017, 0.0015, 0.0014,
|
||||||
|
-0.0012, -0.0012, -0.0010, -0.0010, 0.0006,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
var moonMaxDeclinationSouthCoefficients = moonMaxDeclinationCoefficients{
|
||||||
|
D0: 345.6676,
|
||||||
|
M0: 1.3951,
|
||||||
|
MP0: 186.21,
|
||||||
|
F0: 145.1633,
|
||||||
|
JDE0: 2451548.9289,
|
||||||
|
sign: -1,
|
||||||
|
tc: [44]float64{
|
||||||
|
-0.8975, -0.4726, -0.1030, -0.0976, 0.0541, 0.0516, -0.0438, 0.0112,
|
||||||
|
0.0157, 0.0023, -0.0136, 0.0110, 0.0091, 0.0089, 0.0075, -0.0030,
|
||||||
|
-0.0061, -0.0047, -0.0043, 0.0040, -0.0037, -0.0031, 0.0030, 0.0029,
|
||||||
|
-0.0029, -0.0027, 0.0024, -0.0021, -0.0019, -0.0006, -0.0018, -0.0017,
|
||||||
|
0.0017, 0.0014, -0.0013, -0.0013, 0.0012, 0.0011, 0.0011, 0.0010,
|
||||||
|
0.0010, -0.0009, -0.0007, -0.0007,
|
||||||
|
},
|
||||||
|
dc: [37]float64{
|
||||||
|
-5.1093, 0.2658, -0.1448, 0.0322, 0.0133, 0.0125, -0.0015, 0.0101,
|
||||||
|
-0.0097, 0.0087, 0.0074, 0.0067, -0.0063, -0.0060, 0.0057, -0.0056,
|
||||||
|
-0.0052, -0.0041, -0.0040, -0.0038, 0.0034, -0.0029, 0.0029, 0.0028,
|
||||||
|
-0.0028, 0.0023, 0.0021, 0.0019, 0.0018, -0.0017, 0.0015, 0.0014,
|
||||||
|
0.0012, -0.0012, 0.0010, -0.0010, 0.0037,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// MoonMaximumNorthDeclinations 指定年月内的所有月球最大北赤纬事件 / all maximum northern lunar declination events in the given Gregorian month.
|
||||||
|
func MoonMaximumNorthDeclinations(year int, month time.Month) []DeclinationEvent {
|
||||||
|
return moonMaximumDeclinationsInMonth(year, month, moonMaxDeclinationNorthCoefficients)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MoonMaximumSouthDeclinations 指定年月内的所有月球最大南赤纬事件 / all maximum southern lunar declination events in the given Gregorian month.
|
||||||
|
func MoonMaximumSouthDeclinations(year int, month time.Month) []DeclinationEvent {
|
||||||
|
return moonMaximumDeclinationsInMonth(year, month, moonMaxDeclinationSouthCoefficients)
|
||||||
|
}
|
||||||
|
|
||||||
|
// LastMoonMaximumNorthDeclination 指定时刻之前最近一次月球最大北赤纬 / last maximum northern lunar declination at or before jd.
|
||||||
|
func LastMoonMaximumNorthDeclination(jd float64) DeclinationEvent {
|
||||||
|
return moonMaximumDeclinationSearch(jd, moonMaxDeclinationNorthCoefficients, -1, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NextMoonMaximumNorthDeclination 指定时刻之后最近一次月球最大北赤纬 / next maximum northern lunar declination after jd.
|
||||||
|
func NextMoonMaximumNorthDeclination(jd float64) DeclinationEvent {
|
||||||
|
return moonMaximumDeclinationSearch(jd, moonMaxDeclinationNorthCoefficients, 1, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClosestMoonMaximumNorthDeclination 离指定时刻最近一次月球最大北赤纬 / closest maximum northern lunar declination to jd.
|
||||||
|
func ClosestMoonMaximumNorthDeclination(jd float64) DeclinationEvent {
|
||||||
|
return moonClosestMaximumDeclination(jd, moonMaxDeclinationNorthCoefficients)
|
||||||
|
}
|
||||||
|
|
||||||
|
// LastMoonMaximumSouthDeclination 指定时刻之前最近一次月球最大南赤纬 / last maximum southern lunar declination at or before jd.
|
||||||
|
func LastMoonMaximumSouthDeclination(jd float64) DeclinationEvent {
|
||||||
|
return moonMaximumDeclinationSearch(jd, moonMaxDeclinationSouthCoefficients, -1, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NextMoonMaximumSouthDeclination 指定时刻之后最近一次月球最大南赤纬 / next maximum southern lunar declination after jd.
|
||||||
|
func NextMoonMaximumSouthDeclination(jd float64) DeclinationEvent {
|
||||||
|
return moonMaximumDeclinationSearch(jd, moonMaxDeclinationSouthCoefficients, 1, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClosestMoonMaximumSouthDeclination 离指定时刻最近一次月球最大南赤纬 / closest maximum southern lunar declination to jd.
|
||||||
|
func ClosestMoonMaximumSouthDeclination(jd float64) DeclinationEvent {
|
||||||
|
return moonClosestMaximumDeclination(jd, moonMaxDeclinationSouthCoefficients)
|
||||||
|
}
|
||||||
|
|
||||||
|
func moonMaximumDeclinationsInMonth(year int, month time.Month, coeffs moonMaxDeclinationCoefficients) []DeclinationEvent {
|
||||||
|
startUTC := time.Date(year, month, 1, 0, 0, 0, 0, time.UTC)
|
||||||
|
endUTC := startUTC.AddDate(0, 1, 0)
|
||||||
|
startTT := TD2UT(Date2JDE(startUTC), true)
|
||||||
|
endTT := TD2UT(Date2JDE(endUTC), true)
|
||||||
|
|
||||||
|
kStart := int(math.Floor((startTT-coeffs.JDE0)/moonMaxDeclinationMeanMonthDays)) - 1
|
||||||
|
kEnd := int(math.Ceil((endTT-coeffs.JDE0)/moonMaxDeclinationMeanMonthDays)) + 1
|
||||||
|
|
||||||
|
cfg := apsisSearchConfig{
|
||||||
|
bracketHalfWidth: moonApsisBracketHalfWidth,
|
||||||
|
sampleStep: moonApsisSampleStep,
|
||||||
|
derivativeStep: moonApsisDerivativeStep,
|
||||||
|
toleranceDays: moonApsisToleranceDays,
|
||||||
|
maxIterations: moonApsisMaxIterations,
|
||||||
|
maximize: coeffs.sign > 0,
|
||||||
|
}
|
||||||
|
|
||||||
|
events := make([]DeclinationEvent, 0, 2)
|
||||||
|
for k := kStart; k <= kEnd; k++ {
|
||||||
|
event := moonMaximumDeclinationEvent(k, coeffs, cfg)
|
||||||
|
eventTimeUTC := JDE2DateByZone(event.JDE, time.UTC, false)
|
||||||
|
if eventTimeUTC.Before(startUTC) || !eventTimeUTC.Before(endUTC) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
events = append(events, event)
|
||||||
|
}
|
||||||
|
|
||||||
|
sort.Slice(events, func(i, j int) bool {
|
||||||
|
return events[i].JDE < events[j].JDE
|
||||||
|
})
|
||||||
|
return events
|
||||||
|
}
|
||||||
|
|
||||||
|
func moonMaximumDeclinationEvent(k int, coeffs moonMaxDeclinationCoefficients, cfg apsisSearchConfig) DeclinationEvent {
|
||||||
|
seedTT := moonMaximumDeclinationSeedTT(k, coeffs)
|
||||||
|
eventTT, declination := refineDistanceExtremum(seedTT, cfg, func(sampleTT float64) float64 {
|
||||||
|
return HMoonTrueDecN(sampleTT, -1)
|
||||||
|
})
|
||||||
|
return DeclinationEvent{
|
||||||
|
JDE: TD2UT(eventTT, false),
|
||||||
|
Declination: declination,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func moonMaximumDeclinationSearch(jd float64, coeffs moonMaxDeclinationCoefficients, direction int, includeCurrent bool) DeclinationEvent {
|
||||||
|
cfg := apsisSearchConfig{
|
||||||
|
bracketHalfWidth: moonApsisBracketHalfWidth,
|
||||||
|
sampleStep: moonApsisSampleStep,
|
||||||
|
derivativeStep: moonApsisDerivativeStep,
|
||||||
|
toleranceDays: moonApsisToleranceDays,
|
||||||
|
maxIterations: moonApsisMaxIterations,
|
||||||
|
maximize: coeffs.sign > 0,
|
||||||
|
}
|
||||||
|
targetTT := TD2UT(jd, true)
|
||||||
|
centerK := int(math.Round((targetTT - coeffs.JDE0) / moonMaxDeclinationMeanMonthDays))
|
||||||
|
|
||||||
|
found := false
|
||||||
|
bestDistance := math.Inf(1)
|
||||||
|
var best DeclinationEvent
|
||||||
|
for offset := -moonMaxDeclinationSearchSpan; offset <= moonMaxDeclinationSearchSpan; offset++ {
|
||||||
|
event := moonMaximumDeclinationEvent(centerK+offset, coeffs, cfg)
|
||||||
|
delta := event.JDE - jd
|
||||||
|
if !moonMaximumDeclinationMatchesDirection(delta, direction, includeCurrent) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
distance := math.Abs(delta)
|
||||||
|
if !found || distance < bestDistance || (distance == bestDistance && moonMaximumDeclinationEarlier(event, best)) {
|
||||||
|
best = event
|
||||||
|
bestDistance = distance
|
||||||
|
found = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return best
|
||||||
|
}
|
||||||
|
|
||||||
|
func moonClosestMaximumDeclination(jd float64, coeffs moonMaxDeclinationCoefficients) DeclinationEvent {
|
||||||
|
last := moonMaximumDeclinationSearch(jd, coeffs, -1, true)
|
||||||
|
next := moonMaximumDeclinationSearch(jd, coeffs, 1, false)
|
||||||
|
lastDistance := math.Abs(jd - last.JDE)
|
||||||
|
nextDistance := math.Abs(next.JDE - jd)
|
||||||
|
if lastDistance <= nextDistance {
|
||||||
|
return last
|
||||||
|
}
|
||||||
|
return next
|
||||||
|
}
|
||||||
|
|
||||||
|
func moonMaximumDeclinationMatchesDirection(delta float64, direction int, includeCurrent bool) bool {
|
||||||
|
switch direction {
|
||||||
|
case -1:
|
||||||
|
if includeCurrent {
|
||||||
|
return delta <= 0
|
||||||
|
}
|
||||||
|
return delta < 0
|
||||||
|
case 1:
|
||||||
|
if includeCurrent {
|
||||||
|
return delta >= 0
|
||||||
|
}
|
||||||
|
return delta > 0
|
||||||
|
default:
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func moonMaximumDeclinationEarlier(a, b DeclinationEvent) bool {
|
||||||
|
return a.JDE < b.JDE
|
||||||
|
}
|
||||||
|
|
||||||
|
func moonMaximumDeclinationSeedTT(k int, coeffs moonMaxDeclinationCoefficients) float64 {
|
||||||
|
cycle := float64(k)
|
||||||
|
T := cycle / moonMaxDeclinationBaseCycle
|
||||||
|
D := Limit360(coeffs.D0 + 333.0705546*cycle - 0.0004214*T*T + 0.00000011*T*T*T)
|
||||||
|
M := Limit360(coeffs.M0 + 26.9281592*cycle - 0.0000355*T*T - 0.0000001*T*T*T)
|
||||||
|
MP := Limit360(coeffs.MP0 + 356.9562794*cycle + 0.0103066*T*T + 0.00001251*T*T*T)
|
||||||
|
F := Limit360(coeffs.F0 + 1.4467807*cycle - 0.0020690*T*T - 0.00000215*T*T*T)
|
||||||
|
E := 1 - 0.002516*T - 0.0000074*T*T
|
||||||
|
|
||||||
|
return coeffs.JDE0 +
|
||||||
|
moonMaxDeclinationMeanMonthDays*cycle +
|
||||||
|
0.000119804*T*T -
|
||||||
|
0.000000141*T*T*T +
|
||||||
|
coeffs.tc[0]*Cos(F) +
|
||||||
|
coeffs.tc[1]*Sin(MP) +
|
||||||
|
coeffs.tc[2]*Sin(2*F) +
|
||||||
|
coeffs.tc[3]*Sin(2*D-MP) +
|
||||||
|
coeffs.tc[4]*Cos(MP-F) +
|
||||||
|
coeffs.tc[5]*Cos(MP+F) +
|
||||||
|
coeffs.tc[6]*Sin(2*D) +
|
||||||
|
coeffs.tc[7]*Sin(M)*E +
|
||||||
|
coeffs.tc[8]*Cos(3*F) +
|
||||||
|
coeffs.tc[9]*Sin(MP+2*F) +
|
||||||
|
coeffs.tc[10]*Cos(2*D-F) +
|
||||||
|
coeffs.tc[11]*Cos(2*D-MP-F) +
|
||||||
|
coeffs.tc[12]*Cos(2*D-MP+F) +
|
||||||
|
coeffs.tc[13]*Cos(2*D+F) +
|
||||||
|
coeffs.tc[14]*Sin(2*MP) +
|
||||||
|
coeffs.tc[15]*Sin(MP-2*F) +
|
||||||
|
coeffs.tc[16]*Cos(2*MP-F) +
|
||||||
|
coeffs.tc[17]*Sin(MP+3*F) +
|
||||||
|
coeffs.tc[18]*Sin(2*D-M-MP)*E +
|
||||||
|
coeffs.tc[19]*Cos(MP-2*F) +
|
||||||
|
coeffs.tc[20]*Sin(2*(D-MP)) +
|
||||||
|
coeffs.tc[21]*Sin(F) +
|
||||||
|
coeffs.tc[22]*Sin(2*D+MP) +
|
||||||
|
coeffs.tc[23]*Cos(MP+2*F) +
|
||||||
|
coeffs.tc[24]*Sin(2*D-M)*E +
|
||||||
|
coeffs.tc[25]*Sin(MP+F) +
|
||||||
|
coeffs.tc[26]*Sin(M-MP)*E +
|
||||||
|
coeffs.tc[27]*Sin(MP-3*F) +
|
||||||
|
coeffs.tc[28]*Sin(2*MP+F) +
|
||||||
|
coeffs.tc[29]*Cos(2*(D-MP)-F) +
|
||||||
|
coeffs.tc[30]*Sin(3*F) +
|
||||||
|
coeffs.tc[31]*Cos(MP+3*F) +
|
||||||
|
coeffs.tc[32]*Cos(2*MP) +
|
||||||
|
coeffs.tc[33]*Cos(2*D-MP) +
|
||||||
|
coeffs.tc[34]*Cos(2*D+MP+F) +
|
||||||
|
coeffs.tc[35]*Cos(MP) +
|
||||||
|
coeffs.tc[36]*Sin(3*MP+F) +
|
||||||
|
coeffs.tc[37]*Sin(2*D-MP+F) +
|
||||||
|
coeffs.tc[38]*Cos(2*(D-MP)) +
|
||||||
|
coeffs.tc[39]*Cos(D+F) +
|
||||||
|
coeffs.tc[40]*Sin(M+MP)*E +
|
||||||
|
coeffs.tc[41]*Sin(2*(D-F)) +
|
||||||
|
coeffs.tc[42]*Cos(2*MP+F) +
|
||||||
|
coeffs.tc[43]*Cos(3*MP+F)
|
||||||
|
}
|
||||||
@@ -0,0 +1,213 @@
|
|||||||
|
package basic
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"math"
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type moonMaxDeclinationSample struct {
|
||||||
|
Kind string `json:"kind"`
|
||||||
|
Year int `json:"year"`
|
||||||
|
Month int `json:"month"`
|
||||||
|
TimeUTC string `json:"time_utc"`
|
||||||
|
DeclinationDeg float64 `json:"declination_deg"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type moonMaxDeclinationMonthState struct {
|
||||||
|
north []DeclinationEvent
|
||||||
|
south []DeclinationEvent
|
||||||
|
northI int
|
||||||
|
southI int
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMoonMaximumDeclinationsMatchHorizonsBaseline(t *testing.T) {
|
||||||
|
// Baseline is generated from JPL Horizons by scripts/generate_moon_max_declination_baseline.sh.
|
||||||
|
data, err := os.ReadFile("testdata/moon_max_declination_baseline.json")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("read baseline: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var samples []moonMaxDeclinationSample
|
||||||
|
if err := json.Unmarshal(data, &samples); err != nil {
|
||||||
|
t.Fatalf("decode baseline: %v", err)
|
||||||
|
}
|
||||||
|
if len(samples) == 0 {
|
||||||
|
t.Fatal("empty moon maximum declination baseline")
|
||||||
|
}
|
||||||
|
|
||||||
|
const timeTolerance = 15 * time.Second
|
||||||
|
const declinationToleranceDeg = 0.0002
|
||||||
|
|
||||||
|
states := make(map[int]*moonMaxDeclinationMonthState)
|
||||||
|
var maxTimeDiff time.Duration
|
||||||
|
var maxDeclinationDiff float64
|
||||||
|
|
||||||
|
for _, sample := range samples {
|
||||||
|
wantTime, err := time.Parse(time.RFC3339Nano, sample.TimeUTC)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("parse sample time %q: %v", sample.TimeUTC, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
key := sample.Year*100 + sample.Month
|
||||||
|
state := states[key]
|
||||||
|
if state == nil {
|
||||||
|
state = &moonMaxDeclinationMonthState{
|
||||||
|
north: MoonMaximumNorthDeclinations(sample.Year, time.Month(sample.Month)),
|
||||||
|
south: MoonMaximumSouthDeclinations(sample.Year, time.Month(sample.Month)),
|
||||||
|
}
|
||||||
|
states[key] = state
|
||||||
|
}
|
||||||
|
|
||||||
|
var got DeclinationEvent
|
||||||
|
switch sample.Kind {
|
||||||
|
case "north":
|
||||||
|
if state.northI >= len(state.north) {
|
||||||
|
t.Fatalf("%04d-%02d missing north declination event #%d", sample.Year, sample.Month, state.northI+1)
|
||||||
|
}
|
||||||
|
got = state.north[state.northI]
|
||||||
|
state.northI++
|
||||||
|
case "south":
|
||||||
|
if state.southI >= len(state.south) {
|
||||||
|
t.Fatalf("%04d-%02d missing south declination event #%d", sample.Year, sample.Month, state.southI+1)
|
||||||
|
}
|
||||||
|
got = state.south[state.southI]
|
||||||
|
state.southI++
|
||||||
|
default:
|
||||||
|
t.Fatalf("unknown declination kind %q", sample.Kind)
|
||||||
|
}
|
||||||
|
|
||||||
|
gotTime := JDE2DateByZone(got.JDE, time.UTC, false)
|
||||||
|
timeDiff := gotTime.Sub(wantTime)
|
||||||
|
if timeDiff < 0 {
|
||||||
|
timeDiff = -timeDiff
|
||||||
|
}
|
||||||
|
if timeDiff > maxTimeDiff {
|
||||||
|
maxTimeDiff = timeDiff
|
||||||
|
}
|
||||||
|
if timeDiff > timeTolerance {
|
||||||
|
t.Fatalf("%s %04d-%02d time mismatch: got %s want %s tolerance %v", sample.Kind, sample.Year, sample.Month, gotTime.Format(time.RFC3339Nano), sample.TimeUTC, timeTolerance)
|
||||||
|
}
|
||||||
|
|
||||||
|
declinationDiff := math.Abs(got.Declination - sample.DeclinationDeg)
|
||||||
|
if declinationDiff > maxDeclinationDiff {
|
||||||
|
maxDeclinationDiff = declinationDiff
|
||||||
|
}
|
||||||
|
if declinationDiff > declinationToleranceDeg {
|
||||||
|
t.Fatalf("%s %04d-%02d declination mismatch: got %.8f want %.8f tolerance %.8f", sample.Kind, sample.Year, sample.Month, got.Declination, sample.DeclinationDeg, declinationToleranceDeg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for key, state := range states {
|
||||||
|
year := key / 100
|
||||||
|
month := key % 100
|
||||||
|
if state.northI != len(state.north) {
|
||||||
|
t.Fatalf("%04d-%02d unconsumed north events: got %d of %d", year, month, state.northI, len(state.north))
|
||||||
|
}
|
||||||
|
if state.southI != len(state.south) {
|
||||||
|
t.Fatalf("%04d-%02d unconsumed south events: got %d of %d", year, month, state.southI, len(state.south))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Logf("moon maximum declination max diff: time=%v declination=%.8f deg", maxTimeDiff, maxDeclinationDiff)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMoonMaximumDeclinationSignsAndOrder(t *testing.T) {
|
||||||
|
north := MoonMaximumNorthDeclinations(2026, time.January)
|
||||||
|
south := MoonMaximumSouthDeclinations(2026, time.January)
|
||||||
|
if len(north) == 0 || len(south) == 0 {
|
||||||
|
t.Fatalf("expected both north and south events in 2026-01, got north=%d south=%d", len(north), len(south))
|
||||||
|
}
|
||||||
|
for i, event := range north {
|
||||||
|
if event.Declination <= 0 {
|
||||||
|
t.Fatalf("north event #%d should be positive, got %.8f", i+1, event.Declination)
|
||||||
|
}
|
||||||
|
if i > 0 && !(north[i-1].JDE < event.JDE) {
|
||||||
|
t.Fatalf("north events not strictly increasing: %.12f then %.12f", north[i-1].JDE, event.JDE)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for i, event := range south {
|
||||||
|
if event.Declination >= 0 {
|
||||||
|
t.Fatalf("south event #%d should be negative, got %.8f", i+1, event.Declination)
|
||||||
|
}
|
||||||
|
if i > 0 && !(south[i-1].JDE < event.JDE) {
|
||||||
|
t.Fatalf("south events not strictly increasing: %.12f then %.12f", south[i-1].JDE, event.JDE)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMoonMaximumDeclinationSearchMatchesMonthlyEvents(t *testing.T) {
|
||||||
|
query := time.Date(2026, time.January, 10, 0, 0, 0, 0, time.UTC)
|
||||||
|
queryJDE := Date2JDE(query)
|
||||||
|
|
||||||
|
northEvents := append([]DeclinationEvent{}, MoonMaximumNorthDeclinations(2025, time.December)...)
|
||||||
|
northEvents = append(northEvents, MoonMaximumNorthDeclinations(2026, time.January)...)
|
||||||
|
northEvents = append(northEvents, MoonMaximumNorthDeclinations(2026, time.February)...)
|
||||||
|
|
||||||
|
southEvents := append([]DeclinationEvent{}, MoonMaximumSouthDeclinations(2025, time.December)...)
|
||||||
|
southEvents = append(southEvents, MoonMaximumSouthDeclinations(2026, time.January)...)
|
||||||
|
southEvents = append(southEvents, MoonMaximumSouthDeclinations(2026, time.February)...)
|
||||||
|
|
||||||
|
assertSameDeclinationEvent(t, "last north", LastMoonMaximumNorthDeclination(queryJDE), expectedDirectionalDeclinationEvent(northEvents, queryJDE, -1, true))
|
||||||
|
assertSameDeclinationEvent(t, "next north", NextMoonMaximumNorthDeclination(queryJDE), expectedDirectionalDeclinationEvent(northEvents, queryJDE, 1, false))
|
||||||
|
assertSameDeclinationEvent(t, "closest north", ClosestMoonMaximumNorthDeclination(queryJDE), expectedClosestDeclinationEvent(northEvents, queryJDE))
|
||||||
|
|
||||||
|
assertSameDeclinationEvent(t, "last south", LastMoonMaximumSouthDeclination(queryJDE), expectedDirectionalDeclinationEvent(southEvents, queryJDE, -1, true))
|
||||||
|
assertSameDeclinationEvent(t, "next south", NextMoonMaximumSouthDeclination(queryJDE), expectedDirectionalDeclinationEvent(southEvents, queryJDE, 1, false))
|
||||||
|
assertSameDeclinationEvent(t, "closest south", ClosestMoonMaximumSouthDeclination(queryJDE), expectedClosestDeclinationEvent(southEvents, queryJDE))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMoonMaximumDeclinationSearchAtExactEventTime(t *testing.T) {
|
||||||
|
north := MoonMaximumNorthDeclinations(2026, time.January)
|
||||||
|
if len(north) < 2 {
|
||||||
|
t.Fatalf("expected at least two north events spanning Jan 2026 search window, got %d", len(north))
|
||||||
|
}
|
||||||
|
|
||||||
|
exactJDE := north[0].JDE
|
||||||
|
assertSameDeclinationEvent(t, "exact last north", LastMoonMaximumNorthDeclination(exactJDE), north[0])
|
||||||
|
assertSameDeclinationEvent(t, "exact closest north", ClosestMoonMaximumNorthDeclination(exactJDE), north[0])
|
||||||
|
assertSameDeclinationEvent(t, "exact next north", NextMoonMaximumNorthDeclination(exactJDE), north[1])
|
||||||
|
}
|
||||||
|
|
||||||
|
func assertSameDeclinationEvent(t *testing.T, name string, got, want DeclinationEvent) {
|
||||||
|
t.Helper()
|
||||||
|
if math.Abs(got.JDE-want.JDE) > 1e-12 {
|
||||||
|
t.Fatalf("%s JDE mismatch: got %.12f want %.12f", name, got.JDE, want.JDE)
|
||||||
|
}
|
||||||
|
if math.Float64bits(got.Declination) != math.Float64bits(want.Declination) {
|
||||||
|
t.Fatalf("%s declination mismatch: got %.12f want %.12f", name, got.Declination, want.Declination)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func expectedDirectionalDeclinationEvent(events []DeclinationEvent, queryJDE float64, direction int, includeCurrent bool) DeclinationEvent {
|
||||||
|
var (
|
||||||
|
found bool
|
||||||
|
best DeclinationEvent
|
||||||
|
)
|
||||||
|
for _, event := range events {
|
||||||
|
delta := event.JDE - queryJDE
|
||||||
|
if !moonMaximumDeclinationMatchesDirection(delta, direction, includeCurrent) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !found {
|
||||||
|
best = event
|
||||||
|
found = true
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if math.Abs(delta) < math.Abs(best.JDE-queryJDE) || (math.Abs(delta) == math.Abs(best.JDE-queryJDE) && event.JDE < best.JDE) {
|
||||||
|
best = event
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return best
|
||||||
|
}
|
||||||
|
|
||||||
|
func expectedClosestDeclinationEvent(events []DeclinationEvent, queryJDE float64) DeclinationEvent {
|
||||||
|
last := expectedDirectionalDeclinationEvent(events, queryJDE, -1, true)
|
||||||
|
next := expectedDirectionalDeclinationEvent(events, queryJDE, 1, false)
|
||||||
|
if math.Abs(queryJDE-last.JDE) <= math.Abs(next.JDE-queryJDE) {
|
||||||
|
return last
|
||||||
|
}
|
||||||
|
return next
|
||||||
|
}
|
||||||
@@ -0,0 +1,347 @@
|
|||||||
|
package basic
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math"
|
||||||
|
|
||||||
|
. "b612.me/astro/tools"
|
||||||
|
)
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 月球方位角
|
||||||
|
*/
|
||||||
|
func MoonAzimuth(jd, lon, lat, tz float64) float64 {
|
||||||
|
|
||||||
|
//tmp := (tz*15 - lon) * 4 / 60
|
||||||
|
calcjd := TD2UT(jd-tz/24, true)
|
||||||
|
ra := MoonTrueRa(calcjd)
|
||||||
|
dec := MoonTrueDec(calcjd)
|
||||||
|
away := MoonAway(calcjd) / 149597870.7
|
||||||
|
ndec := TopocentricDec(ra, dec, lat, lon, jd-tz/24, away, 0)
|
||||||
|
nra := TopocentricRa(ra, dec, lat, lon, jd-tz/24, away, 0)
|
||||||
|
calcjd = jd - tz/24
|
||||||
|
st := Limit360(ApparentSiderealTime(calcjd)*15 + lon)
|
||||||
|
hourAngle := Limit360(st - nra)
|
||||||
|
tmp2 := Sin(hourAngle) / (Cos(hourAngle)*Sin(lat) - Tan(ndec)*Cos(lat))
|
||||||
|
azimuth := ArcTan(tmp2)
|
||||||
|
if azimuth < 0 {
|
||||||
|
if hourAngle/15 < 12 {
|
||||||
|
return azimuth + 360
|
||||||
|
} else {
|
||||||
|
return azimuth + 180
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if hourAngle/15 < 12 {
|
||||||
|
return azimuth + 180
|
||||||
|
} else {
|
||||||
|
return azimuth
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func MoonHeight(jd, lon, lat, tz float64) float64 {
|
||||||
|
// tmp := (tz*15 - lon) * 4 / 60
|
||||||
|
//truejd=jd-tmp/24;
|
||||||
|
calcjd := TD2UT(jd-tz/24, true)
|
||||||
|
ra := MoonTrueRa(calcjd)
|
||||||
|
dec := MoonTrueDec(calcjd)
|
||||||
|
away := MoonAway(calcjd) / 149597870.7
|
||||||
|
ndec := TopocentricDec(ra, dec, lat, lon, jd-tz/24, away, 0)
|
||||||
|
nra := TopocentricRa(ra, dec, lat, lon, jd-tz/24, away, 0)
|
||||||
|
calcjd = jd - tz/24
|
||||||
|
st := Limit360(ApparentSiderealTime(calcjd)*15 + lon)
|
||||||
|
hourAngle := Limit360(st - nra)
|
||||||
|
tmp2 := Sin(lat)*Sin(ndec) + Cos(ndec)*Cos(lat)*Cos(hourAngle)
|
||||||
|
return ArcSin(tmp2)
|
||||||
|
}
|
||||||
|
|
||||||
|
func HMoonAzimuth(jd, lon, lat, tz float64) float64 {
|
||||||
|
return HMoonAzimuthN(jd, lon, lat, tz, -1)
|
||||||
|
}
|
||||||
|
|
||||||
|
func HMoonAzimuthN(jd, lon, lat, tz float64, n int) float64 {
|
||||||
|
calcjd := TD2UT(jd-tz/24, true)
|
||||||
|
ra := HMoonTrueRaN(calcjd, n)
|
||||||
|
dec := HMoonTrueDecN(calcjd, n)
|
||||||
|
away := HMoonAwayN(calcjd, n) / 149597870.7
|
||||||
|
ndec := TopocentricDec(ra, dec, lat, lon, jd-tz/24, away, 0)
|
||||||
|
nra := TopocentricRa(ra, dec, lat, lon, jd-tz/24, away, 0)
|
||||||
|
calcjd = jd - tz/24
|
||||||
|
st := Limit360(ApparentSiderealTime(calcjd)*15 + lon)
|
||||||
|
hourAngle := Limit360(st - nra)
|
||||||
|
tmp2 := Sin(hourAngle) / (Cos(hourAngle)*Sin(lat) - Tan(ndec)*Cos(lat))
|
||||||
|
azimuth := ArcTan(tmp2)
|
||||||
|
if azimuth < 0 {
|
||||||
|
if hourAngle/15 < 12 {
|
||||||
|
return azimuth + 360
|
||||||
|
} else {
|
||||||
|
return azimuth + 180
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if hourAngle/15 < 12 {
|
||||||
|
return azimuth + 180
|
||||||
|
} else {
|
||||||
|
return azimuth
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
func HMoonHeight(jd, lon, lat, tz float64) float64 {
|
||||||
|
return HMoonHeightN(jd, lon, lat, tz, -1)
|
||||||
|
}
|
||||||
|
|
||||||
|
func HMoonHeightN(jd, lon, lat, tz float64, n int) float64 {
|
||||||
|
calcjd := TD2UT(jd-tz/24, true)
|
||||||
|
ra, dec := HMoonTrueRaDecN(calcjd, n)
|
||||||
|
away := HMoonAwayN(calcjd, n) / 149597870.7
|
||||||
|
nra, ndec := TopocentricRaDec(ra, dec, lat, lon, calcjd, away, 0)
|
||||||
|
calcjd = jd - tz/24
|
||||||
|
st := Limit360(ApparentSiderealTime(calcjd)*15 + lon)
|
||||||
|
hourAngle := Limit360(st - nra)
|
||||||
|
tmp2 := Sin(lat)*Sin(ndec) + Cos(ndec)*Cos(lat)*Cos(hourAngle)
|
||||||
|
return ArcSin(tmp2)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 废弃
|
||||||
|
func GetMoonTZTime(jd, lon, lat, tz float64) float64 { //实际中天时间{
|
||||||
|
jd = math.Floor(jd) + 0.5
|
||||||
|
ttm := MoonTimeAngle(jd, lon, lat, tz)
|
||||||
|
if ttm > 0 && ttm < 180 {
|
||||||
|
jd += 0.5
|
||||||
|
}
|
||||||
|
estimateJD := jd
|
||||||
|
for {
|
||||||
|
prevJD := estimateJD
|
||||||
|
stDegree := MoonTimeAngle(prevJD, lon, lat, tz) - 359.599
|
||||||
|
stDegreep := (MoonTimeAngle(prevJD+0.000005, lon, lat, tz) - MoonTimeAngle(prevJD-0.000005, lon, lat, tz)) / 0.00001
|
||||||
|
estimateJD = prevJD - stDegree/stDegreep
|
||||||
|
if math.Abs(estimateJD-prevJD) <= 0.00001 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return estimateJD
|
||||||
|
}
|
||||||
|
|
||||||
|
func MoonCulminationTime(jde, lon, lat, timezone float64) float64 {
|
||||||
|
//jde 世界时,非力学时,当地时区 0时,无需转换力学时
|
||||||
|
//ra,dec 瞬时天球座标,非J2000等时间天球坐标
|
||||||
|
jde = math.Floor(jde) + 0.5
|
||||||
|
estimateJD := jde + Limit360(360-MoonTimeAngle(jde, lon, lat, timezone))/15.0/24.0/0.9
|
||||||
|
limitHA := func(jde, lon, timezone float64) float64 {
|
||||||
|
ha := MoonTimeAngle(jde, lon, lat, timezone)
|
||||||
|
if ha < 180 {
|
||||||
|
ha += 360
|
||||||
|
}
|
||||||
|
return ha
|
||||||
|
}
|
||||||
|
for {
|
||||||
|
prevJD := estimateJD
|
||||||
|
stDegree := limitHA(prevJD, lon, timezone) - 360
|
||||||
|
stDegreep := (limitHA(prevJD+0.000005, lon, timezone) - limitHA(prevJD-0.000005, lon, timezone)) / 0.00001
|
||||||
|
estimateJD = prevJD - stDegree/stDegreep
|
||||||
|
if math.Abs(estimateJD-prevJD) <= 0.00001 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return estimateJD
|
||||||
|
}
|
||||||
|
|
||||||
|
func MoonTimeAngle(jd, lon, lat, tz float64) float64 {
|
||||||
|
startime := Limit360(ApparentSiderealTime(jd-tz/24)*15 + lon)
|
||||||
|
timeangle := startime - HMoonApparentRa(jd, lon, lat, tz)
|
||||||
|
if timeangle < 0 {
|
||||||
|
timeangle += 360
|
||||||
|
}
|
||||||
|
return timeangle
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetMoonRiseTime(julianDay, longitude, latitude, timeZone, zenithShift, height float64) (float64, error) {
|
||||||
|
originalTimeZone := timeZone
|
||||||
|
timeZone = longitude / 15
|
||||||
|
var timeToMeridian float64
|
||||||
|
julianDayZero := math.Floor(julianDay) + 0.5
|
||||||
|
//julianDay = math.Floor(julianDay) + 0.5 - originalTimeZone/24 + timeZone/24 // 求0时JDE
|
||||||
|
//fix:这里时间分界线应当以传入的时区为准,不应当使用当地时区,否则在0时的判断会出错
|
||||||
|
julianDay = math.Floor(julianDay) + 0.5
|
||||||
|
estimatedTime := julianDay
|
||||||
|
moonHeight := MoonHeight(julianDay, longitude, latitude, originalTimeZone) // 求此时月亮高度
|
||||||
|
|
||||||
|
moonAngle := StandardAltitudeMoon(zenithShift, height, latitude)
|
||||||
|
|
||||||
|
moonAngleTime := MoonTimeAngle(julianDay, longitude, latitude, originalTimeZone)
|
||||||
|
|
||||||
|
if moonHeight-moonAngle > 0 { // 月亮在地平线上或在落下与下中天之间
|
||||||
|
if moonAngleTime > 180 {
|
||||||
|
timeToMeridian = (180 + 360 - moonAngleTime) / 15
|
||||||
|
} else {
|
||||||
|
timeToMeridian = (180 - moonAngleTime) / 15
|
||||||
|
}
|
||||||
|
estimatedTime += (timeToMeridian/24 + (timeToMeridian/24*12.0)/15.0/24.0)
|
||||||
|
}
|
||||||
|
|
||||||
|
if moonHeight-moonAngle < 0 && moonAngleTime > 180 {
|
||||||
|
timeToMeridian = (180 - moonAngleTime) / 15
|
||||||
|
estimatedTime += (timeToMeridian/24 + (timeToMeridian/24*12.0)/15.0/24.0)
|
||||||
|
} else if moonHeight-moonAngle < 0 && moonAngleTime < 180 {
|
||||||
|
timeToMeridian = (180 - moonAngleTime) / 15
|
||||||
|
estimatedTime += (timeToMeridian/24 + (timeToMeridian/24*12.0)/15.0/24.0)
|
||||||
|
}
|
||||||
|
|
||||||
|
currentAngle := MoonTimeAngle(estimatedTime, longitude, latitude, timeZone)
|
||||||
|
if math.Abs(currentAngle-180) > 0.5 {
|
||||||
|
estimatedTime += (180 - currentAngle) * 4.0 / 60.0 / 24.0
|
||||||
|
}
|
||||||
|
|
||||||
|
currentHeight := HMoonHeight(estimatedTime, longitude, latitude, timeZone)
|
||||||
|
if !(currentHeight < -10 && math.Abs(latitude) < 60) {
|
||||||
|
if currentHeight > moonAngle {
|
||||||
|
return 0, ErrNeverSet
|
||||||
|
}
|
||||||
|
checkTime := estimatedTime + 12.0/24.0 + 6.0/15.0/24.0
|
||||||
|
checkAngle := MoonTimeAngle(checkTime, longitude, latitude, timeZone)
|
||||||
|
if checkAngle < 90 {
|
||||||
|
checkAngle += 360
|
||||||
|
}
|
||||||
|
checkTime += (360 - checkAngle) * 4.0 / 60.0 / 24.0
|
||||||
|
if HMoonHeight(checkTime, longitude, latitude, timeZone) < moonAngle {
|
||||||
|
return 0, ErrNeverRise
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
moonDeclination := MoonApparentDec(estimatedTime, longitude, latitude, timeZone)
|
||||||
|
tmp := (Sin(moonAngle) - Sin(moonDeclination)*Sin(latitude)) / (Cos(moonDeclination) * Cos(latitude))
|
||||||
|
|
||||||
|
if math.Abs(tmp) <= 1 && latitude < 85 {
|
||||||
|
hourAngle := (180 - ArcCos(tmp)) / 15
|
||||||
|
estimatedTime += hourAngle/24.00 + hourAngle/33.00/15.00
|
||||||
|
} else {
|
||||||
|
i := 0
|
||||||
|
for MoonHeight(estimatedTime, longitude, latitude, timeZone) < moonAngle {
|
||||||
|
i++
|
||||||
|
estimatedTime += 15.0 / 60.0 / 24.0
|
||||||
|
if i > 48 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 使用牛顿迭代法求精确解
|
||||||
|
estimatedTime = moonRiseSetNewtonRaphsonIteration(estimatedTime, longitude, latitude, timeZone, moonAngle, HMoonHeight, 0.00002)
|
||||||
|
|
||||||
|
estimatedTime = estimatedTime - timeZone/24 + originalTimeZone/24
|
||||||
|
|
||||||
|
if estimatedTime > julianDayZero+1 || estimatedTime < julianDayZero {
|
||||||
|
return 0, ErrNotOnThisDate
|
||||||
|
}
|
||||||
|
return estimatedTime, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetMoonSetTime(julianDay, longitude, latitude, timeZone, zenithShift, height float64) (float64, error) {
|
||||||
|
originalTimeZone := timeZone
|
||||||
|
timeZone = longitude / 15
|
||||||
|
var timeToMeridian float64
|
||||||
|
julianDayZero := math.Floor(julianDay) + 0.5
|
||||||
|
//julianDay = math.Floor(julianDay) + 0.5 - originalTimeZone/24 + timeZone/24 // 求0时JDE
|
||||||
|
//fix:这里时间分界线应当以传入的时区为准,不应当使用当地时区,否则在0时的判断会出错
|
||||||
|
julianDay = math.Floor(julianDay) + 0.5
|
||||||
|
estimatedTime := julianDay
|
||||||
|
moonHeight := MoonHeight(julianDay, longitude, latitude, originalTimeZone) // 求此时月亮高度
|
||||||
|
|
||||||
|
moonAngle := StandardAltitudeMoon(zenithShift, height, latitude)
|
||||||
|
|
||||||
|
moonAngleTime := MoonTimeAngle(julianDay, longitude, latitude, originalTimeZone)
|
||||||
|
|
||||||
|
if moonHeight-moonAngle < 0 {
|
||||||
|
timeToMeridian = (360 - moonAngleTime) / 15
|
||||||
|
estimatedTime += (timeToMeridian/24 + (timeToMeridian/24.0*12.0)/15.0/24.0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 月亮在地平线上或在落下与下中天之间
|
||||||
|
if moonHeight-moonAngle > 0 && moonAngleTime < 180 {
|
||||||
|
timeToMeridian = (-moonAngleTime) / 15
|
||||||
|
estimatedTime += (timeToMeridian/24.0 + (timeToMeridian/24.0*12.0)/15.0/24.0)
|
||||||
|
} else if moonHeight-moonAngle > 0 {
|
||||||
|
timeToMeridian = (360 - moonAngleTime) / 15
|
||||||
|
estimatedTime += (timeToMeridian/24.0 + (timeToMeridian/24.0*12.0)/15.0/24.0)
|
||||||
|
}
|
||||||
|
|
||||||
|
currentAngle := MoonTimeAngle(estimatedTime, longitude, latitude, timeZone)
|
||||||
|
if currentAngle < 180 {
|
||||||
|
currentAngle += 360
|
||||||
|
}
|
||||||
|
if math.Abs(currentAngle-360) > 0.5 {
|
||||||
|
estimatedTime += (360 - currentAngle) * 4.0 / 60.0 / 24.0
|
||||||
|
}
|
||||||
|
|
||||||
|
// estimatedTime = 月球中天时间
|
||||||
|
currentHeight := HMoonHeight(estimatedTime, longitude, latitude, timeZone)
|
||||||
|
if !(currentHeight > 10 && math.Abs(latitude) < 60) {
|
||||||
|
if currentHeight < moonAngle {
|
||||||
|
return 0, ErrNeverRise
|
||||||
|
}
|
||||||
|
checkTime := estimatedTime + 12.0/24.0 + 6.0/15.0/24.0
|
||||||
|
angleSubtraction := 180 - MoonTimeAngle(checkTime, longitude, latitude, timeZone)
|
||||||
|
checkTime += angleSubtraction * 4.0 / 60.0 / 24.0
|
||||||
|
if HMoonHeight(checkTime, longitude, latitude, timeZone) > moonAngle {
|
||||||
|
return 0, ErrNeverSet
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
moonDeclination := MoonApparentDec(estimatedTime, longitude, latitude, timeZone)
|
||||||
|
tmp := (Sin(moonAngle) - Sin(moonDeclination)*Sin(latitude)) / (Cos(moonDeclination) * Cos(latitude))
|
||||||
|
|
||||||
|
if math.Abs(tmp) <= 1 && latitude < 85 {
|
||||||
|
hourAngle := (ArcCos(tmp)) / 15.0
|
||||||
|
estimatedTime += hourAngle/24 + hourAngle/33.0/15.0
|
||||||
|
} else {
|
||||||
|
i := 0
|
||||||
|
for MoonHeight(estimatedTime, longitude, latitude, timeZone) > moonAngle {
|
||||||
|
i++
|
||||||
|
estimatedTime += 15.0 / 60.0 / 24.0
|
||||||
|
if i > 48 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 使用牛顿迭代法求精确解
|
||||||
|
estimatedTime = moonRiseSetNewtonRaphsonIteration(estimatedTime, longitude, latitude, timeZone, moonAngle, HMoonHeight, 0.00002)
|
||||||
|
|
||||||
|
estimatedTime = estimatedTime - timeZone/24 + originalTimeZone/24
|
||||||
|
|
||||||
|
if estimatedTime > julianDayZero+1 || estimatedTime < julianDayZero {
|
||||||
|
return 0, ErrNotOnThisDate
|
||||||
|
}
|
||||||
|
return estimatedTime, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// heightFunction 高度函数类型定义,用于牛顿迭代法
|
||||||
|
type heightFunction func(time, longitude, latitude, timeZone float64) float64
|
||||||
|
|
||||||
|
// moonRiseSetNewtonRaphsonIteration 牛顿-拉夫逊迭代法求解天体高度方程
|
||||||
|
func moonRiseSetNewtonRaphsonIteration(initialTime, longitude, latitude, timeZone, targetAngle float64,
|
||||||
|
heightFunc heightFunction, tolerance float64) float64 {
|
||||||
|
const derivativeStep = 0.000005
|
||||||
|
|
||||||
|
currentTime := initialTime
|
||||||
|
|
||||||
|
for {
|
||||||
|
previousTime := currentTime
|
||||||
|
|
||||||
|
// 计算函数值:f(t) = height(t) - targetAngle
|
||||||
|
functionValue := heightFunc(previousTime, longitude, latitude, timeZone) - targetAngle
|
||||||
|
|
||||||
|
// 计算导数:f'(t) ≈ (f(t+h) - f(t-h)) / (2h)
|
||||||
|
derivative := (heightFunc(previousTime+derivativeStep, longitude, latitude, timeZone) -
|
||||||
|
heightFunc(previousTime-derivativeStep, longitude, latitude, timeZone)) / (2 * derivativeStep)
|
||||||
|
|
||||||
|
// 牛顿-拉夫逊公式:t_new = t_old - f(t) / f'(t)
|
||||||
|
currentTime = previousTime - functionValue/derivative
|
||||||
|
|
||||||
|
// 检查收敛
|
||||||
|
if math.Abs(currentTime-previousTime) <= tolerance {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return currentTime
|
||||||
|
}
|
||||||
@@ -0,0 +1,209 @@
|
|||||||
|
package basic
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math"
|
||||||
|
|
||||||
|
. "b612.me/astro/tools"
|
||||||
|
)
|
||||||
|
|
||||||
|
func MoonPhase(jd float64) float64 {
|
||||||
|
moonBo := HMoonTrueBo(jd)
|
||||||
|
sunLo := HSunApparentLo(jd)
|
||||||
|
moonLo := HMoonApparentLo(jd)
|
||||||
|
tmp := Cos(moonBo) * Cos(sunLo-moonLo)
|
||||||
|
earthSunDistance := Distance(jd) * 149597870.691
|
||||||
|
i := earthSunDistance * Sin(ArcCos(tmp)) / (HMoonAway(jd) - earthSunDistance*tmp)
|
||||||
|
i = ArcTan(i)
|
||||||
|
if i < 0 {
|
||||||
|
i += 180
|
||||||
|
}
|
||||||
|
if i > 180 {
|
||||||
|
i -= 180
|
||||||
|
}
|
||||||
|
k := (1 + Cos(i)) / 2
|
||||||
|
return k
|
||||||
|
}
|
||||||
|
|
||||||
|
func SunMoonSeek(jde float64, degree float64) float64 {
|
||||||
|
p := HMoonApparentLo(jde) - (HSunApparentLo(jde)) - degree
|
||||||
|
for p < -180 {
|
||||||
|
p += 360
|
||||||
|
}
|
||||||
|
for p > 180 {
|
||||||
|
p -= 360
|
||||||
|
}
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
func CalcMoonSHByJDE(jde float64, phaseType int) float64 {
|
||||||
|
phaseType = phaseType * 180
|
||||||
|
estimateJD := jde
|
||||||
|
for {
|
||||||
|
prevJD := estimateJD
|
||||||
|
stDegree := SunMoonSeek(prevJD, float64(phaseType))
|
||||||
|
stDegreep := (SunMoonSeek(prevJD+0.000005, float64(phaseType)) - SunMoonSeek(prevJD-0.000005, float64(phaseType))) / 0.00001
|
||||||
|
estimateJD = prevJD - stDegree/stDegreep
|
||||||
|
if math.Abs(estimateJD-prevJD) <= 0.00001 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return estimateJD
|
||||||
|
}
|
||||||
|
|
||||||
|
func CalcMoonSH(year float64, phaseType int) float64 {
|
||||||
|
jde := CalcMoonS(year, phaseType)
|
||||||
|
phaseType = phaseType * 180
|
||||||
|
estimateJD := jde
|
||||||
|
for {
|
||||||
|
prevJD := estimateJD
|
||||||
|
stDegree := SunMoonSeek(prevJD, float64(phaseType))
|
||||||
|
stDegreep := (SunMoonSeek(prevJD+0.000005, float64(phaseType)) - SunMoonSeek(prevJD-0.000005, float64(phaseType))) / 0.00001
|
||||||
|
estimateJD = prevJD - stDegree/stDegreep
|
||||||
|
if math.Abs(estimateJD-prevJD) <= 0.00001 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return estimateJD
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* C=0朔月时刻 =1 望月
|
||||||
|
*/
|
||||||
|
func CalcMoonS(year float64, phaseType int) float64 {
|
||||||
|
k := math.Floor((year - 2000) * 12.36827)
|
||||||
|
if phaseType == 1 {
|
||||||
|
k += 0.5
|
||||||
|
}
|
||||||
|
T := k / 1236.85
|
||||||
|
jde := 2451550.09765 + 29.530588853*k + 0.0001337*T*T - 0.000000150*T*T*T + 0.00000000073*T*T*T*T
|
||||||
|
//太阳平近点角:
|
||||||
|
M := Limit360(2.5534 + 29.10535669*k - 0.0000218*T*T - 0.00000011*T*T*T)
|
||||||
|
//月亮的平近点角:
|
||||||
|
N := Limit360(201.5643 + 385.81693528*k + 0.0107438*T*T + 0.00001239*T*T*T - 0.000000058*T*T*T*T)
|
||||||
|
//月亮的纬度参数:
|
||||||
|
F := Limit360(160.7108 + 390.67050274*k - 0.0016341*T*T - 0.00000227*T*T*T + 0.000000011*T*T*T*T)
|
||||||
|
//月亮轨道升交点经度:
|
||||||
|
O := Limit360(124.7746 - 1.56375580*k + 0.0020691*T*T + 0.00000215*T*T*T)
|
||||||
|
E := 1 - 0.002516*T - 0.0000074*T*T
|
||||||
|
//die(E." ".M." ".N." ".F." ".O);
|
||||||
|
angles := []float64{N, M, 2 * N, 2 * F, N - M, N + M, 2 * M, N - 2*F, N + 2*F, 2*N + M, 3 * N, M + 2*F, M - 2*F, 2*N - M, O, N + 2*M, 2*N - 2*F, 3 * M, N + M - 2*F, 2*N + 2*F, N + M + 2*F, N - M + 2*F, N - M - 2*F, 3*N + M, 4 * N}
|
||||||
|
var coeffs []float64
|
||||||
|
if phaseType == 0 {
|
||||||
|
coeffs = []float64{-0.40720, 0.17241 * E, 0.01608, 0.01039, 0.00739 * E, -0.00514 * E, 0.00208 * E * E, -0.00111, -0.00057, 0.00056 * E, -0.00042, 0.00042 * E, 0.00038 * E, -0.00024 * E, -0.00017, -0.00007, 0.00004, 0.00004, 0.00003, 0.00003, -0.00003, 0.00003, -0.00002, -0.00002, 0.00002}
|
||||||
|
} else {
|
||||||
|
coeffs = []float64{-0.40614, 0.17302 * E, 0.01614, 0.01043, 0.00734 * E, -0.00515 * E, 0.00209 * E * E, -0.00111, -0.00057, 0.00056 * E, -0.00042, 0.00042 * E, 0.00038 * E, -0.00024 * E, -0.00017, -0.00007, 0.00004, 0.00004, 0.00003, 0.00003, -0.00003, 0.00003, -0.00002, -0.00002, 0.00002}
|
||||||
|
}
|
||||||
|
var correction float64
|
||||||
|
for idx, angle := range angles {
|
||||||
|
correction += Sin(angle) * coeffs[idx]
|
||||||
|
}
|
||||||
|
//die(tmp);
|
||||||
|
A1 := 299.77 + 0.107408*k - 0.009173*T*T
|
||||||
|
A2 := 251.88 + 0.016321*k
|
||||||
|
A3 := 251.83 + 26.651886*k
|
||||||
|
A4 := 349.42 + 36.412478*k
|
||||||
|
A5 := 84.66 + 18.206239*k
|
||||||
|
A6 := 141.74 + 53.303771*k
|
||||||
|
A7 := 207.14 + 2.453732*k
|
||||||
|
A8 := 154.84 + 7.306860*k
|
||||||
|
A9 := 34.52 + 27.261239*k
|
||||||
|
A10 := 207.19 + 0.121824*k
|
||||||
|
A11 := 291.34 + 1.844379*k
|
||||||
|
A12 := 161.72 + 24.198154*k
|
||||||
|
A13 := 239.56 + 25.513099*k
|
||||||
|
A14 := 331.55 + 3.592518*k
|
||||||
|
planetaryCorrection := 325*Sin(A1) + 165*Sin(A2) + 164*Sin(A3) + 126*Sin(A4) + 110*Sin(A5) + 62*Sin(A6) + 60*Sin(A7) + 56*Sin(A8) + 47*Sin(A9) + 42*Sin(A10) + 40*Sin(A11) + 37*Sin(A12) + 35*Sin(A13) + 23*Sin(A14)
|
||||||
|
planetaryCorrection /= 1000000
|
||||||
|
jde = jde + planetaryCorrection + correction
|
||||||
|
return jde
|
||||||
|
}
|
||||||
|
|
||||||
|
func CalcMoonXHByJDE(jde float64, quarterType int) float64 {
|
||||||
|
if quarterType == 0 {
|
||||||
|
quarterType = 90
|
||||||
|
} else {
|
||||||
|
quarterType = -90
|
||||||
|
}
|
||||||
|
estimateJD := jde
|
||||||
|
for {
|
||||||
|
prevJD := estimateJD
|
||||||
|
stDegree := SunMoonSeek(prevJD, float64(quarterType))
|
||||||
|
stDegreep := (SunMoonSeek(prevJD+0.000005, float64(quarterType)) - SunMoonSeek(prevJD-0.000005, float64(quarterType))) / 0.00001
|
||||||
|
estimateJD = prevJD - stDegree/stDegreep
|
||||||
|
if math.Abs(estimateJD-prevJD) <= 0.00001 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return estimateJD
|
||||||
|
}
|
||||||
|
|
||||||
|
func CalcMoonXH(year float64, quarterType int) float64 {
|
||||||
|
jde := CalcMoonX(year, quarterType)
|
||||||
|
if quarterType == 0 {
|
||||||
|
quarterType = 90
|
||||||
|
} else {
|
||||||
|
quarterType = -90
|
||||||
|
}
|
||||||
|
estimateJD := jde
|
||||||
|
for {
|
||||||
|
prevJD := estimateJD
|
||||||
|
stDegree := SunMoonSeek(prevJD, float64(quarterType))
|
||||||
|
stDegreep := (SunMoonSeek(prevJD+0.000005, float64(quarterType)) - SunMoonSeek(prevJD-0.000005, float64(quarterType))) / 0.00001
|
||||||
|
estimateJD = prevJD - stDegree/stDegreep
|
||||||
|
if math.Abs(estimateJD-prevJD) <= 0.00001 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return estimateJD
|
||||||
|
}
|
||||||
|
|
||||||
|
func CalcMoonX(year float64, quarterType int) float64 {
|
||||||
|
k := math.Floor((year-2000)*12.36827) + 0.25
|
||||||
|
if quarterType == 1 {
|
||||||
|
k += 0.5
|
||||||
|
}
|
||||||
|
T := k / 1236.85
|
||||||
|
jde := 2451550.09765 + 29.530588853*k + 0.0001337*T*T - 0.000000150*T*T*T + 0.00000000073*T*T*T*T
|
||||||
|
//太阳平近点角:
|
||||||
|
M := Limit360(2.5534 + 29.10535669*k - 0.0000218*T*T - 0.00000011*T*T*T)
|
||||||
|
//月亮的平近点角:
|
||||||
|
N := Limit360(201.5643 + 385.81693528*k + 0.0107438*T*T + 0.00001239*T*T*T - 0.000000058*T*T*T*T)
|
||||||
|
//月亮的纬度参数:
|
||||||
|
F := Limit360(160.7108 + 390.67050274*k - 0.0016341*T*T - 0.00000227*T*T*T + 0.000000011*T*T*T*T)
|
||||||
|
//月亮轨道升交点经度:
|
||||||
|
O := Limit360(124.7746 - 1.56375580*k + 0.0020691*T*T + 0.00000215*T*T*T)
|
||||||
|
E := 1 - 0.002516*T - 0.0000074*T*T
|
||||||
|
//die(E." ".M." ".N." ".F." ".O);
|
||||||
|
ZQ := []float64{N, M, N + M, 2 * N, 2 * F, N - M, 2 * M, N - 2*F, N + 2*F, 3 * N, 2*N - M, M + 2*F, M - 2*F, N + 2*M, 2*N + M, O, N - M - 2*F, 2*N + 2*F, N + M + 2*F, N - 2*F, N + M - 2*F, 3 * M, 2*N - 2*F, N - M + 2*F, M + 3*N}
|
||||||
|
MN := []float64{-0.62801, 0.17172 * E, -0.01183 * E, 0.00862, 0.00804, 0.00454 * E, 0.00204 * E * E, -0.00180, -0.00070, -0.00040, -0.00034 * E, 0.00032 * E, 0.00032 * E, -0.00028 * E * E, 0.00027 * E, -0.00017, -0.00005, 0.00004, -0.00004, 0.00004, 0.00003, 0.00003, 0.00002, 0.00002, -0.00002}
|
||||||
|
var correction float64
|
||||||
|
for idx, angle := range ZQ {
|
||||||
|
correction += Sin(angle) * MN[idx]
|
||||||
|
}
|
||||||
|
W := 0.00306 - 0.00038*E*Cos(M) + 0.00026*Cos(N) - 0.00002*Cos(N-M) + 0.00002*Cos(N+M) + 0.00002*Cos(2*F)
|
||||||
|
A1 := 299.77 + 0.107408*k - 0.009173*T*T
|
||||||
|
A2 := 251.88 + 0.016321*k
|
||||||
|
A3 := 251.83 + 26.651886*k
|
||||||
|
A4 := 349.42 + 36.412478*k
|
||||||
|
A5 := 84.66 + 18.206239*k
|
||||||
|
A6 := 141.74 + 53.303771*k
|
||||||
|
A7 := 207.14 + 2.453732*k
|
||||||
|
A8 := 154.84 + 7.306860*k
|
||||||
|
A9 := 34.52 + 27.261239*k
|
||||||
|
A10 := 207.19 + 0.121824*k
|
||||||
|
A11 := 291.34 + 1.844379*k
|
||||||
|
A12 := 161.72 + 24.198154*k
|
||||||
|
A13 := 239.56 + 25.513099*k
|
||||||
|
A14 := 331.55 + 3.592518*k
|
||||||
|
planetaryCorrection := 325*Sin(A1) + 165*Sin(A2) + 164*Sin(A3) + 126*Sin(A4) + 110*Sin(A5) + 62*Sin(A6) + 60*Sin(A7) + 56*Sin(A8) + 47*Sin(A9) + 42*Sin(A10) + 40*Sin(A11) + 37*Sin(A12) + 35*Sin(A13) + 23*Sin(A14)
|
||||||
|
planetaryCorrection /= 1000000
|
||||||
|
//die(tmp2);
|
||||||
|
//die(JDE." ".tmp." ".tmp2." ".W);
|
||||||
|
jde = jde + planetaryCorrection + correction
|
||||||
|
if quarterType == 0 {
|
||||||
|
jde += W
|
||||||
|
} else {
|
||||||
|
jde -= W
|
||||||
|
}
|
||||||
|
return jde
|
||||||
|
}
|
||||||
@@ -0,0 +1,168 @@
|
|||||||
|
package basic
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math"
|
||||||
|
|
||||||
|
. "b612.me/astro/tools"
|
||||||
|
)
|
||||||
|
|
||||||
|
const moonPhysicalInclinationDeg = 1.54242
|
||||||
|
const moonPhysicalAstronomicalUnitKM = 149597870.7
|
||||||
|
|
||||||
|
// MoonPhysicalInfo 月球物理观测参数 / physical observing parameters of the Moon.
|
||||||
|
type MoonPhysicalInfo struct {
|
||||||
|
// OpticalLongitude 光学经度天平动,单位度 / optical libration in longitude, degrees.
|
||||||
|
OpticalLongitude float64
|
||||||
|
// OpticalLatitude 光学纬度天平动,单位度 / optical libration in latitude, degrees.
|
||||||
|
OpticalLatitude float64
|
||||||
|
// PhysicalLongitude 物理经度天平动,单位度 / physical libration in longitude, degrees.
|
||||||
|
PhysicalLongitude float64
|
||||||
|
// PhysicalLatitude 物理纬度天平动,单位度 / physical libration in latitude, degrees.
|
||||||
|
PhysicalLatitude float64
|
||||||
|
// LibrationLongitude 总经度天平动,单位度 / total libration in longitude, degrees.
|
||||||
|
LibrationLongitude float64
|
||||||
|
// LibrationLatitude 总纬度天平动,单位度 / total libration in latitude, degrees.
|
||||||
|
LibrationLatitude float64
|
||||||
|
// PositionAngle 月球自转轴位置角,单位度 / position angle of the lunar rotation axis, degrees.
|
||||||
|
PositionAngle float64
|
||||||
|
}
|
||||||
|
|
||||||
|
// MoonPhysical 月球物理观测参数 / physical observing parameters of the Moon.
|
||||||
|
func MoonPhysical(jd float64) MoonPhysicalInfo {
|
||||||
|
return MoonPhysicalN(jd, -1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MoonPhysicalN 月球物理观测参数(截断版) / truncated physical observing parameters of the Moon.
|
||||||
|
func MoonPhysicalN(jd float64, n int) MoonPhysicalInfo {
|
||||||
|
return moonPhysicalNFromCoordinates(jd, n, HMoonApparentLoN(jd, n), HMoonTrueBoN(jd, n), HMoonTrueRaN(jd, n))
|
||||||
|
}
|
||||||
|
|
||||||
|
// MoonTopocentricPhysical 月球站心物理观测参数 / topocentric physical observing parameters of the Moon.
|
||||||
|
func MoonTopocentricPhysical(jd, observerLon, observerLat, height float64) MoonPhysicalInfo {
|
||||||
|
return MoonTopocentricPhysicalN(jd, observerLon, observerLat, height, -1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MoonTopocentricPhysicalN 月球站心物理观测参数(截断版) / truncated topocentric physical observing parameters of the Moon.
|
||||||
|
func MoonTopocentricPhysicalN(jd, observerLon, observerLat, height float64, n int) MoonPhysicalInfo {
|
||||||
|
lambda, beta, alpha := moonTopocentricPhysicalCoordinatesN(jd, observerLon, observerLat, height, n)
|
||||||
|
return moonPhysicalNFromCoordinates(jd, n, lambda, beta, alpha)
|
||||||
|
}
|
||||||
|
|
||||||
|
func moonPhysicalNFromCoordinates(jd float64, n int, lambda, beta, alpha float64) MoonPhysicalInfo {
|
||||||
|
t := (jd - 2451545.0) / 36525.0
|
||||||
|
epsilon := TrueObliquity(jd)
|
||||||
|
deltaPsi := Nutation2000Bi(jd)
|
||||||
|
|
||||||
|
D := Limit360(SunMoonAngle(jd))
|
||||||
|
sunMeanAnomaly := Limit360(SunM(jd))
|
||||||
|
moonMeanAnomaly := Limit360(MoonM(jd))
|
||||||
|
F := Limit360(MoonLonX(jd))
|
||||||
|
omega := moonPhysicalMeanAscendingNode(t)
|
||||||
|
E := 1 - 0.002516*t - 0.0000074*t*t
|
||||||
|
K1 := 119.75 + 131.849*t
|
||||||
|
K2 := 72.56 + 20.186*t
|
||||||
|
|
||||||
|
W := Limit360(lambda - deltaPsi - omega)
|
||||||
|
A := ArcTan2(Sin(W)*Cos(beta)*Cos(moonPhysicalInclinationDeg)-Sin(beta)*Sin(moonPhysicalInclinationDeg), Cos(W)*Cos(beta))
|
||||||
|
opticalLongitude := wrapSignedAngle180(A - F)
|
||||||
|
opticalLatitude := ArcSin(-Sin(W)*Cos(beta)*Sin(moonPhysicalInclinationDeg) - Sin(beta)*Cos(moonPhysicalInclinationDeg))
|
||||||
|
|
||||||
|
rho, sigma, tau := moonPhysicalLibrationSeries(D, sunMeanAnomaly, moonMeanAnomaly, F, omega, E, K1, K2)
|
||||||
|
physicalLongitude := -tau + (rho*Cos(A)+sigma*Sin(A))*Tan(opticalLatitude)
|
||||||
|
physicalLatitude := sigma*Cos(A) - rho*Sin(A)
|
||||||
|
|
||||||
|
librationLongitude := wrapSignedAngle180(opticalLongitude + physicalLongitude)
|
||||||
|
librationLatitude := opticalLatitude + physicalLatitude
|
||||||
|
|
||||||
|
V := Limit360(omega + deltaPsi + sigma/Sin(moonPhysicalInclinationDeg))
|
||||||
|
X := Sin(moonPhysicalInclinationDeg+rho) * Sin(V)
|
||||||
|
Y := Sin(moonPhysicalInclinationDeg+rho)*Cos(V)*Cos(epsilon) - Cos(moonPhysicalInclinationDeg+rho)*Sin(epsilon)
|
||||||
|
littleOmega := ArcTan2(X, Y)
|
||||||
|
positionAngle := ArcSin(clampUnit((sqrtXY(X, Y) * Cos(alpha-littleOmega)) / Cos(librationLatitude)))
|
||||||
|
|
||||||
|
return MoonPhysicalInfo{
|
||||||
|
OpticalLongitude: opticalLongitude,
|
||||||
|
OpticalLatitude: opticalLatitude,
|
||||||
|
PhysicalLongitude: physicalLongitude,
|
||||||
|
PhysicalLatitude: physicalLatitude,
|
||||||
|
LibrationLongitude: librationLongitude,
|
||||||
|
LibrationLatitude: librationLatitude,
|
||||||
|
PositionAngle: positionAngle,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func moonTopocentricPhysicalCoordinatesN(jd, observerLon, observerLat, height float64, n int) (lambda, beta, alpha float64) {
|
||||||
|
geocentricRA := HMoonTrueRaN(jd, n)
|
||||||
|
geocentricDec := HMoonTrueDecN(jd, n)
|
||||||
|
distanceAU := HMoonAwayN(jd, n) / moonPhysicalAstronomicalUnitKM
|
||||||
|
utJD := TD2UT(jd, false)
|
||||||
|
|
||||||
|
var topocentricDec float64
|
||||||
|
alpha, topocentricDec = TopocentricRaDec(geocentricRA, geocentricDec, observerLat, observerLon, utJD, distanceAU, height)
|
||||||
|
lambda, beta = RaDecToLoBo(jd, alpha, topocentricDec)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func moonPhysicalLibrationSeries(D, M, MP, F, omega, E, K1, K2 float64) (rho, sigma, tau float64) {
|
||||||
|
rho = -0.02752*Cos(MP) -
|
||||||
|
0.02245*Sin(F) +
|
||||||
|
0.00684*Cos(MP-2*F) -
|
||||||
|
0.00293*Cos(2*F) -
|
||||||
|
0.00085*Cos(2*F-2*D) -
|
||||||
|
0.00054*Cos(MP-2*D) -
|
||||||
|
0.00020*Sin(MP+F) -
|
||||||
|
0.00020*Cos(MP+2*F) -
|
||||||
|
0.00020*Cos(MP-F) +
|
||||||
|
0.00014*Cos(MP+2*F-2*D)
|
||||||
|
|
||||||
|
sigma = -0.02816*Sin(MP) +
|
||||||
|
0.02244*Cos(F) -
|
||||||
|
0.00682*Sin(MP-2*F) -
|
||||||
|
0.00279*Sin(2*F) -
|
||||||
|
0.00083*Sin(2*F-2*D) +
|
||||||
|
0.00069*Sin(MP-2*D) +
|
||||||
|
0.00040*Cos(MP+F) -
|
||||||
|
0.00025*Sin(2*MP) -
|
||||||
|
0.00023*Sin(MP+2*F) +
|
||||||
|
0.00020*Cos(MP-F) +
|
||||||
|
0.00019*Sin(MP-F) +
|
||||||
|
0.00013*Sin(MP+2*F-2*D) -
|
||||||
|
0.00010*Cos(MP-3*F)
|
||||||
|
|
||||||
|
tau = 0.02520*E*Sin(M) +
|
||||||
|
0.00473*Sin(2*MP-2*F) -
|
||||||
|
0.00467*Sin(MP) +
|
||||||
|
0.00396*Sin(K1) +
|
||||||
|
0.00276*Sin(2*MP-2*D) +
|
||||||
|
0.00196*Sin(omega) -
|
||||||
|
0.00183*Cos(MP-F) +
|
||||||
|
0.00115*Sin(MP-2*D) -
|
||||||
|
0.00096*Sin(MP-D) +
|
||||||
|
0.00046*Sin(2*F-2*D) -
|
||||||
|
0.00039*Sin(MP-F) -
|
||||||
|
0.00032*Sin(MP-M-D) +
|
||||||
|
0.00027*Sin(2*MP-M-2*D) +
|
||||||
|
0.00023*Sin(K2) -
|
||||||
|
0.00014*Sin(2*D) +
|
||||||
|
0.00014*Cos(2*MP-2*F) -
|
||||||
|
0.00012*Sin(MP-2*F) -
|
||||||
|
0.00012*Sin(2*MP) +
|
||||||
|
0.00011*Sin(2*MP-2*M-2*D)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func moonPhysicalMeanAscendingNode(t float64) float64 {
|
||||||
|
return Limit360(125.04452222222222 - 1934.136261111111*t + 0.0020708333333333334*t*t + 0.0000022222222222222222*t*t*t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func wrapSignedAngle180(angle float64) float64 {
|
||||||
|
angle = Limit360(angle)
|
||||||
|
if angle > 180 {
|
||||||
|
angle -= 360
|
||||||
|
}
|
||||||
|
return angle
|
||||||
|
}
|
||||||
|
|
||||||
|
func sqrtXY(x, y float64) float64 {
|
||||||
|
return math.Sqrt(x*x + y*y)
|
||||||
|
}
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
package basic
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestMoonPhysicalMeeusExample(t *testing.T) {
|
||||||
|
info := MoonPhysical(2448724.5)
|
||||||
|
|
||||||
|
assertClose(t, "OpticalLongitude", info.OpticalLongitude, -1.206, 0.01)
|
||||||
|
assertClose(t, "OpticalLatitude", info.OpticalLatitude, 4.194, 0.01)
|
||||||
|
assertClose(t, "PhysicalLongitude", info.PhysicalLongitude, -0.025, 0.01)
|
||||||
|
assertClose(t, "PhysicalLatitude", info.PhysicalLatitude, 0.006, 0.01)
|
||||||
|
assertClose(t, "LibrationLongitude", info.LibrationLongitude, -1.23, 0.02)
|
||||||
|
assertClose(t, "LibrationLatitude", info.LibrationLatitude, 4.20, 0.02)
|
||||||
|
assertClose(t, "PositionAngle", info.PositionAngle, 15.08, 0.02)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMoonPhysicalNFullMatchesDefault(t *testing.T) {
|
||||||
|
jd := 2461163.896354167
|
||||||
|
|
||||||
|
got := MoonPhysical(jd)
|
||||||
|
gotN := MoonPhysicalN(jd, -1)
|
||||||
|
|
||||||
|
assertSameFloat(t, "OpticalLongitude", got.OpticalLongitude, gotN.OpticalLongitude)
|
||||||
|
assertSameFloat(t, "OpticalLatitude", got.OpticalLatitude, gotN.OpticalLatitude)
|
||||||
|
assertSameFloat(t, "PhysicalLongitude", got.PhysicalLongitude, gotN.PhysicalLongitude)
|
||||||
|
assertSameFloat(t, "PhysicalLatitude", got.PhysicalLatitude, gotN.PhysicalLatitude)
|
||||||
|
assertSameFloat(t, "LibrationLongitude", got.LibrationLongitude, gotN.LibrationLongitude)
|
||||||
|
assertSameFloat(t, "LibrationLatitude", got.LibrationLatitude, gotN.LibrationLatitude)
|
||||||
|
assertSameFloat(t, "PositionAngle", got.PositionAngle, gotN.PositionAngle)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMoonPhysicalSampleSweepFiniteAndInRange(t *testing.T) {
|
||||||
|
dates := []time.Time{
|
||||||
|
time.Date(1900, 1, 1, 0, 0, 0, 0, time.UTC),
|
||||||
|
time.Date(1969, 7, 20, 20, 17, 40, 0, time.UTC),
|
||||||
|
time.Date(2000, 1, 1, 12, 0, 0, 0, time.UTC),
|
||||||
|
time.Date(2026, 4, 28, 9, 30, 45, 0, time.UTC),
|
||||||
|
time.Date(2099, 12, 31, 23, 59, 59, 0, time.UTC),
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, date := range dates {
|
||||||
|
jd := TD2UT(Date2JDE(date.UTC()), true)
|
||||||
|
info := MoonPhysical(jd)
|
||||||
|
prefix := date.Format(time.RFC3339)
|
||||||
|
|
||||||
|
assertFiniteSymmetric(t, prefix+".OpticalLongitude", info.OpticalLongitude, 180)
|
||||||
|
assertFiniteSymmetric(t, prefix+".OpticalLatitude", info.OpticalLatitude, 90)
|
||||||
|
assertFiniteSymmetric(t, prefix+".PhysicalLongitude", info.PhysicalLongitude, 180)
|
||||||
|
assertFiniteSymmetric(t, prefix+".PhysicalLatitude", info.PhysicalLatitude, 90)
|
||||||
|
assertFiniteSymmetric(t, prefix+".LibrationLongitude", info.LibrationLongitude, 180)
|
||||||
|
assertFiniteSymmetric(t, prefix+".LibrationLatitude", info.LibrationLatitude, 90)
|
||||||
|
assertFiniteSymmetric(t, prefix+".PositionAngle", info.PositionAngle, 90)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func assertFiniteSymmetric(t *testing.T, name string, got, limit float64) {
|
||||||
|
t.Helper()
|
||||||
|
if math.IsNaN(got) || math.IsInf(got, 0) {
|
||||||
|
t.Fatalf("%s is not finite: %.18f", name, got)
|
||||||
|
}
|
||||||
|
if got < -limit || got > limit {
|
||||||
|
t.Fatalf("%s out of range: %.18f not in [-%.18f, %.18f]", name, got, limit, limit)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,397 @@
|
|||||||
|
package basic
|
||||||
|
|
||||||
|
import "math"
|
||||||
|
|
||||||
|
const (
|
||||||
|
moonPlanetConjunctionEstimateN = 8
|
||||||
|
moonPlanetConjunctionNearQueryDeltaDeg = 3.0
|
||||||
|
moonPlanetConjunctionDirectionEpsilon = 0.1 / 86400.0
|
||||||
|
moonPlanetConjunctionBracketStepDays = 0.5
|
||||||
|
moonPlanetConjunctionNearQueryStepDays = 0.25
|
||||||
|
moonPlanetConjunctionNearQueryHalfSpan = 1.5
|
||||||
|
moonPlanetConjunctionBracketHalfSpan = 2.0
|
||||||
|
moonPlanetConjunctionBracketGrowth = 2.0
|
||||||
|
moonPlanetConjunctionBracketAttempts = 3
|
||||||
|
moonPlanetConjunctionRefineStepDays = 0.5 / 86400.0
|
||||||
|
moonPlanetConjunctionEventTolerance = 0.01
|
||||||
|
moonPlanetConjunctionFallbackSpanScale = 1.5
|
||||||
|
)
|
||||||
|
|
||||||
|
type moonPlanetConjunctionLocalResult struct {
|
||||||
|
lastUT float64
|
||||||
|
nextUT float64
|
||||||
|
}
|
||||||
|
|
||||||
|
func emptyMoonPlanetConjunctionLocalResult() moonPlanetConjunctionLocalResult {
|
||||||
|
return moonPlanetConjunctionLocalResult{
|
||||||
|
lastUT: math.NaN(),
|
||||||
|
nextUT: math.NaN(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MoonPlanetConjunctionPlanet 月球合月目标行星 / target planet for Moon-planet conjunction events.
|
||||||
|
type MoonPlanetConjunctionPlanet int
|
||||||
|
|
||||||
|
const (
|
||||||
|
MoonPlanetConjunctionMercury MoonPlanetConjunctionPlanet = iota + 1
|
||||||
|
MoonPlanetConjunctionVenus
|
||||||
|
MoonPlanetConjunctionMars
|
||||||
|
MoonPlanetConjunctionJupiter
|
||||||
|
MoonPlanetConjunctionSaturn
|
||||||
|
MoonPlanetConjunctionUranus
|
||||||
|
MoonPlanetConjunctionNeptune
|
||||||
|
)
|
||||||
|
|
||||||
|
func moonPlanetConjunctionWrappedDelta(diff float64) float64 {
|
||||||
|
diff = math.Mod(diff+180, 360)
|
||||||
|
if diff < 0 {
|
||||||
|
diff += 360
|
||||||
|
}
|
||||||
|
return diff - 180
|
||||||
|
}
|
||||||
|
|
||||||
|
func moonPlanetConjunctionDeltaAt(jdTT float64, planet MoonPlanetConjunctionPlanet, n int) float64 {
|
||||||
|
moonRA := HMoonGeocentricApparentRaN(jdTT, n)
|
||||||
|
var planetRA float64
|
||||||
|
switch planet {
|
||||||
|
case MoonPlanetConjunctionMercury:
|
||||||
|
planetRA = MercuryApparentRaN(jdTT, n)
|
||||||
|
case MoonPlanetConjunctionVenus:
|
||||||
|
planetRA = VenusApparentRaN(jdTT, n)
|
||||||
|
case MoonPlanetConjunctionMars:
|
||||||
|
planetRA = MarsApparentRaN(jdTT, n)
|
||||||
|
case MoonPlanetConjunctionJupiter:
|
||||||
|
planetRA = JupiterApparentRaN(jdTT, n)
|
||||||
|
case MoonPlanetConjunctionSaturn:
|
||||||
|
planetRA = SaturnApparentRaN(jdTT, n)
|
||||||
|
case MoonPlanetConjunctionUranus:
|
||||||
|
planetRA = UranusApparentRaN(jdTT, n)
|
||||||
|
case MoonPlanetConjunctionNeptune:
|
||||||
|
planetRA = NeptuneApparentRaN(jdTT, n)
|
||||||
|
default:
|
||||||
|
return math.NaN()
|
||||||
|
}
|
||||||
|
return moonPlanetConjunctionWrappedDelta(moonRA - planetRA)
|
||||||
|
}
|
||||||
|
|
||||||
|
func moonPlanetConjunctionPeriodDays(planet MoonPlanetConjunctionPlanet) float64 {
|
||||||
|
switch planet {
|
||||||
|
case MoonPlanetConjunctionMercury:
|
||||||
|
return 28.1
|
||||||
|
case MoonPlanetConjunctionVenus:
|
||||||
|
return 28.4
|
||||||
|
case MoonPlanetConjunctionMars:
|
||||||
|
return 29.2
|
||||||
|
case MoonPlanetConjunctionJupiter:
|
||||||
|
return 28.0
|
||||||
|
case MoonPlanetConjunctionSaturn:
|
||||||
|
return 27.4
|
||||||
|
case MoonPlanetConjunctionUranus:
|
||||||
|
return 27.3
|
||||||
|
case MoonPlanetConjunctionNeptune:
|
||||||
|
return 27.3
|
||||||
|
default:
|
||||||
|
return math.NaN()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func moonPlanetConjunctionBeforeOrEqual(eventUT, queryTT float64) bool {
|
||||||
|
return eventUTQueryTTDelta(eventUT, queryTT) <= moonPlanetConjunctionDirectionEpsilon
|
||||||
|
}
|
||||||
|
|
||||||
|
func moonPlanetConjunctionAfterOrEqual(eventUT, queryTT float64) bool {
|
||||||
|
return eventUTQueryTTDelta(eventUT, queryTT) >= -moonPlanetConjunctionDirectionEpsilon
|
||||||
|
}
|
||||||
|
|
||||||
|
func moonPlanetConjunctionInDirection(eventUT, queryTT float64, direction int) bool {
|
||||||
|
switch direction {
|
||||||
|
case -1:
|
||||||
|
return moonPlanetConjunctionBeforeOrEqual(eventUT, queryTT)
|
||||||
|
case 1:
|
||||||
|
return moonPlanetConjunctionAfterOrEqual(eventUT, queryTT)
|
||||||
|
default:
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func moonPlanetConjunctionFindBracket(centerTT, halfSpan, step float64, planet MoonPlanetConjunctionPlanet) (float64, float64, bool) {
|
||||||
|
if math.IsNaN(centerTT) || math.IsNaN(halfSpan) || math.IsNaN(step) || halfSpan <= 0 || step <= 0 {
|
||||||
|
return 0, 0, false
|
||||||
|
}
|
||||||
|
start := centerTT - halfSpan
|
||||||
|
end := centerTT + halfSpan
|
||||||
|
samples := int(math.Ceil((end-start)/step)) + 1
|
||||||
|
prevTT := start
|
||||||
|
prevVal := moonPlanetConjunctionDeltaAt(prevTT, planet, -1)
|
||||||
|
if math.IsNaN(prevVal) {
|
||||||
|
return 0, 0, false
|
||||||
|
}
|
||||||
|
if prevVal == 0 {
|
||||||
|
return prevTT, prevTT, true
|
||||||
|
}
|
||||||
|
bestLeft := 0.0
|
||||||
|
bestRight := 0.0
|
||||||
|
bestDistance := math.Inf(1)
|
||||||
|
for i := 1; i <= samples; i++ {
|
||||||
|
tt := start + float64(i)*step
|
||||||
|
if tt > end {
|
||||||
|
tt = end
|
||||||
|
}
|
||||||
|
val := moonPlanetConjunctionDeltaAt(tt, planet, -1)
|
||||||
|
if math.IsNaN(val) {
|
||||||
|
return 0, 0, false
|
||||||
|
}
|
||||||
|
if val == 0 {
|
||||||
|
return tt, tt, true
|
||||||
|
}
|
||||||
|
if prevVal*val < 0 {
|
||||||
|
mid := (prevTT + tt) / 2.0
|
||||||
|
distance := math.Abs(mid - centerTT)
|
||||||
|
if distance < bestDistance {
|
||||||
|
bestLeft = prevTT
|
||||||
|
bestRight = tt
|
||||||
|
bestDistance = distance
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if tt == end {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
prevTT = tt
|
||||||
|
prevVal = val
|
||||||
|
}
|
||||||
|
if math.IsInf(bestDistance, 1) {
|
||||||
|
return 0, 0, false
|
||||||
|
}
|
||||||
|
return bestLeft, bestRight, true
|
||||||
|
}
|
||||||
|
|
||||||
|
func moonPlanetConjunctionRefineBracket(leftTT, rightTT float64, planet MoonPlanetConjunctionPlanet) float64 {
|
||||||
|
if leftTT > rightTT {
|
||||||
|
leftTT, rightTT = rightTT, leftTT
|
||||||
|
}
|
||||||
|
if leftTT == rightTT {
|
||||||
|
return leftTT
|
||||||
|
}
|
||||||
|
center := (leftTT + rightTT) / 2.0
|
||||||
|
halfWindow := (rightTT - leftTT) / 2.0
|
||||||
|
return eventZeroRefine(center, halfWindow, moonPlanetConjunctionRefineStepDays, func(sampleTT float64) float64 {
|
||||||
|
return moonPlanetConjunctionDeltaAt(sampleTT, planet, -1)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func moonPlanetConjunctionEventUT(leftTT, rightTT float64, planet MoonPlanetConjunctionPlanet) float64 {
|
||||||
|
eventTT := moonPlanetConjunctionRefineBracket(leftTT, rightTT, planet)
|
||||||
|
if math.Abs(moonPlanetConjunctionDeltaAt(eventTT, planet, -1)) > moonPlanetConjunctionEventTolerance {
|
||||||
|
return math.NaN()
|
||||||
|
}
|
||||||
|
return TD2UT(eventTT, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
func moonPlanetConjunctionCollectLocalEvent(result *moonPlanetConjunctionLocalResult, queryTT, eventUT float64) {
|
||||||
|
if math.IsNaN(eventUT) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if moonPlanetConjunctionBeforeOrEqual(eventUT, queryTT) {
|
||||||
|
if math.IsNaN(result.lastUT) || math.Abs(eventUTQueryTTDelta(eventUT, queryTT)) < math.Abs(eventUTQueryTTDelta(result.lastUT, queryTT)) {
|
||||||
|
result.lastUT = eventUT
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if moonPlanetConjunctionAfterOrEqual(eventUT, queryTT) {
|
||||||
|
if math.IsNaN(result.nextUT) || math.Abs(eventUTQueryTTDelta(eventUT, queryTT)) < math.Abs(eventUTQueryTTDelta(result.nextUT, queryTT)) {
|
||||||
|
result.nextUT = eventUT
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func moonPlanetConjunctionShouldCheckLocal(queryTT float64, planet MoonPlanetConjunctionPlanet) bool {
|
||||||
|
delta := moonPlanetConjunctionDeltaAt(queryTT, planet, moonPlanetConjunctionEstimateN)
|
||||||
|
if math.IsNaN(delta) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return math.Abs(delta) <= moonPlanetConjunctionNearQueryDeltaDeg
|
||||||
|
}
|
||||||
|
|
||||||
|
func moonPlanetConjunctionLocalEvents(queryTT float64, planet MoonPlanetConjunctionPlanet) moonPlanetConjunctionLocalResult {
|
||||||
|
result := emptyMoonPlanetConjunctionLocalResult()
|
||||||
|
start := queryTT - moonPlanetConjunctionNearQueryHalfSpan
|
||||||
|
end := queryTT + moonPlanetConjunctionNearQueryHalfSpan
|
||||||
|
step := moonPlanetConjunctionNearQueryStepDays
|
||||||
|
prevTT := start
|
||||||
|
prevVal := moonPlanetConjunctionDeltaAt(prevTT, planet, -1)
|
||||||
|
if math.IsNaN(prevVal) {
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
samples := int(math.Ceil((end-start)/step)) + 1
|
||||||
|
for i := 1; i <= samples; i++ {
|
||||||
|
tt := start + float64(i)*step
|
||||||
|
if tt > end {
|
||||||
|
tt = end
|
||||||
|
}
|
||||||
|
val := moonPlanetConjunctionDeltaAt(tt, planet, -1)
|
||||||
|
if math.IsNaN(val) {
|
||||||
|
return emptyMoonPlanetConjunctionLocalResult()
|
||||||
|
}
|
||||||
|
if prevVal == 0 || val == 0 || prevVal*val < 0 {
|
||||||
|
moonPlanetConjunctionCollectLocalEvent(&result, queryTT, moonPlanetConjunctionEventUT(prevTT, tt, planet))
|
||||||
|
}
|
||||||
|
if tt == end {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
prevTT = tt
|
||||||
|
prevVal = val
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func moonPlanetConjunctionMaybeLocalEvents(queryTT float64, planet MoonPlanetConjunctionPlanet) moonPlanetConjunctionLocalResult {
|
||||||
|
if !moonPlanetConjunctionShouldCheckLocal(queryTT, planet) {
|
||||||
|
return emptyMoonPlanetConjunctionLocalResult()
|
||||||
|
}
|
||||||
|
return moonPlanetConjunctionLocalEvents(queryTT, planet)
|
||||||
|
}
|
||||||
|
|
||||||
|
func moonPlanetConjunctionGuessTT(queryTT float64, planet MoonPlanetConjunctionPlanet, direction int) float64 {
|
||||||
|
delta := moonPlanetConjunctionDeltaAt(queryTT, planet, moonPlanetConjunctionEstimateN)
|
||||||
|
if math.IsNaN(delta) {
|
||||||
|
return math.NaN()
|
||||||
|
}
|
||||||
|
period := moonPlanetConjunctionPeriodDays(planet)
|
||||||
|
if math.IsNaN(period) {
|
||||||
|
return math.NaN()
|
||||||
|
}
|
||||||
|
switch direction {
|
||||||
|
case -1:
|
||||||
|
return queryTT - innerLastCycleOffset(delta, period)
|
||||||
|
case 1:
|
||||||
|
return queryTT + innerNextCycleOffset(delta, period)
|
||||||
|
default:
|
||||||
|
return math.NaN()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func moonPlanetConjunctionDirectionalFallback(queryTT float64, planet MoonPlanetConjunctionPlanet, direction int) float64 {
|
||||||
|
period := moonPlanetConjunctionPeriodDays(planet)
|
||||||
|
if math.IsNaN(period) {
|
||||||
|
return math.NaN()
|
||||||
|
}
|
||||||
|
span := period * moonPlanetConjunctionFallbackSpanScale
|
||||||
|
if span <= 0 {
|
||||||
|
return math.NaN()
|
||||||
|
}
|
||||||
|
step := moonPlanetConjunctionNearQueryStepDays
|
||||||
|
start := queryTT
|
||||||
|
end := queryTT
|
||||||
|
switch direction {
|
||||||
|
case -1:
|
||||||
|
start -= span
|
||||||
|
case 1:
|
||||||
|
end += span
|
||||||
|
default:
|
||||||
|
return math.NaN()
|
||||||
|
}
|
||||||
|
|
||||||
|
prevTT := start
|
||||||
|
prevVal := moonPlanetConjunctionDeltaAt(prevTT, planet, -1)
|
||||||
|
if math.IsNaN(prevVal) {
|
||||||
|
return math.NaN()
|
||||||
|
}
|
||||||
|
|
||||||
|
bestEventUT := math.NaN()
|
||||||
|
for tt := start + step; ; tt += step {
|
||||||
|
if tt > end {
|
||||||
|
tt = end
|
||||||
|
}
|
||||||
|
val := moonPlanetConjunctionDeltaAt(tt, planet, -1)
|
||||||
|
if math.IsNaN(val) {
|
||||||
|
return math.NaN()
|
||||||
|
}
|
||||||
|
if prevVal == 0 || val == 0 || prevVal*val < 0 {
|
||||||
|
eventUT := moonPlanetConjunctionEventUT(prevTT, tt, planet)
|
||||||
|
if !math.IsNaN(eventUT) && moonPlanetConjunctionInDirection(eventUT, queryTT, direction) {
|
||||||
|
if direction == 1 {
|
||||||
|
return eventUT
|
||||||
|
}
|
||||||
|
bestEventUT = eventUT
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if tt == end {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
prevTT = tt
|
||||||
|
prevVal = val
|
||||||
|
}
|
||||||
|
return bestEventUT
|
||||||
|
}
|
||||||
|
|
||||||
|
func moonPlanetConjunctionDirectionalEventWithLocal(queryTT float64, planet MoonPlanetConjunctionPlanet, direction int, local moonPlanetConjunctionLocalResult) float64 {
|
||||||
|
switch direction {
|
||||||
|
case -1:
|
||||||
|
if !math.IsNaN(local.lastUT) {
|
||||||
|
return local.lastUT
|
||||||
|
}
|
||||||
|
case 1:
|
||||||
|
if !math.IsNaN(local.nextUT) {
|
||||||
|
return local.nextUT
|
||||||
|
}
|
||||||
|
}
|
||||||
|
guessTT := moonPlanetConjunctionGuessTT(queryTT, planet, direction)
|
||||||
|
if math.IsNaN(guessTT) {
|
||||||
|
return math.NaN()
|
||||||
|
}
|
||||||
|
halfSpan := moonPlanetConjunctionBracketHalfSpan
|
||||||
|
for attempt := 0; attempt < moonPlanetConjunctionBracketAttempts; attempt++ {
|
||||||
|
left, right, ok := moonPlanetConjunctionFindBracket(guessTT, halfSpan, moonPlanetConjunctionBracketStepDays, planet)
|
||||||
|
if ok {
|
||||||
|
eventUT := moonPlanetConjunctionEventUT(left, right, planet)
|
||||||
|
if math.IsNaN(eventUT) {
|
||||||
|
halfSpan *= moonPlanetConjunctionBracketGrowth
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if moonPlanetConjunctionInDirection(eventUT, queryTT, direction) {
|
||||||
|
return eventUT
|
||||||
|
}
|
||||||
|
}
|
||||||
|
halfSpan *= moonPlanetConjunctionBracketGrowth
|
||||||
|
}
|
||||||
|
return moonPlanetConjunctionDirectionalFallback(queryTT, planet, direction)
|
||||||
|
}
|
||||||
|
|
||||||
|
func moonPlanetConjunctionDirectionalEvent(queryTT float64, planet MoonPlanetConjunctionPlanet, direction int) float64 {
|
||||||
|
return moonPlanetConjunctionDirectionalEventWithLocal(queryTT, planet, direction, moonPlanetConjunctionMaybeLocalEvents(queryTT, planet))
|
||||||
|
}
|
||||||
|
|
||||||
|
// LastMoonPlanetConjunction 指定时刻之前最近一次行星合月(赤经合) / previous Moon-planet conjunction at or before jde.
|
||||||
|
func LastMoonPlanetConjunction(jde float64, planet MoonPlanetConjunctionPlanet) float64 {
|
||||||
|
return moonPlanetConjunctionDirectionalEvent(jde, planet, -1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NextMoonPlanetConjunction 指定时刻之后最近一次行星合月(赤经合) / next Moon-planet conjunction at or after jde.
|
||||||
|
func NextMoonPlanetConjunction(jde float64, planet MoonPlanetConjunctionPlanet) float64 {
|
||||||
|
return moonPlanetConjunctionDirectionalEvent(jde, planet, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClosestMoonPlanetConjunction 离指定时刻最近一次行星合月(赤经合) / closest Moon-planet conjunction to jde.
|
||||||
|
func ClosestMoonPlanetConjunction(jde float64, planet MoonPlanetConjunctionPlanet) float64 {
|
||||||
|
local := moonPlanetConjunctionMaybeLocalEvents(jde, planet)
|
||||||
|
if !math.IsNaN(local.lastUT) && !math.IsNaN(local.nextUT) {
|
||||||
|
if sameEventJD(local.lastUT, local.nextUT) {
|
||||||
|
return local.lastUT
|
||||||
|
}
|
||||||
|
return closestEventUTToQueryTT(jde, local.lastUT, local.nextUT)
|
||||||
|
}
|
||||||
|
if !math.IsNaN(local.lastUT) {
|
||||||
|
return local.lastUT
|
||||||
|
}
|
||||||
|
if !math.IsNaN(local.nextUT) {
|
||||||
|
return local.nextUT
|
||||||
|
}
|
||||||
|
last := moonPlanetConjunctionDirectionalEventWithLocal(jde, planet, -1, local)
|
||||||
|
next := moonPlanetConjunctionDirectionalEventWithLocal(jde, planet, 1, local)
|
||||||
|
if math.IsNaN(last) {
|
||||||
|
return next
|
||||||
|
}
|
||||||
|
if math.IsNaN(next) {
|
||||||
|
return last
|
||||||
|
}
|
||||||
|
return closestEventUTToQueryTT(jde, last, next)
|
||||||
|
}
|
||||||
@@ -0,0 +1,284 @@
|
|||||||
|
package basic
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"math"
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type moonPlanetConjunctionBaselineSample struct {
|
||||||
|
Planet string `json:"planet"`
|
||||||
|
Year int `json:"year"`
|
||||||
|
Month int `json:"month"`
|
||||||
|
TimeUTC string `json:"time_utc"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type moonPlanetConjunctionBaseline struct {
|
||||||
|
Samples []moonPlanetConjunctionBaselineSample `json:"samples"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadMoonPlanetConjunctionBaseline(t *testing.T) moonPlanetConjunctionBaseline {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
paths := [][]string{
|
||||||
|
{
|
||||||
|
"testdata/moon_planet_conjunction_baseline.json",
|
||||||
|
"basic/testdata/moon_planet_conjunction_baseline.json",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"testdata/moon_planet_conjunction_baseline_samples.json",
|
||||||
|
"basic/testdata/moon_planet_conjunction_baseline_samples.json",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
var merged moonPlanetConjunctionBaseline
|
||||||
|
for index, candidates := range paths {
|
||||||
|
var (
|
||||||
|
data []byte
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
for _, path := range candidates {
|
||||||
|
data, err = os.ReadFile(path)
|
||||||
|
if err == nil {
|
||||||
|
var baseline moonPlanetConjunctionBaseline
|
||||||
|
if err := json.Unmarshal(data, &baseline); err != nil {
|
||||||
|
t.Fatalf("decode baseline %s: %v", path, err)
|
||||||
|
}
|
||||||
|
merged.Samples = append(merged.Samples, baseline.Samples...)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err != nil && index == 0 {
|
||||||
|
t.Fatalf("read baseline: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(merged.Samples) == 0 {
|
||||||
|
t.Fatal("empty moon-planet conjunction baseline")
|
||||||
|
}
|
||||||
|
return merged
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMoonPlanetConjunctionsMatchHorizonsBaseline(t *testing.T) {
|
||||||
|
baseline := loadMoonPlanetConjunctionBaseline(t)
|
||||||
|
|
||||||
|
type conjunctionCase struct {
|
||||||
|
planet MoonPlanetConjunctionPlanet
|
||||||
|
next func(float64, MoonPlanetConjunctionPlanet) float64
|
||||||
|
}
|
||||||
|
|
||||||
|
cases := map[string]conjunctionCase{
|
||||||
|
"mercury": {planet: MoonPlanetConjunctionMercury, next: NextMoonPlanetConjunction},
|
||||||
|
"venus": {planet: MoonPlanetConjunctionVenus, next: NextMoonPlanetConjunction},
|
||||||
|
"mars": {planet: MoonPlanetConjunctionMars, next: NextMoonPlanetConjunction},
|
||||||
|
"jupiter": {planet: MoonPlanetConjunctionJupiter, next: NextMoonPlanetConjunction},
|
||||||
|
"saturn": {planet: MoonPlanetConjunctionSaturn, next: NextMoonPlanetConjunction},
|
||||||
|
"uranus": {planet: MoonPlanetConjunctionUranus, next: NextMoonPlanetConjunction},
|
||||||
|
"neptune": {planet: MoonPlanetConjunctionNeptune, next: NextMoonPlanetConjunction},
|
||||||
|
}
|
||||||
|
|
||||||
|
const tolerance = 20 * time.Second
|
||||||
|
var maxDiff time.Duration
|
||||||
|
|
||||||
|
seen := make(map[string]int, len(cases))
|
||||||
|
for _, sample := range baseline.Samples {
|
||||||
|
tc, ok := cases[sample.Planet]
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("unknown planet %q", sample.Planet)
|
||||||
|
}
|
||||||
|
|
||||||
|
wantTime, err := time.Parse(time.RFC3339Nano, sample.TimeUTC)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("parse sample time %q: %v", sample.TimeUTC, err)
|
||||||
|
}
|
||||||
|
queryTT := TD2UT(Date2JDE(wantTime.Add(-12*time.Hour).UTC()), true)
|
||||||
|
gotUT := tc.next(queryTT, tc.planet)
|
||||||
|
gotTime := JDE2DateByZone(gotUT, time.UTC, false)
|
||||||
|
diff := gotTime.Sub(wantTime)
|
||||||
|
if diff < 0 {
|
||||||
|
diff = -diff
|
||||||
|
}
|
||||||
|
if diff > maxDiff {
|
||||||
|
maxDiff = diff
|
||||||
|
}
|
||||||
|
if diff > tolerance {
|
||||||
|
t.Fatalf("%s %04d-%02d time mismatch: got %s want %s tolerance %v", sample.Planet, sample.Year, sample.Month, gotTime.Format(time.RFC3339Nano), sample.TimeUTC, tolerance)
|
||||||
|
}
|
||||||
|
|
||||||
|
delta := math.Abs(moonPlanetConjunctionDeltaAt(TD2UT(gotUT, true), tc.planet, -1))
|
||||||
|
if delta > 0.01 {
|
||||||
|
t.Fatalf("%s %04d-%02d event not near conjunction: delta=%.8f deg", sample.Planet, sample.Year, sample.Month, delta)
|
||||||
|
}
|
||||||
|
seen[sample.Planet]++
|
||||||
|
}
|
||||||
|
|
||||||
|
for planet := range cases {
|
||||||
|
if seen[planet] == 0 {
|
||||||
|
t.Fatalf("missing baseline samples for %s", planet)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Logf("moon-planet conjunction max diff: time=%v", maxDiff)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMoonPlanetConjunctionDirectionalConsistencyAtComputedEvent(t *testing.T) {
|
||||||
|
baseline := loadMoonPlanetConjunctionBaseline(t)
|
||||||
|
|
||||||
|
planets := map[string]MoonPlanetConjunctionPlanet{
|
||||||
|
"mercury": MoonPlanetConjunctionMercury,
|
||||||
|
"venus": MoonPlanetConjunctionVenus,
|
||||||
|
"mars": MoonPlanetConjunctionMars,
|
||||||
|
"jupiter": MoonPlanetConjunctionJupiter,
|
||||||
|
"saturn": MoonPlanetConjunctionSaturn,
|
||||||
|
"uranus": MoonPlanetConjunctionUranus,
|
||||||
|
"neptune": MoonPlanetConjunctionNeptune,
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, sample := range baseline.Samples {
|
||||||
|
planet, ok := planets[sample.Planet]
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("unknown planet %q", sample.Planet)
|
||||||
|
}
|
||||||
|
wantTime, err := time.Parse(time.RFC3339Nano, sample.TimeUTC)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("parse sample time %q: %v", sample.TimeUTC, err)
|
||||||
|
}
|
||||||
|
seedTT := TD2UT(Date2JDE(wantTime.Add(-12*time.Hour).UTC()), true)
|
||||||
|
eventUT := NextMoonPlanetConjunction(seedTT, planet)
|
||||||
|
eventTime := JDE2DateByZone(eventUT, time.UTC, false)
|
||||||
|
queryAtTT := TD2UT(Date2JDE(eventTime.UTC()), true)
|
||||||
|
queryAfterTT := TD2UT(Date2JDE(eventTime.Add(time.Hour).UTC()), true)
|
||||||
|
|
||||||
|
exactNext := NextMoonPlanetConjunction(queryAtTT, planet)
|
||||||
|
exactClosest := ClosestMoonPlanetConjunction(queryAtTT, planet)
|
||||||
|
exactLastAfter := LastMoonPlanetConjunction(queryAfterTT, planet)
|
||||||
|
|
||||||
|
for name, gotUT := range map[string]float64{
|
||||||
|
"exactNext": exactNext,
|
||||||
|
"exactClosest": exactClosest,
|
||||||
|
"lastAfterEvent": exactLastAfter,
|
||||||
|
} {
|
||||||
|
gotTime := JDE2DateByZone(gotUT, time.UTC, false)
|
||||||
|
if diff := math.Abs(gotUT - eventUT); diff > 1e-9 {
|
||||||
|
t.Fatalf("%s %s mismatch: got %s want %s diff=%v", sample.Planet, name, gotTime.Format(time.RFC3339Nano), eventTime.Format(time.RFC3339Nano), diff*86400)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMoonPlanetConjunctionRejectsOppositionBranchJump(t *testing.T) {
|
||||||
|
query := time.Date(1900, 11, 10, 12, 0, 0, 0, time.UTC)
|
||||||
|
queryTT := TD2UT(Date2JDE(query), true)
|
||||||
|
|
||||||
|
lastUT := LastMoonPlanetConjunction(queryTT, MoonPlanetConjunctionSaturn)
|
||||||
|
nextUT := NextMoonPlanetConjunction(queryTT, MoonPlanetConjunctionSaturn)
|
||||||
|
|
||||||
|
if math.Abs(lastUT-Date2JDE(query)) <= 5.0/86400.0 {
|
||||||
|
t.Fatalf("last returned query time on branch jump: got %s", JDE2DateByZone(lastUT, time.UTC, false).Format(time.RFC3339Nano))
|
||||||
|
}
|
||||||
|
if math.Abs(nextUT-Date2JDE(query)) <= 5.0/86400.0 {
|
||||||
|
t.Fatalf("next returned query time on branch jump: got %s", JDE2DateByZone(nextUT, time.UTC, false).Format(time.RFC3339Nano))
|
||||||
|
}
|
||||||
|
|
||||||
|
for name, gotUT := range map[string]float64{
|
||||||
|
"last": lastUT,
|
||||||
|
"next": nextUT,
|
||||||
|
} {
|
||||||
|
delta := math.Abs(moonPlanetConjunctionDeltaAt(TD2UT(gotUT, true), MoonPlanetConjunctionSaturn, -1))
|
||||||
|
if delta > moonPlanetConjunctionEventTolerance {
|
||||||
|
t.Fatalf("%s returned non-event candidate: delta=%.8f event=%s", name, delta, JDE2DateByZone(gotUT, time.UTC, false).Format(time.RFC3339Nano))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMoonPlanetConjunctionDirectionalOrderingOnSampleQueries(t *testing.T) {
|
||||||
|
samples := []struct {
|
||||||
|
planet MoonPlanetConjunctionPlanet
|
||||||
|
query time.Time
|
||||||
|
}{
|
||||||
|
{planet: MoonPlanetConjunctionSaturn, query: time.Date(1700, 4, 15, 12, 0, 0, 0, time.UTC)},
|
||||||
|
{planet: MoonPlanetConjunctionMercury, query: time.Date(1900, 1, 14, 12, 0, 0, 0, time.UTC)},
|
||||||
|
{planet: MoonPlanetConjunctionVenus, query: time.Date(1950, 6, 3, 12, 0, 0, 0, time.UTC)},
|
||||||
|
{planet: MoonPlanetConjunctionMars, query: time.Date(2000, 2, 29, 18, 0, 0, 0, time.UTC)},
|
||||||
|
{planet: MoonPlanetConjunctionJupiter, query: time.Date(2026, 5, 20, 0, 0, 0, 0, time.UTC)},
|
||||||
|
{planet: MoonPlanetConjunctionSaturn, query: time.Date(2100, 8, 17, 6, 0, 0, 0, time.UTC)},
|
||||||
|
{planet: MoonPlanetConjunctionUranus, query: time.Date(2200, 11, 2, 9, 0, 0, 0, time.UTC)},
|
||||||
|
{planet: MoonPlanetConjunctionNeptune, query: time.Date(2300, 4, 24, 3, 0, 0, 0, time.UTC)},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, sample := range samples {
|
||||||
|
queryTT := TD2UT(Date2JDE(sample.query.UTC()), true)
|
||||||
|
lastUT := LastMoonPlanetConjunction(queryTT, sample.planet)
|
||||||
|
nextUT := NextMoonPlanetConjunction(queryTT, sample.planet)
|
||||||
|
closestUT := ClosestMoonPlanetConjunction(queryTT, sample.planet)
|
||||||
|
|
||||||
|
if math.IsNaN(lastUT) || math.IsNaN(nextUT) || math.IsNaN(closestUT) {
|
||||||
|
t.Fatalf("planet=%v query=%s returned NaN event(s): last=%v next=%v closest=%v", sample.planet, sample.query.Format(time.RFC3339), lastUT, nextUT, closestUT)
|
||||||
|
}
|
||||||
|
if !eventUTQueryBeforeOrEqual(lastUT, queryTT) {
|
||||||
|
t.Fatalf("planet=%v last after query: last=%s query=%s", sample.planet, JDE2DateByZone(lastUT, time.UTC, false).Format(time.RFC3339Nano), sample.query.Format(time.RFC3339Nano))
|
||||||
|
}
|
||||||
|
if !eventUTQueryAfterOrEqual(nextUT, queryTT) {
|
||||||
|
t.Fatalf("planet=%v next before query: next=%s query=%s", sample.planet, JDE2DateByZone(nextUT, time.UTC, false).Format(time.RFC3339Nano), sample.query.Format(time.RFC3339Nano))
|
||||||
|
}
|
||||||
|
if closestUT != closestEventUTToQueryTT(queryTT, lastUT, nextUT) {
|
||||||
|
t.Fatalf("planet=%v closest mismatch: got=%s want=%s", sample.planet, JDE2DateByZone(closestUT, time.UTC, false).Format(time.RFC3339Nano), JDE2DateByZone(closestEventUTToQueryTT(queryTT, lastUT, nextUT), time.UTC, false).Format(time.RFC3339Nano))
|
||||||
|
}
|
||||||
|
for name, gotUT := range map[string]float64{
|
||||||
|
"last": lastUT,
|
||||||
|
"next": nextUT,
|
||||||
|
"closest": closestUT,
|
||||||
|
} {
|
||||||
|
delta := math.Abs(moonPlanetConjunctionDeltaAt(TD2UT(gotUT, true), sample.planet, -1))
|
||||||
|
if delta > moonPlanetConjunctionEventTolerance {
|
||||||
|
t.Fatalf("planet=%v %s returned non-event candidate: delta=%.8f event=%s", sample.planet, name, delta, JDE2DateByZone(gotUT, time.UTC, false).Format(time.RFC3339Nano))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMoonPlanetConjunctionKeepsImmediateNeighborEvents(t *testing.T) {
|
||||||
|
query := time.Date(1700, 4, 15, 12, 0, 0, 0, time.UTC)
|
||||||
|
queryTT := TD2UT(Date2JDE(query.UTC()), true)
|
||||||
|
|
||||||
|
lastUT := LastMoonPlanetConjunction(queryTT, MoonPlanetConjunctionSaturn)
|
||||||
|
nextUT := NextMoonPlanetConjunction(queryTT, MoonPlanetConjunctionSaturn)
|
||||||
|
closestUT := ClosestMoonPlanetConjunction(queryTT, MoonPlanetConjunctionSaturn)
|
||||||
|
|
||||||
|
wantLast := time.Date(1700, 4, 15, 11, 55, 59, 115569293, time.UTC)
|
||||||
|
wantNext := time.Date(1700, 5, 13, 0, 35, 5, 981616675, time.UTC)
|
||||||
|
const tolerance = 5.0 / 86400.0
|
||||||
|
|
||||||
|
if diff := math.Abs(lastUT - Date2JDE(wantLast)); diff > tolerance {
|
||||||
|
t.Fatalf("last mismatch: got=%s want=%s diff=%.3fs", JDE2DateByZone(lastUT, time.UTC, false).Format(time.RFC3339Nano), wantLast.Format(time.RFC3339Nano), diff*86400)
|
||||||
|
}
|
||||||
|
if diff := math.Abs(nextUT - Date2JDE(wantNext)); diff > tolerance {
|
||||||
|
t.Fatalf("next mismatch: got=%s want=%s diff=%.3fs", JDE2DateByZone(nextUT, time.UTC, false).Format(time.RFC3339Nano), wantNext.Format(time.RFC3339Nano), diff*86400)
|
||||||
|
}
|
||||||
|
if !sameEventJD(closestUT, lastUT) {
|
||||||
|
t.Fatalf("closest should keep immediate previous event: closest=%s last=%s", JDE2DateByZone(closestUT, time.UTC, false).Format(time.RFC3339Nano), JDE2DateByZone(lastUT, time.UTC, false).Format(time.RFC3339Nano))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMoonPlanetConjunctionNextAdvancesPastReturnedEvent(t *testing.T) {
|
||||||
|
seed := TD2UT(Date2JDE(time.Date(2026, 5, 1, 0, 0, 0, 0, time.UTC)), true)
|
||||||
|
eventUT := NextMoonPlanetConjunction(seed, MoonPlanetConjunctionMercury)
|
||||||
|
query := JDE2DateByZone(eventUT, time.UTC, false).Add(time.Second)
|
||||||
|
queryTT := TD2UT(Date2JDE(query.UTC()), true)
|
||||||
|
|
||||||
|
nextUT := NextMoonPlanetConjunction(queryTT, MoonPlanetConjunctionMercury)
|
||||||
|
if eventUTQueryTTDelta(nextUT, queryTT) <= 0 {
|
||||||
|
t.Fatalf("expected next conjunction after query: query=%s next=%s delta=%.6fs",
|
||||||
|
query.Format(time.RFC3339Nano),
|
||||||
|
JDE2DateByZone(nextUT, time.UTC, false).Format(time.RFC3339Nano),
|
||||||
|
eventUTQueryTTDelta(nextUT, queryTT)*86400,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if sameEventJD(nextUT, eventUT) {
|
||||||
|
t.Fatalf("next conjunction should advance to a later event: event=%s next=%s",
|
||||||
|
JDE2DateByZone(eventUT, time.UTC, false).Format(time.RFC3339Nano),
|
||||||
|
JDE2DateByZone(nextUT, time.UTC, false).Format(time.RFC3339Nano),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
File diff suppressed because one or more lines are too long
+757
-38
@@ -1,10 +1,9 @@
|
|||||||
package basic
|
package basic
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"errors"
|
||||||
"math"
|
"math"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func Benchmark_MoonRiseBench(b *testing.B) {
|
func Benchmark_MoonRiseBench(b *testing.B) {
|
||||||
@@ -12,50 +11,770 @@ func Benchmark_MoonRiseBench(b *testing.B) {
|
|||||||
for i := 0; i < b.N; i++ {
|
for i := 0; i < b.N; i++ {
|
||||||
GetMoonRiseTime(jde, 105, 40, 8, 0, 10)
|
GetMoonRiseTime(jde, 105, 40, 8, 0, 10)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func Test_MoonDown(t *testing.T) {
|
// MoonRiseSetTestCase 月出月落测试用例
|
||||||
jde := GetNowJDE()
|
type MoonRiseSetTestCase struct {
|
||||||
for i := 30.0; i < 90.0; i += 0.3 {
|
Year int
|
||||||
fmt.Println(i, GetMoonDownTime(jde, 115, float64(i), 8, 1, 0))
|
Month int
|
||||||
|
Day float64
|
||||||
|
Longitude float64
|
||||||
|
Latitude float64
|
||||||
|
TimeZone float64
|
||||||
|
ZenithShift float64
|
||||||
|
Height float64
|
||||||
|
ExpectedRise float64
|
||||||
|
ExpectedSet float64
|
||||||
|
}
|
||||||
|
|
||||||
|
// moonRiseSetTestData 月出月落测试数据
|
||||||
|
var moonRiseSetTestData = []MoonRiseSetTestCase{
|
||||||
|
{2023, 1, 15.0, 116.4074, 39.9042, 8.0, 0, 0, 2459959.512382, 2459959.985558},
|
||||||
|
{2023, 1, 15.0, 116.4074, 39.9042, 8.0, 0, 100, 2459959.511153, 2459959.986753},
|
||||||
|
{2023, 1, 15.0, 116.4074, 39.9042, 8.0, 0, 1000, 2459959.508489, 2459959.989349},
|
||||||
|
{2023, 1, 15.0, 116.4074, 39.9042, 8.0, 1, 0, 2459959.509182, 2459959.988676},
|
||||||
|
{2023, 1, 15.0, 116.4074, 39.9042, 8.0, 1, 100, 2459959.507955, 2459959.989868},
|
||||||
|
{2023, 1, 15.0, 116.4074, 39.9042, 8.0, 1, 1000, 2459959.505295, 2459959.992458},
|
||||||
|
{2023, 3, 20.0, 116.4074, 39.9042, 8.0, 0, 0, 2460023.743390, 2460024.191155},
|
||||||
|
{2023, 3, 20.0, 116.4074, 39.9042, 8.0, 0, 100, 2460023.742131, 2460024.192411},
|
||||||
|
{2023, 3, 20.0, 116.4074, 39.9042, 8.0, 0, 1000, 2460023.739404, 2460024.195141},
|
||||||
|
{2023, 3, 20.0, 116.4074, 39.9042, 8.0, 1, 0, 2460023.740112, 2460024.194436},
|
||||||
|
{2023, 3, 20.0, 116.4074, 39.9042, 8.0, 1, 100, 2460023.738858, 2460024.195688},
|
||||||
|
{2023, 3, 20.0, 116.4074, 39.9042, 8.0, 1, 1000, 2460023.736141, 2460024.198409},
|
||||||
|
{2023, 6, 21.0, 116.4074, 39.9042, 8.0, 0, 0, 2460116.797325, 2460117.432318},
|
||||||
|
{2023, 6, 21.0, 116.4074, 39.9042, 8.0, 0, 100, 2460116.795884, 2460117.433679},
|
||||||
|
{2023, 6, 21.0, 116.4074, 39.9042, 8.0, 0, 1000, 2460116.792739, 2460117.436652},
|
||||||
|
{2023, 6, 21.0, 116.4074, 39.9042, 8.0, 1, 0, 2460116.793560, 2460117.435878},
|
||||||
|
{2023, 6, 21.0, 116.4074, 39.9042, 8.0, 1, 100, 2460116.792105, 2460117.437250},
|
||||||
|
{2023, 6, 21.0, 116.4074, 39.9042, 8.0, 1, 1000, 2460116.788927, 2460117.440248},
|
||||||
|
{2023, 9, 23.0, 116.4074, 39.9042, 8.0, 0, 0, 2460211.101507, 2460211.459840},
|
||||||
|
{2023, 9, 23.0, 116.4074, 39.9042, 8.0, 0, 100, 2460211.099949, 2460211.461381},
|
||||||
|
{2023, 9, 23.0, 116.4074, 39.9042, 8.0, 0, 1000, 2460211.096593, 2460211.464710},
|
||||||
|
{2023, 9, 23.0, 116.4074, 39.9042, 8.0, 1, 0, 2460211.097463, 2460211.463851},
|
||||||
|
{2023, 9, 23.0, 116.4074, 39.9042, 8.0, 1, 100, 2460211.095924, 2460211.465374},
|
||||||
|
{2023, 9, 23.0, 116.4074, 39.9042, 8.0, 1, 1000, 2460211.092607, 2460211.468664},
|
||||||
|
{2023, 12, 22.0, 116.4074, 39.9042, 8.0, 0, 0, 2460301.062528, 2460300.594160},
|
||||||
|
{2023, 12, 22.0, 116.4074, 39.9042, 8.0, 0, 100, 2460301.061295, 2460300.595393},
|
||||||
|
{2023, 12, 22.0, 116.4074, 39.9042, 8.0, 0, 1000, 2460301.058613, 2460300.598084},
|
||||||
|
{2023, 12, 22.0, 116.4074, 39.9042, 8.0, 1, 0, 2460301.059311, 2460300.597387},
|
||||||
|
{2023, 12, 22.0, 116.4074, 39.9042, 8.0, 1, 100, 2460301.058074, 2460300.598625},
|
||||||
|
{2023, 12, 22.0, 116.4074, 39.9042, 8.0, 1, 1000, 2460301.055382, 2460300.601325},
|
||||||
|
{2024, 2, 29.0, 116.4074, 39.9042, 8.0, 0, 0, 2460370.449673, 2460369.861269},
|
||||||
|
{2024, 2, 29.0, 116.4074, 39.9042, 8.0, 0, 100, 2460370.448375, 2460369.862488},
|
||||||
|
{2024, 2, 29.0, 116.4074, 39.9042, 8.0, 0, 1000, 2460370.445568, 2460369.865132},
|
||||||
|
{2024, 2, 29.0, 116.4074, 39.9042, 8.0, 1, 0, 2460370.446299, 2460369.864446},
|
||||||
|
{2024, 2, 29.0, 116.4074, 39.9042, 8.0, 1, 100, 2460370.445007, 2460369.865661},
|
||||||
|
{2024, 2, 29.0, 116.4074, 39.9042, 8.0, 1, 1000, 2460370.442214, 2460369.868297},
|
||||||
|
{2025, 7, 4.0, 116.4074, 39.9042, 8.0, 0, 0, 2460861.080905, 2460860.500448},
|
||||||
|
{2025, 7, 4.0, 116.4074, 39.9042, 8.0, 0, 100, 2460861.079623, 2460860.501655},
|
||||||
|
{2025, 7, 4.0, 116.4074, 39.9042, 8.0, 0, 1000, 2460861.076853, 2460860.504274},
|
||||||
|
{2025, 7, 4.0, 116.4074, 39.9042, 8.0, 1, 0, 2460861.077574, 2460860.503595},
|
||||||
|
{2025, 7, 4.0, 116.4074, 39.9042, 8.0, 1, 100, 2460861.076299, 2460860.504799},
|
||||||
|
{2025, 7, 4.0, 116.4074, 39.9042, 8.0, 1, 1000, 2460861.073540, 2460860.507411},
|
||||||
|
{2023, 1, 15.0, -0.1276, 51.5074, 0.0, 0, 0, 2459959.528073, 2459959.967571},
|
||||||
|
{2023, 1, 15.0, -0.1276, 51.5074, 0.0, 0, 100, 2459959.526515, 2459959.969080},
|
||||||
|
{2023, 1, 15.0, -0.1276, 51.5074, 0.0, 0, 1000, 2459959.523150, 2459959.972345},
|
||||||
|
{2023, 1, 15.0, -0.1276, 51.5074, 0.0, 1, 0, 2459959.524029, 2459959.971495},
|
||||||
|
{2023, 1, 15.0, -0.1276, 51.5074, 0.0, 1, 100, 2459959.522479, 2459959.972996},
|
||||||
|
{2023, 1, 15.0, -0.1276, 51.5074, 0.0, 1, 1000, 2459959.519128, 2459959.976244},
|
||||||
|
{2023, 3, 20.0, -0.1276, 51.5074, 0.0, 0, 0, 2460023.756367, 2460024.186083},
|
||||||
|
{2023, 3, 20.0, -0.1276, 51.5074, 0.0, 0, 100, 2460023.754816, 2460024.187653},
|
||||||
|
{2023, 3, 20.0, -0.1276, 51.5074, 0.0, 0, 1000, 2460023.751465, 2460024.191054},
|
||||||
|
{2023, 3, 20.0, -0.1276, 51.5074, 0.0, 1, 0, 2460023.752338, 2460024.190171},
|
||||||
|
{2023, 3, 20.0, -0.1276, 51.5074, 0.0, 1, 100, 2460023.750797, 2460024.191733},
|
||||||
|
{2023, 3, 20.0, -0.1276, 51.5074, 0.0, 1, 1000, 2460023.747466, 2460024.195116},
|
||||||
|
{2023, 6, 21.0, -0.1276, 51.5074, 0.0, 0, 0, 2460116.767473, 2460117.458902},
|
||||||
|
{2023, 6, 21.0, -0.1276, 51.5074, 0.0, 0, 100, 2460116.765511, 2460117.460678},
|
||||||
|
{2023, 6, 21.0, -0.1276, 51.5074, 0.0, 0, 1000, 2460116.761206, 2460117.464572},
|
||||||
|
{2023, 6, 21.0, -0.1276, 51.5074, 0.0, 1, 0, 2460116.762340, 2460117.463551},
|
||||||
|
{2023, 6, 21.0, -0.1276, 51.5074, 0.0, 1, 100, 2460116.760335, 2460117.465357},
|
||||||
|
{2023, 6, 21.0, -0.1276, 51.5074, 0.0, 1, 1000, 2460116.755931, 2460117.469316},
|
||||||
|
{2023, 9, 23.0, -0.1276, 51.5074, 0.0, 0, 0, 2460211.152489, 2460211.418569},
|
||||||
|
{2023, 9, 23.0, -0.1276, 51.5074, 0.0, 0, 100, 2460211.150147, 2460211.420895},
|
||||||
|
{2023, 9, 23.0, -0.1276, 51.5074, 0.0, 0, 1000, 2460211.145172, 2460211.425846},
|
||||||
|
{2023, 9, 23.0, -0.1276, 51.5074, 0.0, 1, 0, 2460211.146459, 2460211.424570},
|
||||||
|
{2023, 9, 23.0, -0.1276, 51.5074, 0.0, 1, 100, 2460211.144192, 2460211.426821},
|
||||||
|
{2023, 9, 23.0, -0.1276, 51.5074, 0.0, 1, 1000, 2460211.139369, 2460211.431620},
|
||||||
|
{2023, 12, 22.0, -0.1276, 51.5074, 0.0, 0, 0, 2460301.041592, 2460300.614867},
|
||||||
|
{2023, 12, 22.0, -0.1276, 51.5074, 0.0, 0, 100, 2460301.040020, 2460300.616452},
|
||||||
|
{2023, 12, 22.0, -0.1276, 51.5074, 0.0, 0, 1000, 2460301.036594, 2460300.619912},
|
||||||
|
{2023, 12, 22.0, -0.1276, 51.5074, 0.0, 1, 0, 2460301.037491, 2460300.619011},
|
||||||
|
{2023, 12, 22.0, -0.1276, 51.5074, 0.0, 1, 100, 2460301.035906, 2460300.620607},
|
||||||
|
{2023, 12, 22.0, -0.1276, 51.5074, 0.0, 1, 1000, 2460301.032451, 2460300.624095},
|
||||||
|
{2024, 2, 29.0, -0.1276, 51.5074, 0.0, 0, 0, 2460370.477943, 2460369.838418},
|
||||||
|
{2024, 2, 29.0, -0.1276, 51.5074, 0.0, 0, 100, 2460370.476216, 2460369.839982},
|
||||||
|
{2024, 2, 29.0, -0.1276, 51.5074, 0.0, 0, 1000, 2460370.472503, 2460369.843360},
|
||||||
|
{2024, 2, 29.0, -0.1276, 51.5074, 0.0, 1, 0, 2460370.473471, 2460369.842481},
|
||||||
|
{2024, 2, 29.0, -0.1276, 51.5074, 0.0, 1, 100, 2460370.471764, 2460369.844033},
|
||||||
|
{2024, 2, 29.0, -0.1276, 51.5074, 0.0, 1, 1000, 2460370.468092, 2460369.847386},
|
||||||
|
{2025, 7, 4.0, -0.1276, 51.5074, 0.0, 0, 0, 2460861.106361, 2460861.487905},
|
||||||
|
{2025, 7, 4.0, -0.1276, 51.5074, 0.0, 0, 100, 2460861.104676, 2460861.489558},
|
||||||
|
{2025, 7, 4.0, -0.1276, 51.5074, 0.0, 0, 1000, 2460861.101051, 2460861.493119},
|
||||||
|
{2025, 7, 4.0, -0.1276, 51.5074, 0.0, 1, 0, 2460861.101998, 2460861.492193},
|
||||||
|
{2025, 7, 4.0, -0.1276, 51.5074, 0.0, 1, 100, 2460861.100330, 2460861.493828},
|
||||||
|
{2025, 7, 4.0, -0.1276, 51.5074, 0.0, 1, 1000, 2460861.096740, 2460861.497354},
|
||||||
|
{2023, 1, 15.0, -74.0060, 40.7128, -5.0, 0, 0, 2459959.524199, 2459959.980835},
|
||||||
|
{2023, 1, 15.0, -74.0060, 40.7128, -5.0, 0, 100, 2459959.522935, 2459959.982068},
|
||||||
|
{2023, 1, 15.0, -74.0060, 40.7128, -5.0, 0, 1000, 2459959.520198, 2459959.984744},
|
||||||
|
{2023, 1, 15.0, -74.0060, 40.7128, -5.0, 1, 0, 2459959.520911, 2459959.984050},
|
||||||
|
{2023, 1, 15.0, -74.0060, 40.7128, -5.0, 1, 100, 2459959.519651, 2459959.985280},
|
||||||
|
{2023, 1, 15.0, -74.0060, 40.7128, -5.0, 1, 1000, 2459959.516921, 2459959.987947},
|
||||||
|
{2023, 3, 20.0, -74.0060, 40.7128, -5.0, 0, 0, 2460023.742085, 2460024.205917},
|
||||||
|
{2023, 3, 20.0, -74.0060, 40.7128, -5.0, 0, 100, 2460023.740843, 2460024.207164},
|
||||||
|
{2023, 3, 20.0, -74.0060, 40.7128, -5.0, 0, 1000, 2460023.738150, 2460024.209876},
|
||||||
|
{2023, 3, 20.0, -74.0060, 40.7128, -5.0, 1, 0, 2460023.738849, 2460024.209175},
|
||||||
|
{2023, 3, 20.0, -74.0060, 40.7128, -5.0, 1, 100, 2460023.737610, 2460024.210419},
|
||||||
|
{2023, 3, 20.0, -74.0060, 40.7128, -5.0, 1, 1000, 2460023.734925, 2460024.213124},
|
||||||
|
{2023, 6, 21.0, -74.0060, 40.7128, -5.0, 0, 0, 2460116.805442, 2460117.432629},
|
||||||
|
{2023, 6, 21.0, -74.0060, 40.7128, -5.0, 0, 100, 2460116.804010, 2460117.433973},
|
||||||
|
{2023, 6, 21.0, -74.0060, 40.7128, -5.0, 0, 1000, 2460116.800889, 2460117.436908},
|
||||||
|
{2023, 6, 21.0, -74.0060, 40.7128, -5.0, 1, 0, 2460116.801705, 2460117.436144},
|
||||||
|
{2023, 6, 21.0, -74.0060, 40.7128, -5.0, 1, 100, 2460116.800260, 2460117.437498},
|
||||||
|
{2023, 6, 21.0, -74.0060, 40.7128, -5.0, 1, 1000, 2460116.797108, 2460117.440456},
|
||||||
|
{2023, 9, 23.0, -74.0060, 40.7128, -5.0, 0, 0, 2460211.112450, 2460211.469949},
|
||||||
|
{2023, 9, 23.0, -74.0060, 40.7128, -5.0, 0, 100, 2460211.110879, 2460211.471502},
|
||||||
|
{2023, 9, 23.0, -74.0060, 40.7128, -5.0, 0, 1000, 2460211.107495, 2460211.474858},
|
||||||
|
{2023, 9, 23.0, -74.0060, 40.7128, -5.0, 1, 0, 2460211.108372, 2460211.473992},
|
||||||
|
{2023, 9, 23.0, -74.0060, 40.7128, -5.0, 1, 100, 2460211.106820, 2460211.475527},
|
||||||
|
{2023, 9, 23.0, -74.0060, 40.7128, -5.0, 1, 1000, 2460211.103476, 2460211.478843},
|
||||||
|
{2023, 12, 22.0, -74.0060, 40.7128, -5.0, 0, 0, 2460301.058738, 2460300.608360},
|
||||||
|
{2023, 12, 22.0, -74.0060, 40.7128, -5.0, 0, 100, 2460301.057459, 2460300.609636},
|
||||||
|
{2023, 12, 22.0, -74.0060, 40.7128, -5.0, 0, 1000, 2460301.054673, 2460300.612421},
|
||||||
|
{2023, 12, 22.0, -74.0060, 40.7128, -5.0, 1, 0, 2460301.055399, 2460300.611699},
|
||||||
|
{2023, 12, 22.0, -74.0060, 40.7128, -5.0, 1, 100, 2460301.054113, 2460300.612981},
|
||||||
|
{2023, 12, 22.0, -74.0060, 40.7128, -5.0, 1, 1000, 2460301.051313, 2460300.615779},
|
||||||
|
{2024, 2, 29.0, -74.0060, 40.7128, -5.0, 0, 0, 2460370.461773, 2460369.855646},
|
||||||
|
{2024, 2, 29.0, -74.0060, 40.7128, -5.0, 0, 100, 2460370.460419, 2460369.856909},
|
||||||
|
{2024, 2, 29.0, -74.0060, 40.7128, -5.0, 0, 1000, 2460370.457495, 2460369.859648},
|
||||||
|
{2024, 2, 29.0, -74.0060, 40.7128, -5.0, 1, 0, 2460370.458256, 2460369.858938},
|
||||||
|
{2024, 2, 29.0, -74.0060, 40.7128, -5.0, 1, 100, 2460370.456911, 2460369.860196},
|
||||||
|
{2024, 2, 29.0, -74.0060, 40.7128, -5.0, 1, 1000, 2460370.454004, 2460369.862924},
|
||||||
|
{2025, 7, 4.0, -74.0060, 40.7128, -5.0, 0, 0, 2460861.092013, -3.000000},
|
||||||
|
{2025, 7, 4.0, -74.0060, 40.7128, -5.0, 0, 100, 2460861.090681, -3.000000},
|
||||||
|
{2025, 7, 4.0, -74.0060, 40.7128, -5.0, 0, 1000, 2460861.087804, -3.000000},
|
||||||
|
{2025, 7, 4.0, -74.0060, 40.7128, -5.0, 1, 0, 2460861.088553, -3.000000},
|
||||||
|
{2025, 7, 4.0, -74.0060, 40.7128, -5.0, 1, 100, 2460861.087229, -3.000000},
|
||||||
|
{2025, 7, 4.0, -74.0060, 40.7128, -5.0, 1, 1000, 2460861.084368, 2460860.501759},
|
||||||
|
{2023, 1, 15.0, 151.2093, -33.8688, 10.0, 0, 0, 2459960.490771, 2459960.006541},
|
||||||
|
{2023, 1, 15.0, 151.2093, -33.8688, 10.0, 0, 100, 2459960.489647, 2459960.007661},
|
||||||
|
{2023, 1, 15.0, 151.2093, -33.8688, 10.0, 0, 1000, 2459960.487201, 2459960.010104},
|
||||||
|
{2023, 1, 15.0, 151.2093, -33.8688, 10.0, 1, 0, 2459960.487836, 2459960.009472},
|
||||||
|
{2023, 1, 15.0, 151.2093, -33.8688, 10.0, 1, 100, 2459960.486710, 2459960.010595},
|
||||||
|
{2023, 1, 15.0, 151.2093, -33.8688, 10.0, 1, 1000, 2459960.484259, 2459960.013044},
|
||||||
|
{2023, 3, 20.0, 151.2093, -33.8688, 10.0, 0, 0, 2460023.660650, 2460024.225595},
|
||||||
|
{2023, 3, 20.0, 151.2093, -33.8688, 10.0, 0, 100, 2460023.659463, 2460024.226711},
|
||||||
|
{2023, 3, 20.0, 151.2093, -33.8688, 10.0, 0, 1000, 2460023.656880, 2460024.229146},
|
||||||
|
{2023, 3, 20.0, 151.2093, -33.8688, 10.0, 1, 0, 2460023.657552, 2460024.228516},
|
||||||
|
{2023, 3, 20.0, 151.2093, -33.8688, 10.0, 1, 100, 2460023.656361, 2460024.229635},
|
||||||
|
{2023, 3, 20.0, 151.2093, -33.8688, 10.0, 1, 1000, 2460023.653768, 2460024.232076},
|
||||||
|
{2023, 6, 21.0, 151.2093, -33.8688, 10.0, 0, 0, 2460116.898383, 2460117.306845},
|
||||||
|
{2023, 6, 21.0, 151.2093, -33.8688, 10.0, 0, 100, 2460116.897095, 2460117.308116},
|
||||||
|
{2023, 6, 21.0, 151.2093, -33.8688, 10.0, 0, 1000, 2460116.894309, 2460117.310872},
|
||||||
|
{2023, 6, 21.0, 151.2093, -33.8688, 10.0, 1, 0, 2460116.895031, 2460117.310161},
|
||||||
|
{2023, 6, 21.0, 151.2093, -33.8688, 10.0, 1, 100, 2460116.893751, 2460117.311424},
|
||||||
|
{2023, 6, 21.0, 151.2093, -33.8688, 10.0, 1, 1000, 2460116.890981, 2460117.314164},
|
||||||
|
{2023, 9, 23.0, 151.2093, -33.8688, 10.0, 0, 0, 2460210.944935, 2460210.538063},
|
||||||
|
{2023, 9, 23.0, 151.2093, -33.8688, 10.0, 0, 100, 2460210.943584, 2460210.539396},
|
||||||
|
{2023, 9, 23.0, 151.2093, -33.8688, 10.0, 0, 1000, 2460210.940634, 2460210.542315},
|
||||||
|
{2023, 9, 23.0, 151.2093, -33.8688, 10.0, 1, 0, 2460210.941402, 2460210.541559},
|
||||||
|
{2023, 9, 23.0, 151.2093, -33.8688, 10.0, 1, 100, 2460210.940040, 2460210.542904},
|
||||||
|
{2023, 9, 23.0, 151.2093, -33.8688, 10.0, 1, 1000, 2460210.937065, 2460210.545848},
|
||||||
|
{2023, 12, 22.0, 151.2093, -33.8688, 10.0, 0, 0, 2460301.101438, 2460300.536189},
|
||||||
|
{2023, 12, 22.0, 151.2093, -33.8688, 10.0, 0, 100, 2460301.100269, 2460300.537293},
|
||||||
|
{2023, 12, 22.0, 151.2093, -33.8688, 10.0, 0, 1000, 2460301.097738, 2460300.539695},
|
||||||
|
{2023, 12, 22.0, 151.2093, -33.8688, 10.0, 1, 0, 2460301.098395, 2460300.539074},
|
||||||
|
{2023, 12, 22.0, 151.2093, -33.8688, 10.0, 1, 100, 2460301.097230, 2460300.540177},
|
||||||
|
{2023, 12, 22.0, 151.2093, -33.8688, 10.0, 1, 1000, 2460301.094705, 2460300.542575},
|
||||||
|
{2024, 2, 29.0, 151.2093, -33.8688, 10.0, 0, 0, 2460370.367522, 2460369.898187},
|
||||||
|
{2024, 2, 29.0, 151.2093, -33.8688, 10.0, 0, 100, 2460370.366378, 2460369.899322},
|
||||||
|
{2024, 2, 29.0, 151.2093, -33.8688, 10.0, 0, 1000, 2460370.363890, 2460369.901801},
|
||||||
|
{2024, 2, 29.0, 151.2093, -33.8688, 10.0, 1, 0, 2460370.364537, 2460369.901160},
|
||||||
|
{2024, 2, 29.0, 151.2093, -33.8688, 10.0, 1, 100, 2460370.363390, 2460369.902299},
|
||||||
|
{2024, 2, 29.0, 151.2093, -33.8688, 10.0, 1, 1000, 2460370.360894, 2460369.904785},
|
||||||
|
{2025, 7, 4.0, 151.2093, -33.8688, 10.0, 0, 0, 2460861.005584, 2460860.530894},
|
||||||
|
{2025, 7, 4.0, 151.2093, -33.8688, 10.0, 0, 100, 2460861.004451, 2460860.532021},
|
||||||
|
{2025, 7, 4.0, 151.2093, -33.8688, 10.0, 0, 1000, 2460861.001987, 2460860.534479},
|
||||||
|
{2025, 7, 4.0, 151.2093, -33.8688, 10.0, 1, 0, 2460861.002627, 2460860.533844},
|
||||||
|
{2025, 7, 4.0, 151.2093, -33.8688, 10.0, 1, 100, 2460861.001492, 2460860.534973},
|
||||||
|
{2025, 7, 4.0, 151.2093, -33.8688, 10.0, 1, 1000, 2460860.999020, 2460860.537439},
|
||||||
|
{2023, 1, 15.0, 139.6503, 35.6762, 9.0, 0, 0, -3.000000, 2459959.964633},
|
||||||
|
{2023, 1, 15.0, 139.6503, 35.6762, 9.0, 0, 100, -3.000000, 2459959.965758},
|
||||||
|
{2023, 1, 15.0, 139.6503, 35.6762, 9.0, 0, 1000, -3.000000, 2459959.968203},
|
||||||
|
{2023, 1, 15.0, 139.6503, 35.6762, 9.0, 1, 0, -3.000000, 2459959.967570},
|
||||||
|
{2023, 1, 15.0, 139.6503, 35.6762, 9.0, 1, 100, -3.000000, 2459959.968693},
|
||||||
|
{2023, 1, 15.0, 139.6503, 35.6762, 9.0, 1, 1000, -3.000000, 2459959.971135},
|
||||||
|
{2023, 3, 20.0, 139.6503, 35.6762, 9.0, 0, 0, 2460023.713764, 2460024.169244},
|
||||||
|
{2023, 3, 20.0, 139.6503, 35.6762, 9.0, 0, 100, 2460023.712577, 2460024.170423},
|
||||||
|
{2023, 3, 20.0, 139.6503, 35.6762, 9.0, 0, 1000, 2460023.710003, 2460024.172990},
|
||||||
|
{2023, 3, 20.0, 139.6503, 35.6762, 9.0, 1, 0, 2460023.710670, 2460024.172328},
|
||||||
|
{2023, 3, 20.0, 139.6503, 35.6762, 9.0, 1, 100, 2460023.709487, 2460024.173504},
|
||||||
|
{2023, 3, 20.0, 139.6503, 35.6762, 9.0, 1, 1000, 2460023.706921, 2460024.176064},
|
||||||
|
{2023, 6, 21.0, 139.6503, 35.6762, 9.0, 0, 0, 2460116.781251, 2460117.399403},
|
||||||
|
{2023, 6, 21.0, 139.6503, 35.6762, 9.0, 0, 100, 2460116.779918, 2460117.400670},
|
||||||
|
{2023, 6, 21.0, 139.6503, 35.6762, 9.0, 0, 1000, 2460116.777011, 2460117.403439},
|
||||||
|
{2023, 6, 21.0, 139.6503, 35.6762, 9.0, 1, 0, 2460116.777769, 2460117.402720},
|
||||||
|
{2023, 6, 21.0, 139.6503, 35.6762, 9.0, 1, 100, 2460116.776425, 2460117.403996},
|
||||||
|
{2023, 6, 21.0, 139.6503, 35.6762, 9.0, 1, 1000, 2460116.773495, 2460117.406783},
|
||||||
|
{2023, 9, 23.0, 139.6503, 35.6762, 9.0, 0, 0, 2460211.063872, 2460211.445971},
|
||||||
|
{2023, 9, 23.0, 139.6503, 35.6762, 9.0, 0, 100, 2460211.062451, 2460211.447375},
|
||||||
|
{2023, 9, 23.0, 139.6503, 35.6762, 9.0, 0, 1000, 2460211.059383, 2460211.450416},
|
||||||
|
{2023, 9, 23.0, 139.6503, 35.6762, 9.0, 1, 0, 2460211.060178, 2460211.449632},
|
||||||
|
{2023, 9, 23.0, 139.6503, 35.6762, 9.0, 1, 100, 2460211.058770, 2460211.451024},
|
||||||
|
{2023, 9, 23.0, 139.6503, 35.6762, 9.0, 1, 1000, 2460211.055729, 2460211.454038},
|
||||||
|
{2023, 12, 22.0, 139.6503, 35.6762, 9.0, 0, 0, 2460301.042488, 2460300.564993},
|
||||||
|
{2023, 12, 22.0, 139.6503, 35.6762, 9.0, 0, 100, 2460301.041330, 2460300.566149},
|
||||||
|
{2023, 12, 22.0, 139.6503, 35.6762, 9.0, 0, 1000, 2460301.038808, 2460300.568671},
|
||||||
|
{2023, 12, 22.0, 139.6503, 35.6762, 9.0, 1, 0, 2460301.039464, 2460300.568019},
|
||||||
|
{2023, 12, 22.0, 139.6503, 35.6762, 9.0, 1, 100, 2460301.038302, 2460300.569178},
|
||||||
|
{2023, 12, 22.0, 139.6503, 35.6762, 9.0, 1, 1000, 2460301.035773, 2460300.571707},
|
||||||
|
{2024, 2, 29.0, 139.6503, 35.6762, 9.0, 0, 0, 2460370.418418, 2460369.841663},
|
||||||
|
{2024, 2, 29.0, 139.6503, 35.6762, 9.0, 0, 100, 2460370.417208, 2460369.842807},
|
||||||
|
{2024, 2, 29.0, 139.6503, 35.6762, 9.0, 0, 1000, 2460370.414589, 2460369.845291},
|
||||||
|
{2024, 2, 29.0, 139.6503, 35.6762, 9.0, 1, 0, 2460370.415270, 2460369.844648},
|
||||||
|
{2024, 2, 29.0, 139.6503, 35.6762, 9.0, 1, 100, 2460370.414065, 2460369.845789},
|
||||||
|
{2024, 2, 29.0, 139.6503, 35.6762, 9.0, 1, 1000, 2460370.411455, 2460369.848267},
|
||||||
|
{2025, 7, 4.0, 139.6503, 35.6762, 9.0, 0, 0, 2460861.050307, 2460861.497476},
|
||||||
|
{2025, 7, 4.0, 139.6503, 35.6762, 9.0, 0, 100, 2460861.049110, 2460861.498651},
|
||||||
|
{2025, 7, 4.0, 139.6503, 35.6762, 9.0, 0, 1000, 2460861.046520, -3.000000},
|
||||||
|
{2025, 7, 4.0, 139.6503, 35.6762, 9.0, 1, 0, 2460861.047194, -3.000000},
|
||||||
|
{2025, 7, 4.0, 139.6503, 35.6762, 9.0, 1, 100, 2460861.046002, -3.000000},
|
||||||
|
{2025, 7, 4.0, 139.6503, 35.6762, 9.0, 1, 1000, 2460861.043420, -3.000000},
|
||||||
|
{2023, 1, 15.0, 2.3522, 48.8566, 1.0, 0, 0, 2459959.559564, 2459960.005972},
|
||||||
|
{2023, 1, 15.0, 2.3522, 48.8566, 1.0, 0, 100, 2459959.558099, 2459960.007394},
|
||||||
|
{2023, 1, 15.0, 2.3522, 48.8566, 1.0, 0, 1000, 2459959.554933, 2459960.010471},
|
||||||
|
{2023, 1, 15.0, 2.3522, 48.8566, 1.0, 1, 0, 2459959.555759, 2459960.009670},
|
||||||
|
{2023, 1, 15.0, 2.3522, 48.8566, 1.0, 1, 100, 2459959.554300, 2459960.011086},
|
||||||
|
{2023, 1, 15.0, 2.3522, 48.8566, 1.0, 1, 1000, 2459959.551145, 2459960.014149},
|
||||||
|
{2023, 3, 20.0, 2.3522, 48.8566, 1.0, 0, 0, 2460023.786730, 2460024.223914},
|
||||||
|
{2023, 3, 20.0, 2.3522, 48.8566, 1.0, 0, 100, 2460023.785269, 2460024.225388},
|
||||||
|
{2023, 3, 20.0, 2.3522, 48.8566, 1.0, 0, 1000, 2460023.782112, 2460024.228584},
|
||||||
|
{2023, 3, 20.0, 2.3522, 48.8566, 1.0, 1, 0, 2460023.782934, 2460024.227755},
|
||||||
|
{2023, 3, 20.0, 2.3522, 48.8566, 1.0, 1, 100, 2460023.781482, 2460024.229222},
|
||||||
|
{2023, 3, 20.0, 2.3522, 48.8566, 1.0, 1, 1000, 2460023.778340, 2460024.232405},
|
||||||
|
{2023, 6, 21.0, 2.3522, 48.8566, 1.0, 0, 0, 2460116.811447, 2460117.485600},
|
||||||
|
{2023, 6, 21.0, 2.3522, 48.8566, 1.0, 0, 100, 2460116.809659, 2460117.487241},
|
||||||
|
{2023, 6, 21.0, 2.3522, 48.8566, 1.0, 0, 1000, 2460116.805742, 2460117.490834},
|
||||||
|
{2023, 6, 21.0, 2.3522, 48.8566, 1.0, 1, 0, 2460116.806771, 2460117.489894},
|
||||||
|
{2023, 6, 21.0, 2.3522, 48.8566, 1.0, 1, 100, 2460116.804952, 2460117.491558},
|
||||||
|
{2023, 6, 21.0, 2.3522, 48.8566, 1.0, 1, 1000, 2460116.800964, 2460117.495201},
|
||||||
|
{2023, 9, 23.0, 2.3522, 48.8566, 1.0, 0, 0, 2460211.173246, 2460211.466625},
|
||||||
|
{2023, 9, 23.0, 2.3522, 48.8566, 1.0, 0, 100, 2460211.171184, 2460211.468671},
|
||||||
|
{2023, 9, 23.0, 2.3522, 48.8566, 1.0, 0, 1000, 2460211.166780, 2460211.473050},
|
||||||
|
{2023, 9, 23.0, 2.3522, 48.8566, 1.0, 1, 0, 2460211.167921, 2460211.471920},
|
||||||
|
{2023, 9, 23.0, 2.3522, 48.8566, 1.0, 1, 100, 2460211.165908, 2460211.473916},
|
||||||
|
{2023, 9, 23.0, 2.3522, 48.8566, 1.0, 1, 1000, 2460211.161606, 2460211.478194},
|
||||||
|
{2023, 12, 22.0, 2.3522, 48.8566, 1.0, 0, 0, 2460301.080943, 2460300.645364},
|
||||||
|
{2023, 12, 22.0, 2.3522, 48.8566, 1.0, 0, 100, 2460301.079466, 2460300.646849},
|
||||||
|
{2023, 12, 22.0, 2.3522, 48.8566, 1.0, 0, 1000, 2460301.076249, 2460300.650092},
|
||||||
|
{2023, 12, 22.0, 2.3522, 48.8566, 1.0, 1, 0, 2460301.077090, 2460300.649248},
|
||||||
|
{2023, 12, 22.0, 2.3522, 48.8566, 1.0, 1, 100, 2460301.075603, 2460300.650743},
|
||||||
|
{2023, 12, 22.0, 2.3522, 48.8566, 1.0, 1, 1000, 2460301.072363, 2460300.654007},
|
||||||
|
{2024, 2, 29.0, 2.3522, 48.8566, 1.0, 0, 0, -3.000000, 2460369.878053},
|
||||||
|
{2024, 2, 29.0, 2.3522, 48.8566, 1.0, 0, 100, -3.000000, 2460369.879520},
|
||||||
|
{2024, 2, 29.0, 2.3522, 48.8566, 1.0, 0, 1000, -3.000000, 2460369.882691},
|
||||||
|
{2024, 2, 29.0, 2.3522, 48.8566, 1.0, 1, 0, -3.000000, 2460369.881866},
|
||||||
|
{2024, 2, 29.0, 2.3522, 48.8566, 1.0, 1, 100, -3.000000, 2460369.883323},
|
||||||
|
{2024, 2, 29.0, 2.3522, 48.8566, 1.0, 1, 1000, 2460370.496709, 2460369.886475},
|
||||||
|
{2025, 7, 4.0, 2.3522, 48.8566, 1.0, 0, 0, 2460861.134958, 2460860.518461},
|
||||||
|
{2025, 7, 4.0, 2.3522, 48.8566, 1.0, 0, 100, 2460861.133389, 2460860.519905},
|
||||||
|
{2025, 7, 4.0, 2.3522, 48.8566, 1.0, 0, 1000, 2460861.130011, 2460860.523028},
|
||||||
|
{2025, 7, 4.0, 2.3522, 48.8566, 1.0, 1, 0, 2460861.130892, 2460860.522216},
|
||||||
|
{2025, 7, 4.0, 2.3522, 48.8566, 1.0, 1, 100, 2460861.129337, 2460860.523652},
|
||||||
|
{2025, 7, 4.0, 2.3522, 48.8566, 1.0, 1, 1000, 2460861.125986, 2460860.526759},
|
||||||
|
{2023, 1, 15.0, -118.2437, 34.0522, -8.0, 0, 0, 2459959.522069, 2459959.987849},
|
||||||
|
{2023, 1, 15.0, -118.2437, 34.0522, -8.0, 0, 100, 2459959.520918, 2459959.988976},
|
||||||
|
{2023, 1, 15.0, -118.2437, 34.0522, -8.0, 0, 1000, 2459959.518423, 2459959.991425},
|
||||||
|
{2023, 1, 15.0, -118.2437, 34.0522, -8.0, 1, 0, 2459959.519071, 2459959.990791},
|
||||||
|
{2023, 1, 15.0, -118.2437, 34.0522, -8.0, 1, 100, 2459959.517923, 2459959.991916},
|
||||||
|
{2023, 1, 15.0, -118.2437, 34.0522, -8.0, 1, 1000, 2459959.515433, 2459959.994359},
|
||||||
|
{2023, 3, 20.0, -118.2437, 34.0522, -8.0, 0, 0, 2460023.736567, 2460024.214432},
|
||||||
|
{2023, 3, 20.0, -118.2437, 34.0522, -8.0, 0, 100, 2460023.735438, 2460024.215558},
|
||||||
|
{2023, 3, 20.0, -118.2437, 34.0522, -8.0, 0, 1000, 2460023.732989, 2460024.218013},
|
||||||
|
{2023, 3, 20.0, -118.2437, 34.0522, -8.0, 1, 0, 2460023.733624, 2460024.217380},
|
||||||
|
{2023, 3, 20.0, -118.2437, 34.0522, -8.0, 1, 100, 2460023.732497, 2460024.218505},
|
||||||
|
{2023, 3, 20.0, -118.2437, 34.0522, -8.0, 1, 1000, 2460023.730052, 2460024.220957},
|
||||||
|
{2023, 6, 21.0, -118.2437, 34.0522, -8.0, 0, 0, 2460116.821912, 2460117.421511},
|
||||||
|
{2023, 6, 21.0, -118.2437, 34.0522, -8.0, 0, 100, 2460116.820648, 2460117.422709},
|
||||||
|
{2023, 6, 21.0, -118.2437, 34.0522, -8.0, 0, 1000, 2460116.817895, 2460117.425326},
|
||||||
|
{2023, 6, 21.0, -118.2437, 34.0522, -8.0, 1, 0, 2460116.818612, 2460117.424647},
|
||||||
|
{2023, 6, 21.0, -118.2437, 34.0522, -8.0, 1, 100, 2460116.817341, 2460117.425852},
|
||||||
|
{2023, 6, 21.0, -118.2437, 34.0522, -8.0, 1, 1000, 2460116.814570, 2460117.428482},
|
||||||
|
{2023, 9, 23.0, -118.2437, 34.0522, -8.0, 0, 0, 2460211.096832, 2460211.491736},
|
||||||
|
{2023, 9, 23.0, -118.2437, 34.0522, -8.0, 0, 100, 2460211.095470, 2460211.493079},
|
||||||
|
{2023, 9, 23.0, -118.2437, 34.0522, -8.0, 0, 1000, 2460211.092526, 2460211.495992},
|
||||||
|
{2023, 9, 23.0, -118.2437, 34.0522, -8.0, 1, 0, 2460211.093289, 2460211.495241},
|
||||||
|
{2023, 9, 23.0, -118.2437, 34.0522, -8.0, 1, 100, 2460211.091937, 2460211.496574},
|
||||||
|
{2023, 9, 23.0, -118.2437, 34.0522, -8.0, 1, 1000, 2460211.089016, 2460211.499464},
|
||||||
|
{2023, 12, 22.0, -118.2437, 34.0522, -8.0, 0, 0, 2460301.067741, 2460300.604932},
|
||||||
|
{2023, 12, 22.0, -118.2437, 34.0522, -8.0, 0, 100, 2460301.066573, 2460300.606090},
|
||||||
|
{2023, 12, 22.0, -118.2437, 34.0522, -8.0, 0, 1000, 2460301.064029, 2460300.608620},
|
||||||
|
{2023, 12, 22.0, -118.2437, 34.0522, -8.0, 1, 0, 2460301.064690, 2460300.607966},
|
||||||
|
{2023, 12, 22.0, -118.2437, 34.0522, -8.0, 1, 100, 2460301.063518, 2460300.609128},
|
||||||
|
{2023, 12, 22.0, -118.2437, 34.0522, -8.0, 1, 1000, 2460301.060965, 2460300.611667},
|
||||||
|
{2024, 2, 29.0, -118.2437, 34.0522, -8.0, 0, 0, 2460370.454262, 2460369.864349},
|
||||||
|
{2024, 2, 29.0, -118.2437, 34.0522, -8.0, 0, 100, 2460370.453043, 2460369.865499},
|
||||||
|
{2024, 2, 29.0, -118.2437, 34.0522, -8.0, 0, 1000, 2460370.450405, 2460369.867997},
|
||||||
|
{2024, 2, 29.0, -118.2437, 34.0522, -8.0, 1, 0, 2460370.451090, 2460369.867351},
|
||||||
|
{2024, 2, 29.0, -118.2437, 34.0522, -8.0, 1, 100, 2460370.449876, 2460369.868497},
|
||||||
|
{2024, 2, 29.0, -118.2437, 34.0522, -8.0, 1, 1000, 2460370.447250, 2460369.870987},
|
||||||
|
{2025, 7, 4.0, -118.2437, 34.0522, -8.0, 0, 0, 2460861.085220, 2460860.502309},
|
||||||
|
{2025, 7, 4.0, -118.2437, 34.0522, -8.0, 0, 100, 2460861.084017, 2460860.503446},
|
||||||
|
{2025, 7, 4.0, -118.2437, 34.0522, -8.0, 0, 1000, 2460861.081416, 2460860.505916},
|
||||||
|
{2025, 7, 4.0, -118.2437, 34.0522, -8.0, 1, 0, 2460861.082092, 2460860.505277},
|
||||||
|
{2025, 7, 4.0, -118.2437, 34.0522, -8.0, 1, 100, 2460861.080895, 2460860.506411},
|
||||||
|
{2025, 7, 4.0, -118.2437, 34.0522, -8.0, 1, 1000, 2460861.078303, 2460860.508875},
|
||||||
|
{2023, 1, 15.0, 37.6176, 55.7558, 3.0, 0, 0, 2459959.547825, 2459959.980183},
|
||||||
|
{2023, 1, 15.0, 37.6176, 55.7558, 3.0, 0, 100, 2459959.546091, 2459959.981857},
|
||||||
|
{2023, 1, 15.0, 37.6176, 55.7558, 3.0, 0, 1000, 2459959.542352, 2459959.985470},
|
||||||
|
{2023, 1, 15.0, 37.6176, 55.7558, 3.0, 1, 0, 2459959.543330, 2459959.984528},
|
||||||
|
{2023, 1, 15.0, 37.6176, 55.7558, 3.0, 1, 100, 2459959.541607, 2459959.986191},
|
||||||
|
{2023, 1, 15.0, 37.6176, 55.7558, 3.0, 1, 1000, 2459959.537887, 2459959.989782},
|
||||||
|
{2023, 3, 20.0, 37.6176, 55.7558, 3.0, 0, 0, 2460023.783999, 2460024.192547},
|
||||||
|
{2023, 3, 20.0, 37.6176, 55.7558, 3.0, 0, 100, 2460023.782247, 2460024.194326},
|
||||||
|
{2023, 3, 20.0, 37.6176, 55.7558, 3.0, 0, 1000, 2460023.778469, 2460024.198172},
|
||||||
|
{2023, 3, 20.0, 37.6176, 55.7558, 3.0, 1, 0, 2460023.779453, 2460024.197173},
|
||||||
|
{2023, 3, 20.0, 37.6176, 55.7558, 3.0, 1, 100, 2460023.777717, 2460024.198938},
|
||||||
|
{2023, 3, 20.0, 37.6176, 55.7558, 3.0, 1, 1000, 2460023.773972, 2460024.202755},
|
||||||
|
{2023, 6, 21.0, 37.6176, 55.7558, 3.0, 0, 0, 2460116.763078, 2460117.493338},
|
||||||
|
{2023, 6, 21.0, 37.6176, 55.7558, 3.0, 0, 100, 2460116.760673, 2460117.495441},
|
||||||
|
{2023, 6, 21.0, 37.6176, 55.7558, 3.0, 0, 1000, 2460116.755357, -3.000000},
|
||||||
|
{2023, 6, 21.0, 37.6176, 55.7558, 3.0, 1, 0, 2460116.756765, 2460117.498850},
|
||||||
|
{2023, 6, 21.0, 37.6176, 55.7558, 3.0, 1, 100, 2460116.754275, -3.000000},
|
||||||
|
{2023, 6, 21.0, 37.6176, 55.7558, 3.0, 1, 1000, 2460116.748756, -3.000000},
|
||||||
|
{2023, 9, 23.0, 37.6176, 55.7558, 3.0, 0, 0, 2460211.198496, 2460211.403741},
|
||||||
|
{2023, 9, 23.0, 37.6176, 55.7558, 3.0, 0, 100, 2460211.195303, 2460211.406919},
|
||||||
|
{2023, 9, 23.0, 37.6176, 55.7558, 3.0, 0, 1000, 2460211.188650, 2460211.413549},
|
||||||
|
{2023, 9, 23.0, 37.6176, 55.7558, 3.0, 1, 0, 2460211.190358, 2460211.411852},
|
||||||
|
{2023, 9, 23.0, 37.6176, 55.7558, 3.0, 1, 100, 2460211.187360, 2460211.414835},
|
||||||
|
{2023, 9, 23.0, 37.6176, 55.7558, 3.0, 1, 1000, 2460211.181078, 2460211.421094},
|
||||||
|
{2023, 12, 22.0, 37.6176, 55.7558, 3.0, 0, 0, 2460301.052264, 2460300.635877},
|
||||||
|
{2023, 12, 22.0, 37.6176, 55.7558, 3.0, 0, 100, 2460301.050516, 2460300.637649},
|
||||||
|
{2023, 12, 22.0, 37.6176, 55.7558, 3.0, 0, 1000, 2460301.046704, 2460300.641518},
|
||||||
|
{2023, 12, 22.0, 37.6176, 55.7558, 3.0, 1, 0, 2460301.047704, 2460300.640507},
|
||||||
|
{2023, 12, 22.0, 37.6176, 55.7558, 3.0, 1, 100, 2460301.045938, 2460300.642295},
|
||||||
|
{2023, 12, 22.0, 37.6176, 55.7558, 3.0, 1, 1000, 2460301.042086, 2460300.646202},
|
||||||
|
{2024, 2, 29.0, 37.6176, 55.7558, 3.0, 0, 0, -3.000000, 2460369.848787},
|
||||||
|
{2024, 2, 29.0, 37.6176, 55.7558, 3.0, 0, 100, -3.000000, 2460369.850534},
|
||||||
|
{2024, 2, 29.0, 37.6176, 55.7558, 3.0, 0, 1000, 2460370.498519, 2460369.854299},
|
||||||
|
{2024, 2, 29.0, 37.6176, 55.7558, 3.0, 1, 0, 2460370.499620, 2460369.853318},
|
||||||
|
{2024, 2, 29.0, 37.6176, 55.7558, 3.0, 1, 100, 2460370.497681, 2460369.855048},
|
||||||
|
{2024, 2, 29.0, 37.6176, 55.7558, 3.0, 1, 1000, 2460370.493523, 2460369.858778},
|
||||||
|
{2025, 7, 4.0, 37.6176, 55.7558, 3.0, 0, 0, 2460861.132034, 2460861.495198},
|
||||||
|
{2025, 7, 4.0, 37.6176, 55.7558, 3.0, 0, 100, 2460861.130125, 2460861.497068},
|
||||||
|
{2025, 7, 4.0, 37.6176, 55.7558, 3.0, 0, 1000, 2460861.126028, -3.000000},
|
||||||
|
{2025, 7, 4.0, 37.6176, 55.7558, 3.0, 1, 0, 2460861.127098, -3.000000},
|
||||||
|
{2025, 7, 4.0, 37.6176, 55.7558, 3.0, 1, 100, 2460861.125214, -3.000000},
|
||||||
|
{2025, 7, 4.0, 37.6176, 55.7558, 3.0, 1, 1000, 2460861.121170, 2460860.500614},
|
||||||
|
{2023, 1, 15.0, 31.2357, 30.0444, 2.0, 0, 0, 2459959.503589, 2459959.983951},
|
||||||
|
{2023, 1, 15.0, 31.2357, 30.0444, 2.0, 0, 100, 2459959.502503, 2459959.985013},
|
||||||
|
{2023, 1, 15.0, 31.2357, 30.0444, 2.0, 0, 1000, 2459959.500147, 2459959.987324},
|
||||||
|
{2023, 1, 15.0, 31.2357, 30.0444, 2.0, 1, 0, 2459959.500758, 2459959.986727},
|
||||||
|
{2023, 1, 15.0, 31.2357, 30.0444, 2.0, 1, 100, -3.000000, 2459959.987788},
|
||||||
|
{2023, 1, 15.0, 31.2357, 30.0444, 2.0, 1, 1000, -3.000000, 2459959.990096},
|
||||||
|
{2023, 3, 20.0, 31.2357, 30.0444, 2.0, 0, 0, 2460023.724714, 2460024.198340},
|
||||||
|
{2023, 3, 20.0, 31.2357, 30.0444, 2.0, 0, 100, 2460023.723619, 2460024.199426},
|
||||||
|
{2023, 3, 20.0, 31.2357, 30.0444, 2.0, 0, 1000, 2460023.721241, 2460024.201793},
|
||||||
|
{2023, 3, 20.0, 31.2357, 30.0444, 2.0, 1, 0, 2460023.721856, 2460024.201183},
|
||||||
|
{2023, 3, 20.0, 31.2357, 30.0444, 2.0, 1, 100, 2460023.720764, 2460024.202268},
|
||||||
|
{2023, 3, 20.0, 31.2357, 30.0444, 2.0, 1, 1000, 2460023.718390, 2460024.204631},
|
||||||
|
{2023, 6, 21.0, 31.2357, 30.0444, 2.0, 0, 0, 2460116.813768, 2460117.406719},
|
||||||
|
{2023, 6, 21.0, 31.2357, 30.0444, 2.0, 0, 100, 2460116.812556, 2460117.407876},
|
||||||
|
{2023, 6, 21.0, 31.2357, 30.0444, 2.0, 0, 1000, 2460116.809916, 2460117.410402},
|
||||||
|
{2023, 6, 21.0, 31.2357, 30.0444, 2.0, 1, 0, 2460116.810603, 2460117.409748},
|
||||||
|
{2023, 6, 21.0, 31.2357, 30.0444, 2.0, 1, 100, 2460116.809385, 2460117.410911},
|
||||||
|
{2023, 6, 21.0, 31.2357, 30.0444, 2.0, 1, 1000, 2460116.806730, 2460117.413449},
|
||||||
|
{2023, 9, 23.0, 31.2357, 30.0444, 2.0, 0, 0, 2460211.072262, 2460211.482710},
|
||||||
|
{2023, 9, 23.0, 31.2357, 30.0444, 2.0, 0, 100, 2460211.070975, 2460211.483979},
|
||||||
|
{2023, 9, 23.0, 31.2357, 30.0444, 2.0, 0, 1000, 2460211.068188, 2460211.486735},
|
||||||
|
{2023, 9, 23.0, 31.2357, 30.0444, 2.0, 1, 0, 2460211.068910, 2460211.486026},
|
||||||
|
{2023, 9, 23.0, 31.2357, 30.0444, 2.0, 1, 100, 2460211.067630, 2460211.487287},
|
||||||
|
{2023, 9, 23.0, 31.2357, 30.0444, 2.0, 1, 1000, 2460211.064861, 2460211.490027},
|
||||||
|
{2023, 12, 22.0, 31.2357, 30.0444, 2.0, 0, 0, 2460301.063789, 2460300.584090},
|
||||||
|
{2023, 12, 22.0, 31.2357, 30.0444, 2.0, 0, 100, 2460301.062692, 2460300.585176},
|
||||||
|
{2023, 12, 22.0, 31.2357, 30.0444, 2.0, 0, 1000, 2460301.060304, 2460300.587550},
|
||||||
|
{2023, 12, 22.0, 31.2357, 30.0444, 2.0, 1, 0, 2460301.060923, 2460300.586938},
|
||||||
|
{2023, 12, 22.0, 31.2357, 30.0444, 2.0, 1, 100, 2460301.059824, 2460300.588027},
|
||||||
|
{2023, 12, 22.0, 31.2357, 30.0444, 2.0, 1, 1000, 2460301.057430, 2460300.590407},
|
||||||
|
{2024, 2, 29.0, 31.2357, 30.0444, 2.0, 0, 0, 2460370.433373, 2460369.862017},
|
||||||
|
{2024, 2, 29.0, 31.2357, 30.0444, 2.0, 0, 100, 2460370.432237, 2460369.863096},
|
||||||
|
{2024, 2, 29.0, 31.2357, 30.0444, 2.0, 0, 1000, 2460370.429775, 2460369.865444},
|
||||||
|
{2024, 2, 29.0, 31.2357, 30.0444, 2.0, 1, 0, 2460370.430414, 2460369.864837},
|
||||||
|
{2024, 2, 29.0, 31.2357, 30.0444, 2.0, 1, 100, 2460370.429281, 2460369.865915},
|
||||||
|
{2024, 2, 29.0, 31.2357, 30.0444, 2.0, 1, 1000, 2460370.426826, 2460369.868257},
|
||||||
|
{2025, 7, 4.0, 31.2357, 30.0444, 2.0, 0, 0, 2460861.065509, -3.000000},
|
||||||
|
{2025, 7, 4.0, 31.2357, 30.0444, 2.0, 0, 100, 2460861.064384, 2460860.500980},
|
||||||
|
{2025, 7, 4.0, 31.2357, 30.0444, 2.0, 0, 1000, 2460861.061949, 2460860.503308},
|
||||||
|
{2025, 7, 4.0, 31.2357, 30.0444, 2.0, 1, 0, 2460861.062582, 2460860.502699},
|
||||||
|
{2025, 7, 4.0, 31.2357, 30.0444, 2.0, 1, 100, 2460861.061461, 2460860.503775},
|
||||||
|
{2025, 7, 4.0, 31.2357, 30.0444, 2.0, 1, 1000, 2460861.059032, 2460860.506098},
|
||||||
|
{2023, 1, 15.0, -43.1729, -22.9068, -3.0, 0, 0, -3.000000, 2459960.019905},
|
||||||
|
{2023, 1, 15.0, -43.1729, -22.9068, -3.0, 0, 100, -3.000000, 2459960.020918},
|
||||||
|
{2023, 1, 15.0, -43.1729, -22.9068, -3.0, 0, 1000, -3.000000, 2459960.023131},
|
||||||
|
{2023, 1, 15.0, -43.1729, -22.9068, -3.0, 1, 0, -3.000000, 2459960.022561},
|
||||||
|
{2023, 1, 15.0, -43.1729, -22.9068, -3.0, 1, 100, -3.000000, 2459960.023576},
|
||||||
|
{2023, 1, 15.0, -43.1729, -22.9068, -3.0, 1, 1000, -3.000000, 2459960.025793},
|
||||||
|
{2023, 3, 20.0, -43.1729, -22.9068, -3.0, 0, 0, 2460023.694314, 2460024.230676},
|
||||||
|
{2023, 3, 20.0, -43.1729, -22.9068, -3.0, 0, 100, 2460023.693279, 2460024.231665},
|
||||||
|
{2023, 3, 20.0, -43.1729, -22.9068, -3.0, 0, 1000, 2460023.691025, 2460024.233826},
|
||||||
|
{2023, 3, 20.0, -43.1729, -22.9068, -3.0, 1, 0, 2460023.691609, 2460024.233270},
|
||||||
|
{2023, 3, 20.0, -43.1729, -22.9068, -3.0, 1, 100, 2460023.690571, 2460024.234261},
|
||||||
|
{2023, 3, 20.0, -43.1729, -22.9068, -3.0, 1, 1000, 2460023.688314, 2460024.236424},
|
||||||
|
{2023, 6, 21.0, -43.1729, -22.9068, -3.0, 0, 0, 2460116.892241, 2460117.344631},
|
||||||
|
{2023, 6, 21.0, -43.1729, -22.9068, -3.0, 0, 100, 2460116.891136, 2460117.345716},
|
||||||
|
{2023, 6, 21.0, -43.1729, -22.9068, -3.0, 0, 1000, 2460116.888738, 2460117.348079},
|
||||||
|
{2023, 6, 21.0, -43.1729, -22.9068, -3.0, 1, 0, 2460116.889358, 2460117.347471},
|
||||||
|
{2023, 6, 21.0, -43.1729, -22.9068, -3.0, 1, 100, 2460116.888256, 2460117.348553},
|
||||||
|
{2023, 6, 21.0, -43.1729, -22.9068, -3.0, 1, 1000, 2460116.885865, 2460117.350908},
|
||||||
|
{2023, 9, 23.0, -43.1729, -22.9068, -3.0, 0, 0, 2460210.989126, 2460210.536957},
|
||||||
|
{2023, 9, 23.0, -43.1729, -22.9068, -3.0, 0, 100, 2460210.987956, 2460210.538111},
|
||||||
|
{2023, 9, 23.0, -43.1729, -22.9068, -3.0, 0, 1000, 2460210.985404, 2460210.540637},
|
||||||
|
{2023, 9, 23.0, -43.1729, -22.9068, -3.0, 1, 0, 2460210.986066, 2460210.539987},
|
||||||
|
{2023, 9, 23.0, -43.1729, -22.9068, -3.0, 1, 100, 2460210.984890, 2460210.541146},
|
||||||
|
{2023, 9, 23.0, -43.1729, -22.9068, -3.0, 1, 1000, 2460210.982326, 2460210.543684},
|
||||||
|
{2023, 12, 22.0, -43.1729, -22.9068, -3.0, 0, 0, 2460301.112646, 2460300.554998},
|
||||||
|
{2023, 12, 22.0, -43.1729, -22.9068, -3.0, 0, 100, 2460301.111589, 2460300.556005},
|
||||||
|
{2023, 12, 22.0, -43.1729, -22.9068, -3.0, 0, 1000, 2460301.109294, 2460300.558202},
|
||||||
|
{2023, 12, 22.0, -43.1729, -22.9068, -3.0, 1, 0, 2460301.109888, 2460300.557636},
|
||||||
|
{2023, 12, 22.0, -43.1729, -22.9068, -3.0, 1, 100, 2460301.108833, 2460300.558643},
|
||||||
|
{2023, 12, 22.0, -43.1729, -22.9068, -3.0, 1, 1000, 2460301.106543, 2460300.560837},
|
||||||
|
{2024, 2, 29.0, -43.1729, -22.9068, -3.0, 0, 0, 2460370.388602, 2460369.907565},
|
||||||
|
{2024, 2, 29.0, -43.1729, -22.9068, -3.0, 0, 100, 2460370.387558, 2460369.908592},
|
||||||
|
{2024, 2, 29.0, -43.1729, -22.9068, -3.0, 0, 1000, 2460370.385285, 2460369.910835},
|
||||||
|
{2024, 2, 29.0, -43.1729, -22.9068, -3.0, 1, 0, 2460370.385874, 2460369.910257},
|
||||||
|
{2024, 2, 29.0, -43.1729, -22.9068, -3.0, 1, 100, 2460370.384828, 2460369.911287},
|
||||||
|
{2024, 2, 29.0, -43.1729, -22.9068, -3.0, 1, 1000, 2460370.382551, 2460369.913535},
|
||||||
|
{2025, 7, 4.0, -43.1729, -22.9068, -3.0, 0, 0, 2460861.024935, 2460860.540907},
|
||||||
|
{2025, 7, 4.0, -43.1729, -22.9068, -3.0, 0, 100, 2460861.023901, 2460860.541926},
|
||||||
|
{2025, 7, 4.0, -43.1729, -22.9068, -3.0, 0, 1000, 2460861.021651, 2460860.544150},
|
||||||
|
{2025, 7, 4.0, -43.1729, -22.9068, -3.0, 1, 0, 2460861.022235, 2460860.543577},
|
||||||
|
{2025, 7, 4.0, -43.1729, -22.9068, -3.0, 1, 100, 2460861.021199, 2460860.544598},
|
||||||
|
{2025, 7, 4.0, -43.1729, -22.9068, -3.0, 1, 1000, 2460861.018944, 2460860.546826},
|
||||||
|
{2023, 1, 15.0, 103.8198, 1.3521, 8.0, 0, 0, 2459959.533659, 2459960.042803},
|
||||||
|
{2023, 1, 15.0, 103.8198, 1.3521, 8.0, 0, 100, 2459959.532736, 2459960.043718},
|
||||||
|
{2023, 1, 15.0, 103.8198, 1.3521, 8.0, 0, 1000, 2459959.530726, 2459960.045716},
|
||||||
|
{2023, 1, 15.0, 103.8198, 1.3521, 8.0, 1, 0, 2459959.531245, 2459960.045203},
|
||||||
|
{2023, 1, 15.0, 103.8198, 1.3521, 8.0, 1, 100, 2459959.530321, 2459960.046118},
|
||||||
|
{2023, 1, 15.0, 103.8198, 1.3521, 8.0, 1, 1000, 2459959.528311, 2459960.048117},
|
||||||
|
{2023, 3, 20.0, 103.8198, 1.3521, 8.0, 0, 0, 2460023.744413, 2460024.255583},
|
||||||
|
{2023, 3, 20.0, 103.8198, 1.3521, 8.0, 0, 100, 2460023.743460, 2460024.256507},
|
||||||
|
{2023, 3, 20.0, 103.8198, 1.3521, 8.0, 0, 1000, 2460023.741384, 2460024.258530},
|
||||||
|
{2023, 3, 20.0, 103.8198, 1.3521, 8.0, 1, 0, 2460023.741919, 2460024.258011},
|
||||||
|
{2023, 3, 20.0, 103.8198, 1.3521, 8.0, 1, 100, 2460023.740966, 2460024.258937},
|
||||||
|
{2023, 3, 20.0, 103.8198, 1.3521, 8.0, 1, 1000, 2460023.738890, 2460024.260959},
|
||||||
|
{2023, 6, 21.0, 103.8198, 1.3521, 8.0, 0, 0, 2460116.896343, 2460117.411271},
|
||||||
|
{2023, 6, 21.0, 103.8198, 1.3521, 8.0, 0, 100, 2460116.895329, 2460117.412257},
|
||||||
|
{2023, 6, 21.0, 103.8198, 1.3521, 8.0, 0, 1000, 2460116.893122, 2460117.414410},
|
||||||
|
{2023, 6, 21.0, 103.8198, 1.3521, 8.0, 1, 0, 2460116.893692, 2460117.413857},
|
||||||
|
{2023, 6, 21.0, 103.8198, 1.3521, 8.0, 1, 100, 2460116.892677, 2460117.414843},
|
||||||
|
{2023, 6, 21.0, 103.8198, 1.3521, 8.0, 1, 1000, 2460116.890469, 2460117.416997},
|
||||||
|
{2023, 9, 23.0, 103.8198, 1.3521, 8.0, 0, 0, 2460211.061198, 2460210.530116},
|
||||||
|
{2023, 9, 23.0, 103.8198, 1.3521, 8.0, 0, 100, 2460211.060143, 2460210.531151},
|
||||||
|
{2023, 9, 23.0, 103.8198, 1.3521, 8.0, 0, 1000, 2460211.057847, 2460210.533414},
|
||||||
|
{2023, 9, 23.0, 103.8198, 1.3521, 8.0, 1, 0, 2460211.058440, 2460210.532834},
|
||||||
|
{2023, 9, 23.0, 103.8198, 1.3521, 8.0, 1, 100, 2460211.057385, 2460210.533870},
|
||||||
|
{2023, 9, 23.0, 103.8198, 1.3521, 8.0, 1, 1000, 2460211.055090, 2460210.536133},
|
||||||
|
{2023, 12, 22.0, 103.8198, 1.3521, 8.0, 0, 0, 2460301.128082, 2460300.607764},
|
||||||
|
{2023, 12, 22.0, 103.8198, 1.3521, 8.0, 0, 100, 2460301.127137, 2460300.608684},
|
||||||
|
{2023, 12, 22.0, 103.8198, 1.3521, 8.0, 0, 1000, 2460301.125078, 2460300.610694},
|
||||||
|
{2023, 12, 22.0, 103.8198, 1.3521, 8.0, 1, 0, 2460301.125610, 2460300.610178},
|
||||||
|
{2023, 12, 22.0, 103.8198, 1.3521, 8.0, 1, 100, 2460301.124664, 2460300.611098},
|
||||||
|
{2023, 12, 22.0, 103.8198, 1.3521, 8.0, 1, 1000, 2460301.122605, 2460300.613109},
|
||||||
|
{2024, 2, 29.0, 103.8198, 1.3521, 8.0, 0, 0, 2460370.448355, 2460369.926847},
|
||||||
|
{2024, 2, 29.0, 103.8198, 1.3521, 8.0, 0, 100, 2460370.447403, 2460369.927771},
|
||||||
|
{2024, 2, 29.0, 103.8198, 1.3521, 8.0, 0, 1000, 2460370.445333, 2460369.929790},
|
||||||
|
{2024, 2, 29.0, 103.8198, 1.3521, 8.0, 1, 0, 2460370.445868, 2460369.929271},
|
||||||
|
{2024, 2, 29.0, 103.8198, 1.3521, 8.0, 1, 100, 2460370.444917, 2460369.930196},
|
||||||
|
{2024, 2, 29.0, 103.8198, 1.3521, 8.0, 1, 1000, 2460370.442847, 2460369.932215},
|
||||||
|
{2025, 7, 4.0, 103.8198, 1.3521, 8.0, 0, 0, 2460861.083263, 2460860.562492},
|
||||||
|
{2025, 7, 4.0, 103.8198, 1.3521, 8.0, 0, 100, 2460861.082319, 2460860.563411},
|
||||||
|
{2025, 7, 4.0, 103.8198, 1.3521, 8.0, 0, 1000, 2460861.080264, 2460860.565417},
|
||||||
|
{2025, 7, 4.0, 103.8198, 1.3521, 8.0, 1, 0, 2460861.080795, 2460860.564902},
|
||||||
|
{2025, 7, 4.0, 103.8198, 1.3521, 8.0, 1, 100, 2460861.079851, 2460860.565821},
|
||||||
|
{2025, 7, 4.0, 103.8198, 1.3521, 8.0, 1, 1000, 2460861.077796, 2460860.567828},
|
||||||
|
{2023, 1, 15.0, -149.9003, 61.2181, -9.0, 0, 0, 2459959.611424, 2459959.989333},
|
||||||
|
{2023, 1, 15.0, -149.9003, 61.2181, -9.0, 0, 100, 2459959.609258, 2459959.991433},
|
||||||
|
{2023, 1, 15.0, -149.9003, 61.2181, -9.0, 0, 1000, 2459959.604610, 2459959.995941},
|
||||||
|
{2023, 1, 15.0, -149.9003, 61.2181, -9.0, 1, 0, 2459959.605825, 2459959.994766},
|
||||||
|
{2023, 1, 15.0, -149.9003, 61.2181, -9.0, 1, 100, 2459959.603687, 2459959.996835},
|
||||||
|
{2023, 1, 15.0, -149.9003, 61.2181, -9.0, 1, 1000, 2459959.599095, 2459960.001283},
|
||||||
|
{2023, 3, 20.0, -149.9003, 61.2181, -9.0, 0, 0, 2460023.818593, 2460024.240506},
|
||||||
|
{2023, 3, 20.0, -149.9003, 61.2181, -9.0, 0, 100, 2460023.816608, 2460024.242553},
|
||||||
|
{2023, 3, 20.0, -149.9003, 61.2181, -9.0, 0, 1000, 2460023.812331, 2460024.246975},
|
||||||
|
{2023, 3, 20.0, -149.9003, 61.2181, -9.0, 1, 0, 2460023.813448, 2460024.245824},
|
||||||
|
{2023, 3, 20.0, -149.9003, 61.2181, -9.0, 1, 100, 2460023.811481, 2460024.247856},
|
||||||
|
{2023, 3, 20.0, -149.9003, 61.2181, -9.0, 1, 1000, 2460023.807242, 2460024.252248},
|
||||||
|
{2023, 6, 21.0, -149.9003, 61.2181, -9.0, 0, 0, 2460116.779694, 2460116.541691},
|
||||||
|
{2023, 6, 21.0, -149.9003, 61.2181, -9.0, 0, 100, 2460116.776499, 2460116.544856},
|
||||||
|
{2023, 6, 21.0, -149.9003, 61.2181, -9.0, 0, 1000, 2460116.769322, 2460116.551979},
|
||||||
|
{2023, 6, 21.0, -149.9003, 61.2181, -9.0, 1, 0, 2460116.771242, 2460116.550076},
|
||||||
|
{2023, 6, 21.0, -149.9003, 61.2181, -9.0, 1, 100, 2460116.767840, 2460116.553450},
|
||||||
|
{2023, 6, 21.0, -149.9003, 61.2181, -9.0, 1, 1000, 2460116.760140, 2460116.561096},
|
||||||
|
{2023, 9, 23.0, -149.9003, 61.2181, -9.0, 0, 0, 2460211.309271, 2460211.381831},
|
||||||
|
{2023, 9, 23.0, -149.9003, 61.2181, -9.0, 0, 100, 2460211.300322, 2460211.390769},
|
||||||
|
{2023, 9, 23.0, -149.9003, 61.2181, -9.0, 0, 1000, 2460211.285237, 2460211.405841},
|
||||||
|
{2023, 9, 23.0, -149.9003, 61.2181, -9.0, 1, 0, 2460211.288811, 2460211.402275},
|
||||||
|
{2023, 9, 23.0, -149.9003, 61.2181, -9.0, 1, 100, 2460211.282640, 2460211.408435},
|
||||||
|
{2023, 9, 23.0, -149.9003, 61.2181, -9.0, 1, 1000, 2460211.270923, 2460211.420140},
|
||||||
|
{2023, 12, 22.0, -149.9003, 61.2181, -9.0, 0, 0, 2460301.058819, 2460300.705421},
|
||||||
|
{2023, 12, 22.0, -149.9003, 61.2181, -9.0, 0, 100, 2460301.056561, 2460300.707705},
|
||||||
|
{2023, 12, 22.0, -149.9003, 61.2181, -9.0, 0, 1000, 2460301.051607, 2460300.712725},
|
||||||
|
{2023, 12, 22.0, -149.9003, 61.2181, -9.0, 1, 0, 2460301.052913, 2460300.711406},
|
||||||
|
{2023, 12, 22.0, -149.9003, 61.2181, -9.0, 1, 100, 2460301.050606, 2460300.713738},
|
||||||
|
{2023, 12, 22.0, -149.9003, 61.2181, -9.0, 1, 1000, 2460301.045537, 2460300.718869},
|
||||||
|
{2024, 2, 29.0, -149.9003, 61.2181, -9.0, 0, 0, 2460369.515897, 2460369.852889},
|
||||||
|
{2024, 2, 29.0, -149.9003, 61.2181, -9.0, 0, 100, 2460369.513573, 2460369.855159},
|
||||||
|
{2024, 2, 29.0, -149.9003, 61.2181, -9.0, 0, 1000, 2460369.508609, 2460369.860014},
|
||||||
|
{2024, 2, 29.0, -149.9003, 61.2181, -9.0, 1, 0, 2460369.509904, 2460369.858750},
|
||||||
|
{2024, 2, 29.0, -149.9003, 61.2181, -9.0, 1, 100, 2460369.507626, 2460369.860974},
|
||||||
|
{2024, 2, 29.0, -149.9003, 61.2181, -9.0, 1, 1000, 2460369.502753, 2460369.865734},
|
||||||
|
{2025, 7, 4.0, -149.9003, 61.2181, -9.0, 0, 0, 2460861.205484, 2460861.493716},
|
||||||
|
{2025, 7, 4.0, -149.9003, 61.2181, -9.0, 0, 100, 2460861.202866, 2460861.496292},
|
||||||
|
{2025, 7, 4.0, -149.9003, 61.2181, -9.0, 0, 1000, 2460861.197312, 2460860.503722},
|
||||||
|
{2025, 7, 4.0, -149.9003, 61.2181, -9.0, 1, 0, 2460861.198757, 2460860.502500},
|
||||||
|
{2025, 7, 4.0, -149.9003, 61.2181, -9.0, 1, 100, 2460861.196218, 2460860.504648},
|
||||||
|
{2025, 7, 4.0, -149.9003, 61.2181, -9.0, 1, 1000, 2460861.190823, 2460860.509247},
|
||||||
|
{2023, 1, 15.0, -42.6043, 71.7069, -3.0, 0, 0, 2459959.583014, 2459959.895953},
|
||||||
|
{2023, 1, 15.0, -42.6043, 71.7069, -3.0, 0, 100, 2459959.579313, 2459959.899514},
|
||||||
|
{2023, 1, 15.0, -42.6043, 71.7069, -3.0, 0, 1000, 2459959.571484, 2459959.907035},
|
||||||
|
{2023, 1, 15.0, -42.6043, 71.7069, -3.0, 1, 0, 2459959.573523, 2459959.905081},
|
||||||
|
{2023, 1, 15.0, -42.6043, 71.7069, -3.0, 1, 100, 2459959.569948, 2459959.908508},
|
||||||
|
{2023, 1, 15.0, -42.6043, 71.7069, -3.0, 1, 1000, 2459959.562362, 2459959.915773},
|
||||||
|
{2023, 3, 20.0, -42.6043, 71.7069, -3.0, 0, 0, 2460023.817806, 2460024.130940},
|
||||||
|
{2023, 3, 20.0, -42.6043, 71.7069, -3.0, 0, 100, 2460023.814218, 2460024.134658},
|
||||||
|
{2023, 3, 20.0, -42.6043, 71.7069, -3.0, 0, 1000, 2460023.806634, 2460024.142543},
|
||||||
|
{2023, 3, 20.0, -42.6043, 71.7069, -3.0, 1, 0, 2460023.808601, 2460024.140499},
|
||||||
|
{2023, 3, 20.0, -42.6043, 71.7069, -3.0, 1, 100, 2460023.805148, 2460024.144090},
|
||||||
|
{2023, 3, 20.0, -42.6043, 71.7069, -3.0, 1, 1000, 2460023.797827, 2460024.151730},
|
||||||
|
{2023, 6, 21.0, -42.6043, 71.7069, -3.0, 0, 0, -1.000000, -1.000000},
|
||||||
|
{2023, 6, 21.0, -42.6043, 71.7069, -3.0, 0, 100, -1.000000, -1.000000},
|
||||||
|
{2023, 6, 21.0, -42.6043, 71.7069, -3.0, 0, 1000, -1.000000, -1.000000},
|
||||||
|
{2023, 6, 21.0, -42.6043, 71.7069, -3.0, 1, 0, -1.000000, -1.000000},
|
||||||
|
{2023, 6, 21.0, -42.6043, 71.7069, -3.0, 1, 100, -1.000000, -1.000000},
|
||||||
|
{2023, 6, 21.0, -42.6043, 71.7069, -3.0, 1, 1000, -1.000000, -1.000000},
|
||||||
|
{2023, 9, 23.0, -42.6043, 71.7069, -3.0, 0, 0, -2.000000, -2.000000},
|
||||||
|
{2023, 9, 23.0, -42.6043, 71.7069, -3.0, 0, 100, -2.000000, -2.000000},
|
||||||
|
{2023, 9, 23.0, -42.6043, 71.7069, -3.0, 0, 1000, -2.000000, -2.000000},
|
||||||
|
{2023, 9, 23.0, -42.6043, 71.7069, -3.0, 1, 0, -2.000000, -2.000000},
|
||||||
|
{2023, 9, 23.0, -42.6043, 71.7069, -3.0, 1, 100, -2.000000, -2.000000},
|
||||||
|
{2023, 9, 23.0, -42.6043, 71.7069, -3.0, 1, 1000, -2.000000, -2.000000},
|
||||||
|
{2023, 12, 22.0, -42.6043, 71.7069, -3.0, 0, 0, 2460300.951391, 2460300.689493},
|
||||||
|
{2023, 12, 22.0, -42.6043, 71.7069, -3.0, 0, 100, 2460300.947139, 2460300.693837},
|
||||||
|
{2023, 12, 22.0, -42.6043, 71.7069, -3.0, 0, 1000, 2460300.937499, 2460300.703677},
|
||||||
|
{2023, 12, 22.0, -42.6043, 71.7069, -3.0, 1, 0, 2460300.940091, 2460300.701037},
|
||||||
|
{2023, 12, 22.0, -42.6043, 71.7069, -3.0, 1, 100, 2460300.935496, 2460300.705720},
|
||||||
|
{2023, 12, 22.0, -42.6043, 71.7069, -3.0, 1, 1000, 2460300.924958, 2460300.716448},
|
||||||
|
{2024, 2, 29.0, -42.6043, 71.7069, -3.0, 0, 0, 2460369.510257, 2460369.739050},
|
||||||
|
{2024, 2, 29.0, -42.6043, 71.7069, -3.0, 0, 100, 2460369.505593, 2460369.743606},
|
||||||
|
{2024, 2, 29.0, -42.6043, 71.7069, -3.0, 0, 1000, -3.000000, 2460369.752996},
|
||||||
|
{2024, 2, 29.0, -42.6043, 71.7069, -3.0, 1, 0, -2.000000, 2460369.750583},
|
||||||
|
{2024, 2, 29.0, -42.6043, 71.7069, -3.0, 1, 100, -3.000000, 2460369.754803},
|
||||||
|
{2024, 2, 29.0, -42.6043, 71.7069, -3.0, 1, 1000, -3.000000, 2460369.763575},
|
||||||
|
{2025, 7, 4.0, -42.6043, 71.7069, -3.0, 0, 0, 2460861.253176, 2460861.327087},
|
||||||
|
{2025, 7, 4.0, -42.6043, 71.7069, -3.0, 0, 100, 2460861.241274, 2460861.338907},
|
||||||
|
{2025, 7, 4.0, -42.6043, 71.7069, -3.0, 0, 1000, 2460861.222145, 2460861.357865},
|
||||||
|
{2025, 7, 4.0, -42.6043, 71.7069, -3.0, 1, 0, 2460861.226647, 2460861.353412},
|
||||||
|
{2025, 7, 4.0, -42.6043, 71.7069, -3.0, 1, 100, 2460861.218910, 2460861.361065},
|
||||||
|
{2025, 7, 4.0, -42.6043, 71.7069, -3.0, 1, 1000, 2460861.204387, 2460861.375411},
|
||||||
|
{2023, 1, 15.0, 0.0000, -85.0000, 0.0, 0, 0, -1.000000, -1.000000},
|
||||||
|
{2023, 1, 15.0, 0.0000, -85.0000, 0.0, 0, 100, -1.000000, -1.000000},
|
||||||
|
{2023, 1, 15.0, 0.0000, -85.0000, 0.0, 0, 1000, -1.000000, -1.000000},
|
||||||
|
{2023, 1, 15.0, 0.0000, -85.0000, 0.0, 1, 0, -1.000000, -1.000000},
|
||||||
|
{2023, 1, 15.0, 0.0000, -85.0000, 0.0, 1, 100, -1.000000, -1.000000},
|
||||||
|
{2023, 1, 15.0, 0.0000, -85.0000, 0.0, 1, 1000, -1.000000, -1.000000},
|
||||||
|
{2023, 3, 20.0, 0.0000, -85.0000, 0.0, 0, 0, -1.000000, -1.000000},
|
||||||
|
{2023, 3, 20.0, 0.0000, -85.0000, 0.0, 0, 100, -1.000000, -1.000000},
|
||||||
|
{2023, 3, 20.0, 0.0000, -85.0000, 0.0, 0, 1000, -1.000000, -1.000000},
|
||||||
|
{2023, 3, 20.0, 0.0000, -85.0000, 0.0, 1, 0, -1.000000, -1.000000},
|
||||||
|
{2023, 3, 20.0, 0.0000, -85.0000, 0.0, 1, 100, -1.000000, -1.000000},
|
||||||
|
{2023, 3, 20.0, 0.0000, -85.0000, 0.0, 1, 1000, -1.000000, -1.000000},
|
||||||
|
{2023, 6, 21.0, 0.0000, -85.0000, 0.0, 0, 0, -2.000000, -2.000000},
|
||||||
|
{2023, 6, 21.0, 0.0000, -85.0000, 0.0, 0, 100, -2.000000, -2.000000},
|
||||||
|
{2023, 6, 21.0, 0.0000, -85.0000, 0.0, 0, 1000, -2.000000, -2.000000},
|
||||||
|
{2023, 6, 21.0, 0.0000, -85.0000, 0.0, 1, 0, -2.000000, -2.000000},
|
||||||
|
{2023, 6, 21.0, 0.0000, -85.0000, 0.0, 1, 100, -2.000000, -2.000000},
|
||||||
|
{2023, 6, 21.0, 0.0000, -85.0000, 0.0, 1, 1000, -2.000000, -2.000000},
|
||||||
|
{2023, 9, 23.0, 0.0000, -85.0000, 0.0, 0, 0, -1.000000, -1.000000},
|
||||||
|
{2023, 9, 23.0, 0.0000, -85.0000, 0.0, 0, 100, -1.000000, -1.000000},
|
||||||
|
{2023, 9, 23.0, 0.0000, -85.0000, 0.0, 0, 1000, -1.000000, -1.000000},
|
||||||
|
{2023, 9, 23.0, 0.0000, -85.0000, 0.0, 1, 0, -1.000000, -1.000000},
|
||||||
|
{2023, 9, 23.0, 0.0000, -85.0000, 0.0, 1, 100, -1.000000, -1.000000},
|
||||||
|
{2023, 9, 23.0, 0.0000, -85.0000, 0.0, 1, 1000, -1.000000, -1.000000},
|
||||||
|
{2023, 12, 22.0, 0.0000, -85.0000, 0.0, 0, 0, -2.000000, -2.000000},
|
||||||
|
{2023, 12, 22.0, 0.0000, -85.0000, 0.0, 0, 100, -2.000000, -2.000000},
|
||||||
|
{2023, 12, 22.0, 0.0000, -85.0000, 0.0, 0, 1000, -2.000000, -2.000000},
|
||||||
|
{2023, 12, 22.0, 0.0000, -85.0000, 0.0, 1, 0, -2.000000, -2.000000},
|
||||||
|
{2023, 12, 22.0, 0.0000, -85.0000, 0.0, 1, 100, -2.000000, -2.000000},
|
||||||
|
{2023, 12, 22.0, 0.0000, -85.0000, 0.0, 1, 1000, -2.000000, -2.000000},
|
||||||
|
{2024, 2, 29.0, 0.0000, -85.0000, 0.0, 0, 0, -1.000000, -1.000000},
|
||||||
|
{2024, 2, 29.0, 0.0000, -85.0000, 0.0, 0, 100, -1.000000, -1.000000},
|
||||||
|
{2024, 2, 29.0, 0.0000, -85.0000, 0.0, 0, 1000, -1.000000, -1.000000},
|
||||||
|
{2024, 2, 29.0, 0.0000, -85.0000, 0.0, 1, 0, -1.000000, -1.000000},
|
||||||
|
{2024, 2, 29.0, 0.0000, -85.0000, 0.0, 1, 100, -1.000000, -1.000000},
|
||||||
|
{2024, 2, 29.0, 0.0000, -85.0000, 0.0, 1, 1000, -1.000000, -1.000000},
|
||||||
|
{2025, 7, 4.0, 0.0000, -85.0000, 0.0, 0, 0, -1.000000, -1.000000},
|
||||||
|
{2025, 7, 4.0, 0.0000, -85.0000, 0.0, 0, 100, -1.000000, -1.000000},
|
||||||
|
{2025, 7, 4.0, 0.0000, -85.0000, 0.0, 0, 1000, -1.000000, -1.000000},
|
||||||
|
{2025, 7, 4.0, 0.0000, -85.0000, 0.0, 1, 0, -1.000000, -1.000000},
|
||||||
|
{2025, 7, 4.0, 0.0000, -85.0000, 0.0, 1, 100, -1.000000, -1.000000},
|
||||||
|
{2025, 7, 4.0, 0.0000, -85.0000, 0.0, 1, 1000, -1.000000, -1.000000},
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestMoonRiseSetRegression 月出月落回归测试
|
||||||
|
func TestMoonRiseSetRegression(t *testing.T) {
|
||||||
|
beforeDeltaT := defDeltaTFn
|
||||||
|
SetDeltaTFn(DefaultDeltaTv2)
|
||||||
|
defer SetDeltaTFn(beforeDeltaT)
|
||||||
|
const tolerance = 0.00011574074
|
||||||
|
|
||||||
|
for i, testCase := range moonRiseSetTestData {
|
||||||
|
julianDay := JDECalc(testCase.Year, testCase.Month, testCase.Day)
|
||||||
|
|
||||||
|
// 测试月出时间
|
||||||
|
|
||||||
|
actualRise, riseErr := GetMoonRiseTime(julianDay, testCase.Longitude, testCase.Latitude,
|
||||||
|
testCase.TimeZone, testCase.ZenithShift, testCase.Height)
|
||||||
|
|
||||||
|
if !moonRiseSetMatches(actualRise, riseErr, testCase.ExpectedRise, tolerance) {
|
||||||
|
t.Errorf("测试用例 %d 月出时间不匹配:\n"+
|
||||||
|
" 日期: %d-%d-%.1f, 经纬度: (%.4f, %.4f), 时区: %.1f, 天顶修正: %.0f, 海拔: %.0f\n"+
|
||||||
|
" 期望月出: %s, 实际月出: %.6f, 实际错误: %v, 差值: %.9f",
|
||||||
|
i, testCase.Year, testCase.Month, testCase.Day,
|
||||||
|
testCase.Longitude, testCase.Latitude, testCase.TimeZone,
|
||||||
|
testCase.ZenithShift, testCase.Height,
|
||||||
|
moonRiseSetExpectation(testCase.ExpectedRise), actualRise, riseErr, math.Abs(actualRise-testCase.ExpectedRise))
|
||||||
|
}
|
||||||
|
|
||||||
|
// 测试月落时间
|
||||||
|
actualSet, setErr := GetMoonSetTime(julianDay, testCase.Longitude, testCase.Latitude,
|
||||||
|
testCase.TimeZone, testCase.ZenithShift, testCase.Height)
|
||||||
|
|
||||||
|
if !moonRiseSetMatches(actualSet, setErr, testCase.ExpectedSet, tolerance) {
|
||||||
|
t.Errorf("测试用例 %d 月落时间不匹配:\n"+
|
||||||
|
" 日期: %d-%d-%.1f, 经纬度: (%.4f, %.4f), 时区: %.1f, 天顶修正: %.0f, 海拔: %.0f\n"+
|
||||||
|
" 期望月落: %s, 实际月落: %.6f, 实际错误: %v, 差值: %.9f",
|
||||||
|
i, testCase.Year, testCase.Month, testCase.Day,
|
||||||
|
testCase.Longitude, testCase.Latitude, testCase.TimeZone,
|
||||||
|
testCase.ZenithShift, testCase.Height,
|
||||||
|
moonRiseSetExpectation(testCase.ExpectedSet), actualSet, setErr, math.Abs(actualSet-testCase.ExpectedSet))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Logf("月出月落回归测试通过,共测试 %d 个用例", len(moonRiseSetTestData))
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestMoonRiseSetSpecialCases 测试特殊情况
|
||||||
|
func TestMoonRiseSetSpecialCases(t *testing.T) {
|
||||||
|
beforeDeltaT := defDeltaTFn
|
||||||
|
SetDeltaTFn(DefaultDeltaTv2)
|
||||||
|
defer SetDeltaTFn(beforeDeltaT)
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
year int
|
||||||
|
month int
|
||||||
|
day float64
|
||||||
|
longitude float64
|
||||||
|
latitude float64
|
||||||
|
timeZone float64
|
||||||
|
zenithShift float64
|
||||||
|
height float64
|
||||||
|
expectedRiseCode float64 // -1=拱, -2=沉, -3=明日
|
||||||
|
expectedSetCode float64
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "极地地区极昼测试",
|
||||||
|
year: 2023, month: 6, day: 21,
|
||||||
|
longitude: 0, latitude: 85,
|
||||||
|
timeZone: 0, zenithShift: 1, height: 0,
|
||||||
|
expectedRiseCode: -1, // 可能出现拱的情况
|
||||||
|
expectedSetCode: -1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "极地地区极夜测试",
|
||||||
|
year: 2023, month: 12, day: 22,
|
||||||
|
longitude: 0, latitude: -85,
|
||||||
|
timeZone: 0, zenithShift: 1, height: 0,
|
||||||
|
expectedRiseCode: -2, // 可能出现沉的情况
|
||||||
|
expectedSetCode: -2,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
julianDay := JDECalc(tc.year, tc.month, tc.day)
|
||||||
|
|
||||||
|
actualRise, riseErr := GetMoonRiseTime(julianDay, tc.longitude, tc.latitude,
|
||||||
|
tc.timeZone, tc.zenithShift, tc.height)
|
||||||
|
actualSet, setErr := GetMoonSetTime(julianDay, tc.longitude, tc.latitude,
|
||||||
|
tc.timeZone, tc.zenithShift, tc.height)
|
||||||
|
|
||||||
|
t.Logf("特殊情况测试结果: 月出=%.6f err=%v, 月落=%.6f err=%v", actualRise, riseErr, actualSet, setErr)
|
||||||
|
|
||||||
|
if !errors.Is(riseErr, moonRiseSetExpectedError(tc.expectedRiseCode)) {
|
||||||
|
t.Errorf("月出特殊情况错误不匹配: got %v want %v", riseErr, moonRiseSetExpectedError(tc.expectedRiseCode))
|
||||||
|
}
|
||||||
|
if !errors.Is(setErr, moonRiseSetExpectedError(tc.expectedSetCode)) {
|
||||||
|
t.Errorf("月落特殊情况错误不匹配: got %v want %v", setErr, moonRiseSetExpectedError(tc.expectedSetCode))
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func Test_MoonRise(t *testing.T) {
|
func moonRiseSetExpectedError(code float64) error {
|
||||||
//2.459984692085961e+06 113.58880556 87.36833333 8 0 0
|
switch code {
|
||||||
//2.459984692085961e+06 113.58880556 87.36833333 8 0 0
|
case -1:
|
||||||
//2.4599846948519214e+06 113.58880556 87.36833333 8 0 0
|
return ErrNeverSet
|
||||||
|
case -2:
|
||||||
//cst := time.FixedZone("cst", 8*3600)
|
return ErrNeverRise
|
||||||
//jde := Date2JDE(time.Date(2023, 2, 9, 15, 59, 0, 0, cst))
|
case -3:
|
||||||
fmt.Println(GetMoonRiseTime(2.4599846948519214e+06, 113.58880556, 87.36833333, 8, 0, 0))
|
return ErrNotOnThisDate
|
||||||
for i := 30.0; i < 90.0; i += 0.3 {
|
default:
|
||||||
fmt.Println(i, GetMoonRiseTime(2.459984692085961e+06, 117.76653567, float64(i), 8, 0, 0))
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func Test_MoonS(t *testing.T) {
|
func moonRiseSetExpectation(code float64) string {
|
||||||
//fmt.Println(Sita(2451547))
|
if err := moonRiseSetExpectedError(code); err != nil {
|
||||||
//fmt.Println(MoonHeight(2451547, 115, 32, 8))
|
return err.Error()
|
||||||
a := time.Now().UnixNano()
|
}
|
||||||
b := GetMoonRiseTime(GetNowJDE(), 123, 40, 8, 0, 10)
|
return JDE2Date(code).String()
|
||||||
fmt.Println(HMoonHeight(b, 115, 32, 8))
|
|
||||||
fmt.Println(time.Now().UnixNano() - a)
|
|
||||||
fmt.Println(JDE2Date((b)))
|
|
||||||
fmt.Println(time.Now().UnixNano() - a)
|
|
||||||
//fmt.Printf("%.14f", GetMoonRiseTime(2451547, 115, 32, 8, 0))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestMoonCu(t *testing.T) {
|
func moonRiseSetMatches(actual float64, err error, expected float64, tolerance float64) bool {
|
||||||
jde := math.Floor(GetNowJDE() - 20.0/24.0)
|
expectedErr := moonRiseSetExpectedError(expected)
|
||||||
n := MoonCulminationTime(jde, 115, 23, 8)
|
if expectedErr != nil {
|
||||||
fmt.Println(JDE2Date(n))
|
return errors.Is(err, expectedErr)
|
||||||
fmt.Println(MoonTimeAngle(n, 115, 23, 8))
|
}
|
||||||
fmt.Println(MoonAngle(n, 115, 23, 8))
|
if err != nil {
|
||||||
//fmt.Println(JDE2Date(jde))
|
return false
|
||||||
//ra, dec := HMoonApparentRaDec(jde, 115, 23, 8)
|
}
|
||||||
//fmt.Println(tools.Format(ra/15, 1), tools.Format(dec, 0))
|
return floatEquals(actual, expected, tolerance)
|
||||||
//fmt.Println(JDE2Date(GetMoonTZTime(jde, 115, 23, 8)))
|
}
|
||||||
//fmt.Println(JDE2Date(GetMoonDownTime(jde+1, 115, 23, 8, 1, 0)))
|
|
||||||
|
// floatEquals 比较两个浮点数是否在给定容差内相等
|
||||||
|
func floatEquals(a, b, tolerance float64) bool {
|
||||||
|
return math.Abs(a-b) <= tolerance
|
||||||
|
}
|
||||||
|
|
||||||
|
// BenchmarkMoonRiseTime 月出时间计算性能测试
|
||||||
|
func BenchmarkMoonRiseTime(b *testing.B) {
|
||||||
|
julianDay := JDECalc(2023, 6, 21)
|
||||||
|
longitude, latitude := 116.4074, 39.9042
|
||||||
|
timeZone, zenithShift, height := 8.0, 1.0, 0.0
|
||||||
|
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
_, _ = GetMoonRiseTime(julianDay, longitude, latitude, timeZone, zenithShift, height)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// BenchmarkMoonSetTime 月落时间计算性能测试
|
||||||
|
func BenchmarkMoonSetTime(b *testing.B) {
|
||||||
|
julianDay := JDECalc(2023, 6, 21)
|
||||||
|
longitude, latitude := 116.4074, 39.9042
|
||||||
|
timeZone, zenithShift, height := 8.0, 1.0, 0.0
|
||||||
|
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
_, _ = GetMoonSetTime(julianDay, longitude, latitude, timeZone, zenithShift, height)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,80 @@
|
|||||||
|
package basic
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
. "b612.me/astro/tools"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestMoonTopocentricPhysicalMatchesCorrectionMethod(t *testing.T) {
|
||||||
|
jd := TD2UT(Date2JDE(testTime(2026, 4, 28, 9, 30, 45)), true)
|
||||||
|
observerLon := 121.4737
|
||||||
|
observerLat := 31.2304
|
||||||
|
|
||||||
|
got := MoonTopocentricPhysical(jd, observerLon, observerLat, 0)
|
||||||
|
want := moonTopocentricPhysicalByCorrection(jd, observerLon, observerLat)
|
||||||
|
|
||||||
|
assertPlanetPhaseClose(t, "MoonTopocentricPhysical.LibrationLongitude", got.LibrationLongitude, want.LibrationLongitude, 0.1)
|
||||||
|
assertPlanetPhaseClose(t, "MoonTopocentricPhysical.LibrationLatitude", got.LibrationLatitude, want.LibrationLatitude, 0.1)
|
||||||
|
assertPlanetPhaseClose(t, "MoonTopocentricPhysical.PositionAngle", got.PositionAngle, want.PositionAngle, 0.1)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMoonTopocentricPhysicalSampleSweepFiniteAndInRange(t *testing.T) {
|
||||||
|
samples := []struct {
|
||||||
|
name string
|
||||||
|
jd float64
|
||||||
|
observerLon float64
|
||||||
|
observerLat float64
|
||||||
|
height float64
|
||||||
|
}{
|
||||||
|
{"shanghai", TD2UT(Date2JDE(testTime(2026, 4, 28, 9, 30, 45)), true), 121.4737, 31.2304, 4},
|
||||||
|
{"chicago", TD2UT(Date2JDE(testTime(2024, 3, 25, 7, 0, 0)), true), -87.65, 41.85, 180},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, sample := range samples {
|
||||||
|
info := MoonTopocentricPhysical(sample.jd, sample.observerLon, sample.observerLat, sample.height)
|
||||||
|
prefix := sample.name + "."
|
||||||
|
|
||||||
|
assertFiniteRange(t, prefix+"OpticalLongitude", info.OpticalLongitude, -180, 180, false)
|
||||||
|
assertFiniteRange(t, prefix+"OpticalLatitude", info.OpticalLatitude, -90, 90, false)
|
||||||
|
assertFiniteRange(t, prefix+"PhysicalLongitude", info.PhysicalLongitude, -180, 180, false)
|
||||||
|
assertFiniteRange(t, prefix+"PhysicalLatitude", info.PhysicalLatitude, -90, 90, false)
|
||||||
|
assertFiniteRange(t, prefix+"LibrationLongitude", info.LibrationLongitude, -180, 180, false)
|
||||||
|
assertFiniteRange(t, prefix+"LibrationLatitude", info.LibrationLatitude, -90, 90, false)
|
||||||
|
assertFiniteRange(t, prefix+"PositionAngle", info.PositionAngle, -90, 90, false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func moonTopocentricPhysicalByCorrection(jd, observerLon, observerLat float64) MoonPhysicalInfo {
|
||||||
|
geocentric := MoonPhysical(jd)
|
||||||
|
moonRA := HMoonTrueRa(jd)
|
||||||
|
moonDec := HMoonTrueDec(jd)
|
||||||
|
hourAngle := StarHourAngle(TD2UT(jd, false), moonRA, observerLon, 0)
|
||||||
|
horizontalParallax := ArcSin(6378.1366 / HMoonAway(jd))
|
||||||
|
|
||||||
|
Q := ArcTan2(
|
||||||
|
Cos(moonDec)*Sin(hourAngle),
|
||||||
|
Cos(moonDec)*Sin(observerLat)-Sin(moonDec)*Cos(observerLat)*Cos(hourAngle),
|
||||||
|
)
|
||||||
|
z := ArcCos(Sin(moonDec)*Sin(observerLat) + Cos(moonDec)*Cos(observerLat)*Cos(hourAngle))
|
||||||
|
piPrime := horizontalParallax * (Sin(z) + 0.0084*Sin(2*z))
|
||||||
|
|
||||||
|
deltaL := -piPrime * Sin(Q-geocentric.PositionAngle) / Cos(geocentric.LibrationLatitude)
|
||||||
|
deltaB := piPrime * Cos(Q-geocentric.PositionAngle)
|
||||||
|
deltaP := deltaL*Sin(geocentric.LibrationLatitude+deltaB) - piPrime*Sin(Q)*Tan(moonDec)
|
||||||
|
|
||||||
|
return MoonPhysicalInfo{
|
||||||
|
OpticalLongitude: geocentric.OpticalLongitude,
|
||||||
|
OpticalLatitude: geocentric.OpticalLatitude,
|
||||||
|
PhysicalLongitude: geocentric.PhysicalLongitude,
|
||||||
|
PhysicalLatitude: geocentric.PhysicalLatitude,
|
||||||
|
LibrationLongitude: wrapSignedAngle180(geocentric.LibrationLongitude + deltaL),
|
||||||
|
LibrationLatitude: geocentric.LibrationLatitude + deltaB,
|
||||||
|
PositionAngle: geocentric.PositionAngle + deltaP,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func testTime(year int, month time.Month, day, hour, minute, second int) time.Time {
|
||||||
|
return time.Date(year, month, day, hour, minute, second, 0, time.UTC)
|
||||||
|
}
|
||||||
+102
-341
@@ -7,143 +7,112 @@ import (
|
|||||||
. "b612.me/astro/tools"
|
. "b612.me/astro/tools"
|
||||||
)
|
)
|
||||||
|
|
||||||
func NeptuneL(JD float64) float64 {
|
func NeptuneL(jd float64) float64 {
|
||||||
return planet.WherePlanet(7, 0, JD)
|
return planet.WherePlanet(7, 0, jd)
|
||||||
}
|
}
|
||||||
|
|
||||||
func NeptuneB(JD float64) float64 {
|
func NeptuneB(jd float64) float64 {
|
||||||
return planet.WherePlanet(7, 1, JD)
|
return planet.WherePlanet(7, 1, jd)
|
||||||
}
|
}
|
||||||
func NeptuneR(JD float64) float64 {
|
func NeptuneR(jd float64) float64 {
|
||||||
return planet.WherePlanet(7, 2, JD)
|
return planet.WherePlanet(7, 2, jd)
|
||||||
}
|
}
|
||||||
func ANeptuneX(JD float64) float64 {
|
func ANeptuneX(jd float64) float64 {
|
||||||
l := NeptuneL(JD)
|
l := NeptuneL(jd)
|
||||||
b := NeptuneB(JD)
|
b := NeptuneB(jd)
|
||||||
r := NeptuneR(JD)
|
r := NeptuneR(jd)
|
||||||
el := planet.WherePlanet(-1, 0, JD)
|
el := planet.WherePlanet(-1, 0, jd)
|
||||||
eb := planet.WherePlanet(-1, 1, JD)
|
eb := planet.WherePlanet(-1, 1, jd)
|
||||||
er := planet.WherePlanet(-1, 2, JD)
|
er := planet.WherePlanet(-1, 2, jd)
|
||||||
x := r*Cos(b)*Cos(l) - er*Cos(eb)*Cos(el)
|
x := r*Cos(b)*Cos(l) - er*Cos(eb)*Cos(el)
|
||||||
return x
|
return x
|
||||||
}
|
}
|
||||||
|
|
||||||
func ANeptuneY(JD float64) float64 {
|
func ANeptuneY(jd float64) float64 {
|
||||||
|
|
||||||
l := NeptuneL(JD)
|
l := NeptuneL(jd)
|
||||||
b := NeptuneB(JD)
|
b := NeptuneB(jd)
|
||||||
r := NeptuneR(JD)
|
r := NeptuneR(jd)
|
||||||
el := planet.WherePlanet(-1, 0, JD)
|
el := planet.WherePlanet(-1, 0, jd)
|
||||||
eb := planet.WherePlanet(-1, 1, JD)
|
eb := planet.WherePlanet(-1, 1, jd)
|
||||||
er := planet.WherePlanet(-1, 2, JD)
|
er := planet.WherePlanet(-1, 2, jd)
|
||||||
y := r*Cos(b)*Sin(l) - er*Cos(eb)*Sin(el)
|
y := r*Cos(b)*Sin(l) - er*Cos(eb)*Sin(el)
|
||||||
return y
|
return y
|
||||||
}
|
}
|
||||||
func ANeptuneZ(JD float64) float64 {
|
func ANeptuneZ(jd float64) float64 {
|
||||||
//l := NeptuneL(JD)
|
//l := NeptuneL(jd)
|
||||||
b := NeptuneB(JD)
|
b := NeptuneB(jd)
|
||||||
r := NeptuneR(JD)
|
r := NeptuneR(jd)
|
||||||
// el := planet.WherePlanet(-1, 0, JD)
|
// el := planet.WherePlanet(-1, 0, jd)
|
||||||
eb := planet.WherePlanet(-1, 1, JD)
|
eb := planet.WherePlanet(-1, 1, jd)
|
||||||
er := planet.WherePlanet(-1, 2, JD)
|
er := planet.WherePlanet(-1, 2, jd)
|
||||||
z := r*Sin(b) - er*Sin(eb)
|
z := r*Sin(b) - er*Sin(eb)
|
||||||
return z
|
return z
|
||||||
}
|
}
|
||||||
|
|
||||||
func ANeptuneXYZ(JD float64) (float64, float64, float64) {
|
func ANeptuneXYZ(jd float64) (float64, float64, float64) {
|
||||||
l := NeptuneL(JD)
|
l := NeptuneL(jd)
|
||||||
b := NeptuneB(JD)
|
b := NeptuneB(jd)
|
||||||
r := NeptuneR(JD)
|
r := NeptuneR(jd)
|
||||||
el := planet.WherePlanet(-1, 0, JD)
|
el := planet.WherePlanet(-1, 0, jd)
|
||||||
eb := planet.WherePlanet(-1, 1, JD)
|
eb := planet.WherePlanet(-1, 1, jd)
|
||||||
er := planet.WherePlanet(-1, 2, JD)
|
er := planet.WherePlanet(-1, 2, jd)
|
||||||
x := r*Cos(b)*Cos(l) - er*Cos(eb)*Cos(el)
|
x := r*Cos(b)*Cos(l) - er*Cos(eb)*Cos(el)
|
||||||
y := r*Cos(b)*Sin(l) - er*Cos(eb)*Sin(el)
|
y := r*Cos(b)*Sin(l) - er*Cos(eb)*Sin(el)
|
||||||
z := r*Sin(b) - er*Sin(eb)
|
z := r*Sin(b) - er*Sin(eb)
|
||||||
return x, y, z
|
return x, y, z
|
||||||
}
|
}
|
||||||
|
|
||||||
func NeptuneApparentRa(JD float64) float64 {
|
func NeptuneApparentRa(jd float64) float64 {
|
||||||
lo, bo := NeptuneApparentLoBo(JD)
|
lo, bo := NeptuneApparentLoBo(jd)
|
||||||
sita := Sita(JD)
|
eps := TrueObliquity(jd)
|
||||||
ra := math.Atan2((Sin(lo)*Cos(sita) - Tan(bo)*Sin(sita)), Cos(lo))
|
ra := math.Atan2((Sin(lo)*Cos(eps) - Tan(bo)*Sin(eps)), Cos(lo))
|
||||||
ra = ra * 180 / math.Pi
|
ra = ra * 180 / math.Pi
|
||||||
return Limit360(ra)
|
return Limit360(ra)
|
||||||
}
|
}
|
||||||
func NeptuneApparentDec(JD float64) float64 {
|
func NeptuneApparentDec(jd float64) float64 {
|
||||||
lo, bo := NeptuneApparentLoBo(JD)
|
lo, bo := NeptuneApparentLoBo(jd)
|
||||||
sita := Sita(JD)
|
eps := TrueObliquity(jd)
|
||||||
dec := ArcSin(Sin(bo)*Cos(sita) + Cos(bo)*Sin(sita)*Sin(lo))
|
dec := ArcSin(Sin(bo)*Cos(eps) + Cos(bo)*Sin(eps)*Sin(lo))
|
||||||
return dec
|
return dec
|
||||||
}
|
}
|
||||||
|
|
||||||
func NeptuneApparentRaDec(JD float64) (float64, float64) {
|
func NeptuneApparentRaDec(jd float64) (float64, float64) {
|
||||||
lo, bo := NeptuneApparentLoBo(JD)
|
lo, bo := NeptuneApparentLoBo(jd)
|
||||||
sita := Sita(JD)
|
eps := TrueObliquity(jd)
|
||||||
ra := math.Atan2((Sin(lo)*Cos(sita) - Tan(bo)*Sin(sita)), Cos(lo))
|
ra := math.Atan2((Sin(lo)*Cos(eps) - Tan(bo)*Sin(eps)), Cos(lo))
|
||||||
ra = ra * 180 / math.Pi
|
ra = ra * 180 / math.Pi
|
||||||
dec := ArcSin(Sin(bo)*Cos(sita) + Cos(bo)*Sin(sita)*Sin(lo))
|
dec := ArcSin(Sin(bo)*Cos(eps) + Cos(bo)*Sin(eps)*Sin(lo))
|
||||||
return Limit360(ra), dec
|
return Limit360(ra), dec
|
||||||
}
|
}
|
||||||
|
|
||||||
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 += HJZD(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+=HJZD(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 += HJZD(JD)
|
|
||||||
return lo, bo
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NeptuneMag(JD float64) float64 {
|
func NeptuneMag(jd float64) float64 {
|
||||||
AwaySun := NeptuneR(JD)
|
sunDistance := NeptuneR(jd)
|
||||||
AwayEarth := EarthNeptuneAway(JD)
|
earthDistance := EarthNeptuneAway(jd)
|
||||||
Away := planet.WherePlanet(-1, 2, JD)
|
earthSunDistance := planet.WherePlanet(-1, 2, jd)
|
||||||
i := (AwaySun*AwaySun + AwayEarth*AwayEarth - Away*Away) / (2 * AwaySun * AwayEarth)
|
i := (sunDistance*sunDistance + earthDistance*earthDistance - earthSunDistance*earthSunDistance) / (2 * sunDistance * earthDistance)
|
||||||
i = ArcCos(i)
|
i = ArcCos(i)
|
||||||
Mag := -6.87 + 5*math.Log10(AwaySun*AwayEarth)
|
mag := -6.87 + 5*math.Log10(sunDistance*earthDistance)
|
||||||
return FloatRound(Mag, 2)
|
return FloatRound(mag, 2)
|
||||||
}
|
}
|
||||||
|
|
||||||
func NeptuneHeight(jde, lon, lat, timezone float64) float64 {
|
func NeptuneHeight(jde, lon, lat, timezone float64) float64 {
|
||||||
@@ -153,10 +122,10 @@ func NeptuneHeight(jde, lon, lat, timezone float64) float64 {
|
|||||||
ra, dec := NeptuneApparentRaDec(TD2UT(utcJde, true))
|
ra, dec := NeptuneApparentRaDec(TD2UT(utcJde, true))
|
||||||
st := Limit360(ApparentSiderealTime(utcJde)*15 + lon)
|
st := Limit360(ApparentSiderealTime(utcJde)*15 + lon)
|
||||||
// 计算时角
|
// 计算时角
|
||||||
H := Limit360(st - ra)
|
hourAngle := Limit360(st - ra)
|
||||||
// 高度角、时角与天球座标三角转换公式
|
// 高度角、时角与天球座标三角转换公式
|
||||||
// sin(h)=sin(lat)*sin(dec)+cos(dec)*cos(lat)*cos(H)
|
// sin(h)=sin(lat)*sin(dec)+cos(dec)*cos(lat)*cos(hourAngle)
|
||||||
sinHeight := Sin(lat)*Sin(dec) + Cos(dec)*Cos(lat)*Cos(H)
|
sinHeight := Sin(lat)*Sin(dec) + Cos(dec)*Cos(lat)*Cos(hourAngle)
|
||||||
return ArcSin(sinHeight)
|
return ArcSin(sinHeight)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -167,271 +136,63 @@ func NeptuneAzimuth(jde, lon, lat, timezone float64) float64 {
|
|||||||
ra, dec := NeptuneApparentRaDec(TD2UT(utcJde, true))
|
ra, dec := NeptuneApparentRaDec(TD2UT(utcJde, true))
|
||||||
st := Limit360(ApparentSiderealTime(utcJde)*15 + lon)
|
st := Limit360(ApparentSiderealTime(utcJde)*15 + lon)
|
||||||
// 计算时角
|
// 计算时角
|
||||||
H := Limit360(st - ra)
|
hourAngle := Limit360(st - ra)
|
||||||
// 三角转换公式
|
// 三角转换公式
|
||||||
tanAzimuth := Sin(H) / (Cos(H)*Sin(lat) - Tan(dec)*Cos(lat))
|
tanAzimuth := Sin(hourAngle) / (Cos(hourAngle)*Sin(lat) - Tan(dec)*Cos(lat))
|
||||||
Azimuth := ArcTan(tanAzimuth)
|
azimuth := ArcTan(tanAzimuth)
|
||||||
if Azimuth < 0 {
|
if azimuth < 0 {
|
||||||
if H/15 < 12 {
|
if hourAngle/15 < 12 {
|
||||||
return Azimuth + 360
|
return azimuth + 360
|
||||||
}
|
}
|
||||||
return Azimuth + 180
|
return azimuth + 180
|
||||||
}
|
}
|
||||||
if H/15 < 12 {
|
if hourAngle/15 < 12 {
|
||||||
return Azimuth + 180
|
return azimuth + 180
|
||||||
}
|
}
|
||||||
return Azimuth
|
return azimuth
|
||||||
}
|
}
|
||||||
|
|
||||||
func NeptuneHourAngle(JD, Lon, TZ float64) float64 {
|
func NeptuneHourAngle(jd, lon, timezone float64) float64 {
|
||||||
startime := Limit360(ApparentSiderealTime(JD-TZ/24)*15 + Lon)
|
siderealLongitude := Limit360(ApparentSiderealTime(jd-timezone/24)*15 + lon)
|
||||||
timeangle := startime - NeptuneApparentRa(TD2UT(JD-TZ/24.0, true))
|
hourAngle := siderealLongitude - NeptuneApparentRa(TD2UT(jd-timezone/24.0, true))
|
||||||
if timeangle < 0 {
|
if hourAngle < 0 {
|
||||||
timeangle += 360
|
hourAngle += 360
|
||||||
}
|
}
|
||||||
return timeangle
|
return hourAngle
|
||||||
}
|
}
|
||||||
|
|
||||||
func NeptuneCulminationTime(jde, lon, timezone float64) float64 {
|
func NeptuneCulminationTime(jde, lon, timezone float64) float64 {
|
||||||
//jde 世界时,非力学时,当地时区 0时,无需转换力学时
|
//jde 世界时,非力学时,当地时区 0时,无需转换力学时
|
||||||
//ra,dec 瞬时天球座标,非J2000等时间天球坐标
|
//ra,dec 瞬时天球座标,非J2000等时间天球坐标
|
||||||
jde = math.Floor(jde) + 0.5
|
jde = math.Floor(jde) + 0.5
|
||||||
JD1 := jde + Limit360(360-NeptuneHourAngle(jde, lon, timezone))/15.0/24.0*0.99726851851851851851
|
estimateJD := jde + Limit360(360-NeptuneHourAngle(jde, lon, timezone))/15.0/24.0*0.99726851851851851851
|
||||||
limitHA := func(jde, lon, timezone float64) float64 {
|
normalizedHourAngle := func(jde, lon, timezone float64) float64 {
|
||||||
ha := NeptuneHourAngle(jde, lon, timezone)
|
currentHourAngle := NeptuneHourAngle(jde, lon, timezone)
|
||||||
if ha < 180 {
|
if currentHourAngle < 180 {
|
||||||
ha += 360
|
currentHourAngle += 360
|
||||||
}
|
}
|
||||||
return ha
|
return currentHourAngle
|
||||||
}
|
}
|
||||||
for {
|
for {
|
||||||
JD0 := JD1
|
prevJD := estimateJD
|
||||||
stDegree := limitHA(JD0, lon, timezone) - 360
|
hourAngleDelta := normalizedHourAngle(prevJD, lon, timezone) - 360
|
||||||
stDegreep := (limitHA(JD0+0.000005, lon, timezone) - limitHA(JD0-0.000005, lon, timezone)) / 0.00001
|
hourAngleSlope := (normalizedHourAngle(prevJD+0.000005, lon, timezone) - normalizedHourAngle(prevJD-0.000005, lon, timezone)) / 0.00001
|
||||||
JD1 = JD0 - stDegree/stDegreep
|
estimateJD = prevJD - hourAngleDelta/hourAngleSlope
|
||||||
if math.Abs(JD1-JD0) <= 0.00001 {
|
if math.Abs(estimateJD-prevJD) <= 0.00001 {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return JD1
|
return estimateJD
|
||||||
}
|
}
|
||||||
|
|
||||||
func NeptuneRiseTime(JD, Lon, Lat, TZ, ZS, HEI float64) float64 {
|
func NeptuneRiseTime(jd, lon, lat, timezone, aeroCorrection, observerHeight float64) (float64, error) {
|
||||||
return neptuneRiseDown(JD, Lon, Lat, TZ, ZS, HEI, true)
|
return neptuneRiseDown(jd, lon, lat, timezone, aeroCorrection, observerHeight, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
func NeptuneDownTime(JD, Lon, Lat, TZ, ZS, HEI float64) float64 {
|
func NeptuneSetTime(jd, lon, lat, timezone, aeroCorrection, observerHeight float64) (float64, error) {
|
||||||
return neptuneRiseDown(JD, Lon, Lat, TZ, ZS, HEI, false)
|
return neptuneRiseDown(jd, lon, lat, timezone, aeroCorrection, observerHeight, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
func neptuneRiseDown(JD, Lon, Lat, TZ, ZS, HEI float64, isRise bool) float64 {
|
func neptuneRiseDown(jd, lon, lat, timezone, aeroCorrection, observerHeight float64, isRise bool) (float64, error) {
|
||||||
var An float64
|
return planetRiseDown(jd, lon, lat, timezone, aeroCorrection, observerHeight, isRise, NeptuneCulminationTime, NeptuneHeight, NeptuneApparentDec)
|
||||||
JD = math.Floor(JD) + 0.5
|
|
||||||
ntz := math.Round(Lon / 15)
|
|
||||||
if ZS != 0 {
|
|
||||||
An = -0.8333
|
|
||||||
}
|
|
||||||
An = An - HeightDegreeByLat(HEI, Lat)
|
|
||||||
tztime := NeptuneCulminationTime(JD, Lon, ntz)
|
|
||||||
if NeptuneHeight(tztime, Lon, Lat, ntz) < An {
|
|
||||||
return -2 //极夜
|
|
||||||
}
|
|
||||||
if NeptuneHeight(tztime-0.5, Lon, Lat, ntz) > An {
|
|
||||||
return -1 //极昼
|
|
||||||
}
|
|
||||||
dec := HSunApparentDec(TD2UT(tztime-ntz/24, true))
|
|
||||||
//(sin(ho)-sin(φ)*sin(δ2))/(cos(φ)*cos(δ2))
|
|
||||||
tmp := (Sin(An) - Sin(dec)*Sin(Lat)) / (Cos(dec) * Cos(Lat))
|
|
||||||
var rise float64
|
|
||||||
if math.Abs(tmp) <= 1 {
|
|
||||||
rzsc := ArcCos(tmp) / 15
|
|
||||||
if isRise {
|
|
||||||
rise = tztime - rzsc/24 - 25.0/24.0/60.0
|
|
||||||
} else {
|
|
||||||
rise = tztime + rzsc/24 - 25.0/24.0/60.0
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
rise = tztime
|
|
||||||
i := 0
|
|
||||||
//TODO:使用二分法计算
|
|
||||||
for NeptuneHeight(rise, Lon, Lat, ntz) > An {
|
|
||||||
i++
|
|
||||||
if isRise {
|
|
||||||
rise -= 15.0 / 60.0 / 24.0
|
|
||||||
} else {
|
|
||||||
rise += 15.0 / 60.0 / 24.0
|
|
||||||
}
|
|
||||||
if i > 48 {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
JD1 := rise
|
|
||||||
for {
|
|
||||||
JD0 := JD1
|
|
||||||
stDegree := NeptuneHeight(JD0, Lon, Lat, ntz) - An
|
|
||||||
stDegreep := (NeptuneHeight(JD0+0.000005, Lon, Lat, ntz) - NeptuneHeight(JD0-0.000005, Lon, Lat, ntz)) / 0.00001
|
|
||||||
JD1 = JD0 - stDegree/stDegreep
|
|
||||||
if math.Abs(JD1-JD0) <= 0.00001 {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return JD1 - ntz/24 + TZ/24
|
|
||||||
}
|
|
||||||
|
|
||||||
// Pos
|
|
||||||
|
|
||||||
const NEPTUNE_S_PERIOD = 1 / ((1 / 365.256363004) - (1 / 4332.59))
|
|
||||||
|
|
||||||
func neptuneConjunction(jde, degree float64, next uint8) float64 {
|
|
||||||
//0=last 1=next
|
|
||||||
decSub := func(jde float64, degree float64, filter bool) float64 {
|
|
||||||
sub := Limit360(Limit360(NeptuneApparentLo(jde)-HSunApparentLo(jde)) - degree)
|
|
||||||
if filter {
|
|
||||||
if sub > 180 {
|
|
||||||
sub -= 360
|
|
||||||
}
|
|
||||||
if sub < -180 {
|
|
||||||
sub += 360
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return sub
|
|
||||||
}
|
|
||||||
dayCost := NEPTUNE_S_PERIOD / 360
|
|
||||||
nowSub := decSub(jde, degree, false)
|
|
||||||
if next == 0 {
|
|
||||||
jde -= (360 - nowSub) * dayCost
|
|
||||||
} else {
|
|
||||||
jde += dayCost * nowSub
|
|
||||||
}
|
|
||||||
JD1 := jde
|
|
||||||
for {
|
|
||||||
JD0 := JD1
|
|
||||||
stDegree := decSub(JD0, degree, true)
|
|
||||||
stDegreep := (decSub(JD0+0.000005, degree, true) - decSub(JD0-0.000005, degree, true)) / 0.00001
|
|
||||||
JD1 = JD0 - stDegree/stDegreep
|
|
||||||
if math.Abs(JD1-JD0) <= 0.00001 {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return TD2UT(JD1, false)
|
|
||||||
}
|
|
||||||
|
|
||||||
func LastNeptuneConjunction(jde float64) float64 {
|
|
||||||
return neptuneConjunction(jde, 0, 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
func NextNeptuneConjunction(jde float64) float64 {
|
|
||||||
return neptuneConjunction(jde, 0, 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
func LastNeptuneOpposition(jde float64) float64 {
|
|
||||||
return neptuneConjunction(jde, 180, 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
func NextNeptuneOpposition(jde float64) float64 {
|
|
||||||
return neptuneConjunction(jde, 180, 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
func NextNeptuneEasternQuadrature(jde float64) float64 {
|
|
||||||
return neptuneConjunction(jde, 90, 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
func LastNeptuneEasternQuadrature(jde float64) float64 {
|
|
||||||
return neptuneConjunction(jde, 90, 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
func NextNeptuneWesternQuadrature(jde float64) float64 {
|
|
||||||
return neptuneConjunction(jde, 270, 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
func LastNeptuneWesternQuadrature(jde float64) float64 {
|
|
||||||
return neptuneConjunction(jde, 270, 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
func neptuneRetrograde(jde float64, isLeft bool) float64 {
|
|
||||||
//0=last 1=next
|
|
||||||
decSub := func(jde float64, val float64) float64 {
|
|
||||||
sub := NeptuneApparentRa(jde+val) - NeptuneApparentRa(jde-val)
|
|
||||||
if sub > 180 {
|
|
||||||
sub -= 360
|
|
||||||
}
|
|
||||||
if sub < -180 {
|
|
||||||
sub += 360
|
|
||||||
}
|
|
||||||
return sub / (2 * val)
|
|
||||||
}
|
|
||||||
jde = NextNeptuneOpposition(jde)
|
|
||||||
if isLeft {
|
|
||||||
jde -= 60
|
|
||||||
} else {
|
|
||||||
jde += 60
|
|
||||||
}
|
|
||||||
for {
|
|
||||||
nowSub := decSub(jde, 1.0/86400.0)
|
|
||||||
if math.Abs(nowSub) > 0.55 {
|
|
||||||
jde += 2
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
break
|
|
||||||
}
|
|
||||||
JD1 := jde
|
|
||||||
for {
|
|
||||||
JD0 := JD1
|
|
||||||
stDegree := decSub(JD0, 2.0/86400.0)
|
|
||||||
stDegreep := (decSub(JD0+15.0/86400.0, 2.0/86400.0) - decSub(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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
JD1 = JD1 - 15.0/86400.0
|
|
||||||
min := JD1
|
|
||||||
minRa := 100.0
|
|
||||||
for i := 0.0; i < 60.0; i++ {
|
|
||||||
tmp := decSub(JD1+i*0.5/86400.0, 0.5/86400.0)
|
|
||||||
if math.Abs(tmp) < math.Abs(minRa) {
|
|
||||||
minRa = tmp
|
|
||||||
min = JD1 + i*0.5/86400.0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return TD2UT(min, false)
|
|
||||||
}
|
|
||||||
|
|
||||||
func NextNeptuneRetrogradeToPrograde(jde float64) float64 {
|
|
||||||
date := neptuneRetrograde(jde, false)
|
|
||||||
if date < jde {
|
|
||||||
op := NextNeptuneOpposition(jde)
|
|
||||||
return neptuneRetrograde(op+10, false)
|
|
||||||
}
|
|
||||||
return date
|
|
||||||
}
|
|
||||||
|
|
||||||
func LastNeptuneRetrogradeToPrograde(jde float64) float64 {
|
|
||||||
jde = LastNeptuneOpposition(jde) - 10
|
|
||||||
date := neptuneRetrograde(jde, false)
|
|
||||||
if date > jde {
|
|
||||||
op := LastNeptuneOpposition(jde)
|
|
||||||
return neptuneRetrograde(op-10, false)
|
|
||||||
}
|
|
||||||
return date
|
|
||||||
}
|
|
||||||
|
|
||||||
func NextNeptuneProgradeToRetrograde(jde float64) float64 {
|
|
||||||
date := neptuneRetrograde(jde, true)
|
|
||||||
if date < jde {
|
|
||||||
op := NextNeptuneOpposition(jde)
|
|
||||||
return neptuneRetrograde(op+10, true)
|
|
||||||
}
|
|
||||||
return date
|
|
||||||
}
|
|
||||||
|
|
||||||
func LastNeptuneProgradeToRetrograde(jde float64) float64 {
|
|
||||||
jde = LastNeptuneOpposition(jde) - 10
|
|
||||||
date := neptuneRetrograde(jde, true)
|
|
||||||
if date > jde {
|
|
||||||
op := LastNeptuneOpposition(jde)
|
|
||||||
return neptuneRetrograde(op-10, true)
|
|
||||||
}
|
|
||||||
return date
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,207 @@
|
|||||||
|
package basic
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math"
|
||||||
|
|
||||||
|
. "b612.me/astro/tools"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Pos
|
||||||
|
|
||||||
|
const (
|
||||||
|
NEPTUNE_S_PERIOD = 1 / ((1 / 365.256363004) - (1 / 60190.03))
|
||||||
|
neptuneEventSearchN = 16
|
||||||
|
neptunePhaseCoarseTolerance = 30.0 / 86400.0
|
||||||
|
)
|
||||||
|
|
||||||
|
func neptuneSunLongitudeDelta(jde, degree float64, filter bool) float64 {
|
||||||
|
sub := Limit360(Limit360(NeptuneApparentLo(jde)-HSunApparentLo(jde)) - degree)
|
||||||
|
if filter {
|
||||||
|
if sub > 180 {
|
||||||
|
sub -= 360
|
||||||
|
}
|
||||||
|
if sub < -180 {
|
||||||
|
sub += 360
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return sub
|
||||||
|
}
|
||||||
|
|
||||||
|
func neptuneSunLongitudeDeltaN(jde, degree float64, filter bool, n int) float64 {
|
||||||
|
sub := Limit360(Limit360(NeptuneApparentLoN(jde, n)-HSunApparentLoN(jde, n)) - degree)
|
||||||
|
if filter {
|
||||||
|
if sub > 180 {
|
||||||
|
sub -= 360
|
||||||
|
}
|
||||||
|
if sub < -180 {
|
||||||
|
sub += 360
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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 {
|
||||||
|
//0=last 1=next
|
||||||
|
daysPerDegree := NEPTUNE_S_PERIOD / 360
|
||||||
|
currentDelta := neptuneSunLongitudeDelta(jde, degree, false)
|
||||||
|
if next == 0 {
|
||||||
|
jde -= (360 - currentDelta) * daysPerDegree
|
||||||
|
} else {
|
||||||
|
jde += daysPerDegree * currentDelta
|
||||||
|
}
|
||||||
|
estimateJD := jde
|
||||||
|
for {
|
||||||
|
prevJD := estimateJD
|
||||||
|
longitudeDelta := neptuneSunLongitudeDelta(prevJD, degree, true)
|
||||||
|
longitudeSlope := (neptuneSunLongitudeDelta(prevJD+0.000005, degree, true) - neptuneSunLongitudeDelta(prevJD-0.000005, degree, true)) / 0.00001
|
||||||
|
estimateJD = prevJD - longitudeDelta/longitudeSlope
|
||||||
|
if math.Abs(estimateJD-prevJD) <= 0.00001 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return TD2UT(estimateJD, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
func neptuneConjunction(jde, degree float64, next uint8) float64 {
|
||||||
|
//0=last 1=next
|
||||||
|
daysPerDegree := NEPTUNE_S_PERIOD / 360
|
||||||
|
currentDelta := neptuneSunLongitudeDelta(jde, degree, false)
|
||||||
|
if next == 0 {
|
||||||
|
jde -= (360 - currentDelta) * daysPerDegree
|
||||||
|
} else {
|
||||||
|
jde += daysPerDegree * currentDelta
|
||||||
|
}
|
||||||
|
estimateJD := jde
|
||||||
|
for {
|
||||||
|
prevJD := estimateJD
|
||||||
|
longitudeDelta := neptuneSunLongitudeDeltaN(prevJD, degree, true, neptuneEventSearchN)
|
||||||
|
longitudeSlope := (neptuneSunLongitudeDeltaN(prevJD+0.000005, degree, true, neptuneEventSearchN) - neptuneSunLongitudeDeltaN(prevJD-0.000005, degree, true, neptuneEventSearchN)) / 0.00001
|
||||||
|
estimateJD = prevJD - longitudeDelta/longitudeSlope
|
||||||
|
if math.Abs(estimateJD-prevJD) <= neptunePhaseCoarseTolerance {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for {
|
||||||
|
prevJD := estimateJD
|
||||||
|
longitudeDelta := neptuneSunLongitudeDelta(prevJD, degree, true)
|
||||||
|
longitudeSlope := (neptuneSunLongitudeDelta(prevJD+0.000005, degree, true) - neptuneSunLongitudeDelta(prevJD-0.000005, degree, true)) / 0.00001
|
||||||
|
estimateJD = prevJD - longitudeDelta/longitudeSlope
|
||||||
|
if math.Abs(estimateJD-prevJD) <= 0.00001 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return TD2UT(estimateJD, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
func LastNeptuneConjunction(jde float64) float64 {
|
||||||
|
return inclusiveLastPhaseEvent(jde, 0, neptuneConjunction)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NextNeptuneConjunction(jde float64) float64 {
|
||||||
|
return inclusiveNextPhaseEvent(jde, 0, neptuneConjunction)
|
||||||
|
}
|
||||||
|
|
||||||
|
func LastNeptuneOpposition(jde float64) float64 {
|
||||||
|
return inclusiveLastPhaseEvent(jde, 180, neptuneConjunction)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NextNeptuneOpposition(jde float64) float64 {
|
||||||
|
return inclusiveNextPhaseEvent(jde, 180, neptuneConjunction)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NextNeptuneEasternQuadrature(jde float64) float64 {
|
||||||
|
return inclusiveNextPhaseEvent(jde, 90, neptuneConjunction)
|
||||||
|
}
|
||||||
|
|
||||||
|
func LastNeptuneEasternQuadrature(jde float64) float64 {
|
||||||
|
return inclusiveLastPhaseEvent(jde, 90, neptuneConjunction)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NextNeptuneWesternQuadrature(jde float64) float64 {
|
||||||
|
return inclusiveNextPhaseEvent(jde, 270, neptuneConjunction)
|
||||||
|
}
|
||||||
|
|
||||||
|
func LastNeptuneWesternQuadrature(jde float64) float64 {
|
||||||
|
return inclusiveLastPhaseEvent(jde, 270, neptuneConjunction)
|
||||||
|
}
|
||||||
|
|
||||||
|
func neptuneRetrogradeAroundOpposition(oppositionJD float64, searchBeforeOpposition bool) float64 {
|
||||||
|
oppositionTT := TD2UT(oppositionJD, true)
|
||||||
|
startTT := oppositionTT
|
||||||
|
endTT := oppositionTT
|
||||||
|
if searchBeforeOpposition {
|
||||||
|
easternQuadratureUT := neptuneConjunction(oppositionTT, 90, 0)
|
||||||
|
startTT = TD2UT(easternQuadratureUT, true)
|
||||||
|
} else {
|
||||||
|
westernQuadratureUT := neptuneConjunction(oppositionTT, 270, 1)
|
||||||
|
endTT = TD2UT(westernQuadratureUT, true)
|
||||||
|
}
|
||||||
|
bestJD := zeroEventInWindow(startTT, endTT, 2.0, 2.0, 30.0/86400.0, func(jd float64) float64 {
|
||||||
|
return neptuneRADerivativeN(jd, 1.0/86400.0, neptuneEventSearchN)
|
||||||
|
}, func(jd float64) float64 {
|
||||||
|
return neptuneRADerivative(jd, 0.5/86400.0)
|
||||||
|
})
|
||||||
|
return TD2UT(bestJD, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NextNeptuneRetrogradeToPrograde(jde float64) float64 {
|
||||||
|
lastOppositionJD := neptuneConjunctionFull(jde, 180, 0)
|
||||||
|
date := neptuneRetrogradeAroundOpposition(lastOppositionJD, false)
|
||||||
|
if sameEventUTQueryTT(date, jde) || eventUTQueryAfterOrEqual(date, jde) {
|
||||||
|
return date
|
||||||
|
}
|
||||||
|
nextOppositionJD := neptuneConjunctionFull(jde, 180, 1)
|
||||||
|
return neptuneRetrogradeAroundOpposition(nextOppositionJD, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
func LastNeptuneRetrogradeToPrograde(jde float64) float64 {
|
||||||
|
lastOppositionJD := neptuneConjunctionFull(jde, 180, 0)
|
||||||
|
date := neptuneRetrogradeAroundOpposition(lastOppositionJD, false)
|
||||||
|
if sameEventUTQueryTT(date, jde) || eventUTQueryBeforeOrEqual(date, jde) {
|
||||||
|
return date
|
||||||
|
}
|
||||||
|
previousOppositionJD := neptuneConjunctionFull(eventUTLastQueryTT(lastOppositionJD), 180, 0)
|
||||||
|
return neptuneRetrogradeAroundOpposition(previousOppositionJD, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NextNeptuneProgradeToRetrograde(jde float64) float64 {
|
||||||
|
nextOppositionJD := neptuneConjunctionFull(jde, 180, 1)
|
||||||
|
date := neptuneRetrogradeAroundOpposition(nextOppositionJD, true)
|
||||||
|
if sameEventUTQueryTT(date, jde) || eventUTQueryAfterOrEqual(date, jde) {
|
||||||
|
return date
|
||||||
|
}
|
||||||
|
followingOppositionJD := neptuneConjunctionFull(eventUTNextQueryTT(nextOppositionJD), 180, 1)
|
||||||
|
return neptuneRetrogradeAroundOpposition(followingOppositionJD, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
func LastNeptuneProgradeToRetrograde(jde float64) float64 {
|
||||||
|
nextOppositionJD := neptuneConjunctionFull(jde, 180, 1)
|
||||||
|
date := neptuneRetrogradeAroundOpposition(nextOppositionJD, true)
|
||||||
|
if sameEventUTQueryTT(date, jde) || eventUTQueryBeforeOrEqual(date, jde) {
|
||||||
|
return date
|
||||||
|
}
|
||||||
|
lastOppositionJD := neptuneConjunctionFull(jde, 180, 0)
|
||||||
|
return neptuneRetrogradeAroundOpposition(lastOppositionJD, true)
|
||||||
|
}
|
||||||
@@ -0,0 +1,702 @@
|
|||||||
|
package basic
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math"
|
||||||
|
)
|
||||||
|
|
||||||
|
/*
|
||||||
|
黄赤交角、nutation==true时,计算交角章动
|
||||||
|
*/
|
||||||
|
func EclipticObliquity(jde float64, nutation bool) float64 {
|
||||||
|
eps := Obliquity1980(jde)
|
||||||
|
if nutation {
|
||||||
|
eps += Nutation2000Bs(jde)
|
||||||
|
}
|
||||||
|
return eps
|
||||||
|
}
|
||||||
|
|
||||||
|
func TrueObliquity(JD float64) float64 {
|
||||||
|
return EclipticObliquity(JD, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 黄经章动 1980
|
||||||
|
func Nutation1980i(jd float64) float64 { // '黄经章动
|
||||||
|
res, _ := Nutation1980(jd)
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
// 交角章动1980
|
||||||
|
func Nutation1980s(jd float64) float64 { //交角章动
|
||||||
|
_, res := Nutation1980(jd)
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
// 黄经章动 2000B
|
||||||
|
func Nutation2000Bi(jd float64) float64 { // '黄经章动
|
||||||
|
res, _ := Nutation2000B(jd)
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
// 交角章动2000B
|
||||||
|
func Nutation2000Bs(jd float64) float64 { //交角章动
|
||||||
|
_, res := Nutation2000B(jd)
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
// 定义 IAU 1980 章动系数
|
||||||
|
var coefficientsNut1980 = [][9]float64{
|
||||||
|
{0, 0, 0, 0, 1, -171996.0, -174.2, 92025.0, 8.9},
|
||||||
|
{0, 0, 0, 0, 2, 2062.0, 0.2, -895.0, 0.5},
|
||||||
|
{-2, 0, 2, 0, 1, 46.0, 0.0, -24.0, 0.0},
|
||||||
|
{2, 0, -2, 0, 0, 11.0, 0.0, 0.0, 0.0},
|
||||||
|
{-2, 0, 2, 0, 2, -3.0, 0.0, 1.0, 0.0},
|
||||||
|
{1, -1, 0, -1, 0, -3.0, 0.0, 0.0, 0.0},
|
||||||
|
{0, -2, 2, -2, 1, -2.0, 0.0, 1.0, 0.0},
|
||||||
|
{2, 0, -2, 0, 1, 1.0, 0.0, 0.0, 0.0},
|
||||||
|
{0, 0, 2, -2, 2, -13187.0, -1.6, 5736.0, -3.1},
|
||||||
|
{0, 1, 0, 0, 0, 1426.0, -3.4, 54.0, -0.1},
|
||||||
|
{0, 1, 2, -2, 2, -517.0, 1.2, 224.0, -0.6},
|
||||||
|
{0, -1, 2, -2, 2, 217.0, -0.5, -95.0, 0.3},
|
||||||
|
{0, 0, 2, -2, 1, 129.0, 0.1, -70.0, 0.0},
|
||||||
|
{2, 0, 0, -2, 0, 48.0, 0.0, 1.0, 0.0},
|
||||||
|
{0, 0, 2, -2, 0, -22.0, 0.0, 0.0, 0.0},
|
||||||
|
{0, 2, 0, 0, 0, 17.0, -0.1, 0.0, 0.0},
|
||||||
|
{0, 1, 0, 0, 1, -15.0, 0.0, 9.0, 0.0},
|
||||||
|
{0, 2, 2, -2, 2, -16.0, 0.1, 7.0, 0.0},
|
||||||
|
{0, -1, 0, 0, 1, -12.0, 0.0, 6.0, 0.0},
|
||||||
|
{-2, 0, 0, 2, 1, -6.0, 0.0, 3.0, 0.0},
|
||||||
|
{0, -1, 2, -2, 1, -5.0, 0.0, 3.0, 0.0},
|
||||||
|
{2, 0, 0, -2, 1, 4.0, 0.0, -2.0, 0.0},
|
||||||
|
{0, 1, 2, -2, 1, 4.0, 0.0, -2.0, 0.0},
|
||||||
|
{1, 0, 0, -1, 0, -4.0, 0.0, 0.0, 0.0},
|
||||||
|
{2, 1, 0, -2, 0, 1.0, 0.0, 0.0, 0.0},
|
||||||
|
{0, 0, -2, 2, 1, 1.0, 0.0, 0.0, 0.0},
|
||||||
|
{0, 1, -2, 2, 0, -1.0, 0.0, 0.0, 0.0},
|
||||||
|
{0, 1, 0, 0, 2, 1.0, 0.0, 0.0, 0.0},
|
||||||
|
{-1, 0, 0, 1, 1, 1.0, 0.0, 0.0, 0.0},
|
||||||
|
{0, 1, 2, -2, 0, -1.0, 0.0, 0.0, 0.0},
|
||||||
|
{0, 0, 2, 0, 2, -2274.0, -0.2, 977.0, -0.5},
|
||||||
|
{1, 0, 0, 0, 0, 712.0, 0.1, -7.0, 0.0},
|
||||||
|
{0, 0, 2, 0, 1, -386.0, -0.4, 200.0, 0.0},
|
||||||
|
{1, 0, 2, 0, 2, -301.0, 0.0, 129.0, -0.1},
|
||||||
|
{1, 0, 0, -2, 0, -158.0, 0.0, -1.0, 0.0},
|
||||||
|
{-1, 0, 2, 0, 2, 123.0, 0.0, -53.0, 0.0},
|
||||||
|
{0, 0, 0, 2, 0, 63.0, 0.0, -2.0, 0.0},
|
||||||
|
{1, 0, 0, 0, 1, 63.0, 0.1, -33.0, 0.0},
|
||||||
|
{-1, 0, 0, 0, 1, -58.0, -0.1, 32.0, 0.0},
|
||||||
|
{-1, 0, 2, 2, 2, -59.0, 0.0, 26.0, 0.0},
|
||||||
|
{1, 0, 2, 0, 1, -51.0, 0.0, 27.0, 0.0},
|
||||||
|
{0, 0, 2, 2, 2, -38.0, 0.0, 16.0, 0.0},
|
||||||
|
{2, 0, 0, 0, 0, 29.0, 0.0, -1.0, 0.0},
|
||||||
|
{1, 0, 2, -2, 2, 29.0, 0.0, -12.0, 0.0},
|
||||||
|
{2, 0, 2, 0, 2, -31.0, 0.0, 13.0, 0.0},
|
||||||
|
{0, 0, 2, 0, 0, 26.0, 0.0, -1.0, 0.0},
|
||||||
|
{-1, 0, 2, 0, 1, 21.0, 0.0, -10.0, 0.0},
|
||||||
|
{-1, 0, 0, 2, 1, 16.0, 0.0, -8.0, 0.0},
|
||||||
|
{1, 0, 0, -2, 1, -13.0, 0.0, 7.0, 0.0},
|
||||||
|
{-1, 0, 2, 2, 1, -10.0, 0.0, 5.0, 0.0},
|
||||||
|
{1, 1, 0, -2, 0, -7.0, 0.0, 0.0, 0.0},
|
||||||
|
{0, 1, 2, 0, 2, 7.0, 0.0, -3.0, 0.0},
|
||||||
|
{0, -1, 2, 0, 2, -7.0, 0.0, 3.0, 0.0},
|
||||||
|
{1, 0, 2, 2, 2, -8.0, 0.0, 3.0, 0.0},
|
||||||
|
{1, 0, 0, 2, 0, 6.0, 0.0, 0.0, 0.0},
|
||||||
|
{2, 0, 2, -2, 2, 6.0, 0.0, -3.0, 0.0},
|
||||||
|
{0, 0, 0, 2, 1, -6.0, 0.0, 3.0, 0.0},
|
||||||
|
{0, 0, 2, 2, 1, -7.0, 0.0, 3.0, 0.0},
|
||||||
|
{1, 0, 2, -2, 1, 6.0, 0.0, -3.0, 0.0},
|
||||||
|
{0, 0, 0, -2, 1, -5.0, 0.0, 3.0, 0.0},
|
||||||
|
{1, -1, 0, 0, 0, 5.0, 0.0, 0.0, 0.0},
|
||||||
|
{2, 0, 2, 0, 1, -5.0, 0.0, 3.0, 0.0},
|
||||||
|
{0, 1, 0, -2, 0, -4.0, 0.0, 0.0, 0.0},
|
||||||
|
{1, 0, -2, 0, 0, 4.0, 0.0, 0.0, 0.0},
|
||||||
|
{0, 0, 0, 1, 0, -4.0, 0.0, 0.0, 0.0},
|
||||||
|
{1, 1, 0, 0, 0, -3.0, 0.0, 0.0, 0.0},
|
||||||
|
{1, 0, 2, 0, 0, 3.0, 0.0, 0.0, 0.0},
|
||||||
|
{1, -1, 2, 0, 2, -3.0, 0.0, 1.0, 0.0},
|
||||||
|
{-1, -1, 2, 2, 2, -3.0, 0.0, 1.0, 0.0},
|
||||||
|
{-2, 0, 0, 0, 1, -2.0, 0.0, 1.0, 0.0},
|
||||||
|
{3, 0, 2, 0, 2, -3.0, 0.0, 1.0, 0.0},
|
||||||
|
{0, -1, 2, 2, 2, -3.0, 0.0, 1.0, 0.0},
|
||||||
|
{1, 1, 2, 0, 2, 2.0, 0.0, -1.0, 0.0},
|
||||||
|
{-1, 0, 2, -2, 1, -2.0, 0.0, 1.0, 0.0},
|
||||||
|
{2, 0, 0, 0, 1, 2.0, 0.0, -1.0, 0.0},
|
||||||
|
{1, 0, 0, 0, 2, -2.0, 0.0, 1.0, 0.0},
|
||||||
|
{3, 0, 0, 0, 0, 2.0, 0.0, 0.0, 0.0},
|
||||||
|
{0, 0, 2, 1, 2, 2.0, 0.0, -1.0, 0.0},
|
||||||
|
{-1, 0, 0, 0, 2, 1.0, 0.0, -1.0, 0.0},
|
||||||
|
{1, 0, 0, -4, 0, -1.0, 0.0, 0.0, 0.0},
|
||||||
|
{-2, 0, 2, 2, 2, 1.0, 0.0, -1.0, 0.0},
|
||||||
|
{-1, 0, 2, 4, 2, -2.0, 0.0, 1.0, 0.0},
|
||||||
|
{2, 0, 0, -4, 0, -1.0, 0.0, 0.0, 0.0},
|
||||||
|
{1, 1, 2, -2, 2, 1.0, 0.0, -1.0, 0.0},
|
||||||
|
{1, 0, 2, 2, 1, -1.0, 0.0, 1.0, 0.0},
|
||||||
|
{-2, 0, 2, 4, 2, -1.0, 0.0, 1.0, 0.0},
|
||||||
|
{-1, 0, 4, 0, 2, 1.0, 0.0, 0.0, 0.0},
|
||||||
|
{1, -1, 0, -2, 0, 1.0, 0.0, 0.0, 0.0},
|
||||||
|
{2, 0, 2, -2, 1, 1.0, 0.0, -1.0, 0.0},
|
||||||
|
{2, 0, 2, 2, 2, -1.0, 0.0, 0.0, 0.0},
|
||||||
|
{1, 0, 0, 2, 1, -1.0, 0.0, 0.0, 0.0},
|
||||||
|
{0, 0, 4, -2, 2, 1.0, 0.0, 0.0, 0.0},
|
||||||
|
{3, 0, 2, -2, 2, 1.0, 0.0, 0.0, 0.0},
|
||||||
|
{1, 0, 2, -2, 0, -1.0, 0.0, 0.0, 0.0},
|
||||||
|
{0, 1, 2, 0, 1, 1.0, 0.0, 0.0, 0.0},
|
||||||
|
{-1, -1, 0, 2, 1, 1.0, 0.0, 0.0, 0.0},
|
||||||
|
{0, 0, -2, 0, 1, -1.0, 0.0, 0.0, 0.0},
|
||||||
|
{0, 0, 2, -1, 2, -1.0, 0.0, 0.0, 0.0},
|
||||||
|
{0, 1, 0, 2, 0, -1.0, 0.0, 0.0, 0.0},
|
||||||
|
{1, 0, -2, -2, 0, -1.0, 0.0, 0.0, 0.0},
|
||||||
|
{0, -1, 2, 0, 1, -1.0, 0.0, 0.0, 0.0},
|
||||||
|
{1, 1, 0, -2, 1, -1.0, 0.0, 0.0, 0.0},
|
||||||
|
{1, 0, -2, 2, 0, -1.0, 0.0, 0.0, 0.0},
|
||||||
|
{2, 0, 0, 2, 0, 1.0, 0.0, 0.0, 0.0},
|
||||||
|
{0, 0, 2, 4, 2, -1.0, 0.0, 0.0, 0.0},
|
||||||
|
{0, 1, 0, 1, 0, 1.0, 0.0, 0.0, 0.0},
|
||||||
|
}
|
||||||
|
|
||||||
|
// fmod 函数实现浮点数取模
|
||||||
|
func fmod(f, m float64) float64 {
|
||||||
|
return f - m*math.Floor(f/m)
|
||||||
|
}
|
||||||
|
|
||||||
|
// frac 函数获取小数部分
|
||||||
|
func frac(f float64) float64 {
|
||||||
|
return f - math.Floor(f)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Obliquity1980 计算黄赤交角,单位为度
|
||||||
|
// 公式 3.222-1
|
||||||
|
func Obliquity1980(jd float64) float64 {
|
||||||
|
T := (jd - 2451545.0) / 36525.0
|
||||||
|
as2r := ((1.0 / 3600.0) * math.Pi) / 180.0
|
||||||
|
eps := 84381.448 - 46.8150*T - 0.00059*T*T + 0.001813*T*T*T // 84381.448 = 23d26'21.448 转换为角秒
|
||||||
|
|
||||||
|
return math.Mod(eps*as2r, 2*math.Pi) * deg
|
||||||
|
}
|
||||||
|
|
||||||
|
// Nutation1980 计算 IAU 1980 章动模型
|
||||||
|
// 返回黄经章动 (dpsi) 和交角章动 (deps),单位为度
|
||||||
|
func Nutation1980(jd float64) (float64, float64) {
|
||||||
|
t := (jd - 2451545.0) / 36525.0
|
||||||
|
|
||||||
|
twoPI := 2 * math.Pi
|
||||||
|
as2r := ((1.0 / 3600.0) * math.Pi) / 180.0
|
||||||
|
|
||||||
|
// 表 3.222.2 - 计算章动参数
|
||||||
|
l := fmod((485866.733+(715922.633+(31.310+0.064*t)*t)*t)*as2r+frac(1325.0*t)*twoPI, twoPI)
|
||||||
|
lp := fmod((1287099.804+(1292581.224+(-0.577-0.012*t)*t)*t)*as2r+frac(99.0*t)*twoPI, twoPI)
|
||||||
|
F := fmod((335778.877+(295263.137+(-13.257+0.011*t)*t)*t)*as2r+frac(1342.0*t)*twoPI, twoPI)
|
||||||
|
D := fmod((1072261.307+(1105601.328+(-6.891+0.019*t)*t)*t)*as2r+frac(1236.0*t)*twoPI, twoPI)
|
||||||
|
O := fmod((450160.280+(-482890.539+(7.455+0.008*t)*t)*t)*as2r+frac(-5.0*t)*twoPI, twoPI)
|
||||||
|
|
||||||
|
deps := 0.0
|
||||||
|
dpsi := 0.0
|
||||||
|
|
||||||
|
// 公式 3.222-6 - 计算章动
|
||||||
|
for i := len(coefficientsNut1980) - 1; i >= 0; i-- {
|
||||||
|
sumargs := coefficientsNut1980[i][0]*l +
|
||||||
|
coefficientsNut1980[i][1]*lp +
|
||||||
|
coefficientsNut1980[i][2]*F +
|
||||||
|
coefficientsNut1980[i][3]*D +
|
||||||
|
coefficientsNut1980[i][4]*O
|
||||||
|
|
||||||
|
deps += math.Cos(sumargs) * (coefficientsNut1980[i][7] + coefficientsNut1980[i][8]*t)
|
||||||
|
dpsi += math.Sin(sumargs) * (coefficientsNut1980[i][5] + coefficientsNut1980[i][6]*t)
|
||||||
|
}
|
||||||
|
|
||||||
|
deps = (deps * as2r) / 10000.0
|
||||||
|
dpsi = (dpsi * as2r) / 10000.0
|
||||||
|
|
||||||
|
return dpsi * deg, deps * deg
|
||||||
|
}
|
||||||
|
|
||||||
|
// Nutation2000B 计算 IAU 2000B 章动模型
|
||||||
|
// 返回交角章动 (de) 和黄经章动 (dp),单位为度
|
||||||
|
func Nutation2000B(jd float64) (float64, float64) {
|
||||||
|
// 常量定义
|
||||||
|
as2r := 4.848136811095359935899141e-6 // 角秒到弧度的转换因子
|
||||||
|
twopi := 6.283185307179586476925287 // 2π
|
||||||
|
|
||||||
|
T := (jd - 2451545) / 36525
|
||||||
|
L := math.Mod(485868.249036+1717915923.2178*T, 1296000.0) * as2r
|
||||||
|
Lp := math.Mod(1287104.79305+129596581.0481*T, 1296000.0) * as2r
|
||||||
|
F := math.Mod(335779.526232+1739527262.8478*T, 1296000.0) * as2r
|
||||||
|
D := math.Mod(1072260.70369+1602961601.2090*T, 1296000.0) * as2r
|
||||||
|
Om := math.Mod(450160.398036-6962890.5431*T, 1296000.0) * as2r
|
||||||
|
|
||||||
|
dp := 0.0
|
||||||
|
de := 0.0
|
||||||
|
var arg, sinarg, cosarg float64
|
||||||
|
|
||||||
|
// 以下是 IAU 2000B 章动模型的各项计算
|
||||||
|
// 每行对应一个章动项
|
||||||
|
arg = math.Mod(L+Lp+2*F+-2*D+2*Om, twopi)
|
||||||
|
sinarg = math.Sin(arg)
|
||||||
|
cosarg = math.Cos(arg)
|
||||||
|
dp += 1290 * sinarg
|
||||||
|
de += -556 * cosarg
|
||||||
|
|
||||||
|
arg = math.Mod(-1*L+2*Om, twopi)
|
||||||
|
sinarg = math.Sin(arg)
|
||||||
|
cosarg = math.Cos(arg)
|
||||||
|
dp += 1405*sinarg + 4*cosarg
|
||||||
|
de += -610*cosarg + 2*sinarg
|
||||||
|
|
||||||
|
arg = math.Mod(-2*L+2*F+2*D+2*Om, twopi)
|
||||||
|
sinarg = math.Sin(arg)
|
||||||
|
cosarg = math.Cos(arg)
|
||||||
|
dp += 1383*sinarg + -2*cosarg
|
||||||
|
de += -594*cosarg + -2*sinarg
|
||||||
|
|
||||||
|
arg = math.Mod(L+2*F+2*D+Om, twopi)
|
||||||
|
sinarg = math.Sin(arg)
|
||||||
|
cosarg = math.Cos(arg)
|
||||||
|
dp += -1331*sinarg + 8*cosarg
|
||||||
|
de += 663*cosarg + 4*sinarg
|
||||||
|
|
||||||
|
arg = math.Mod(-2*Lp+2*F+-2*D+Om, twopi)
|
||||||
|
sinarg = math.Sin(arg)
|
||||||
|
cosarg = math.Cos(arg)
|
||||||
|
dp += -1283 * sinarg
|
||||||
|
de += 672 * cosarg
|
||||||
|
|
||||||
|
arg = math.Mod(-1*L+Lp+D+Om, twopi)
|
||||||
|
sinarg = math.Sin(arg)
|
||||||
|
cosarg = math.Cos(arg)
|
||||||
|
dp += 1314 * sinarg
|
||||||
|
de += -700 * cosarg
|
||||||
|
|
||||||
|
arg = math.Mod(-1*L+2*F+4*D+2*Om, twopi)
|
||||||
|
sinarg = math.Sin(arg)
|
||||||
|
cosarg = math.Cos(arg)
|
||||||
|
dp += -1521*sinarg + 9*cosarg
|
||||||
|
de += 647*cosarg + 4*sinarg
|
||||||
|
|
||||||
|
arg = math.Mod(2*F+D+2*Om, twopi)
|
||||||
|
sinarg = math.Sin(arg)
|
||||||
|
cosarg = math.Cos(arg)
|
||||||
|
dp += 1660*sinarg + -5*cosarg
|
||||||
|
de += -710*cosarg + -2*sinarg
|
||||||
|
|
||||||
|
arg = math.Mod(-1*L+D, twopi)
|
||||||
|
sinarg = math.Sin(arg)
|
||||||
|
cosarg = math.Cos(arg)
|
||||||
|
dp += 4026*sinarg + -353*cosarg
|
||||||
|
de += -553*cosarg + -139*sinarg
|
||||||
|
|
||||||
|
arg = math.Mod(L+2*Om, twopi)
|
||||||
|
sinarg = math.Sin(arg)
|
||||||
|
cosarg = math.Cos(arg)
|
||||||
|
dp += -1981 * sinarg
|
||||||
|
de += 854 * cosarg
|
||||||
|
|
||||||
|
arg = math.Mod(-1*L+2*F+-2*D+Om, twopi)
|
||||||
|
sinarg = math.Sin(arg)
|
||||||
|
cosarg = math.Cos(arg)
|
||||||
|
dp += -1987*sinarg + -6*cosarg
|
||||||
|
de += 1073*cosarg + -2*sinarg
|
||||||
|
|
||||||
|
arg = math.Mod(L+2*F, twopi)
|
||||||
|
sinarg = math.Sin(arg)
|
||||||
|
cosarg = math.Cos(arg)
|
||||||
|
dp += 3339*sinarg + -13*cosarg
|
||||||
|
de += -107*cosarg + 1*sinarg
|
||||||
|
|
||||||
|
arg = math.Mod(L+Lp, twopi)
|
||||||
|
sinarg = math.Sin(arg)
|
||||||
|
cosarg = math.Cos(arg)
|
||||||
|
dp += -3389*sinarg + 5*cosarg
|
||||||
|
de += 35*cosarg + -2*sinarg
|
||||||
|
|
||||||
|
arg = math.Mod(-1*L+Lp+D, twopi)
|
||||||
|
sinarg = math.Sin(arg)
|
||||||
|
cosarg = math.Cos(arg)
|
||||||
|
dp += 3276*sinarg + 1*cosarg
|
||||||
|
de += -9 * cosarg
|
||||||
|
|
||||||
|
arg = math.Mod(2*L+Om, twopi)
|
||||||
|
sinarg = math.Sin(arg)
|
||||||
|
cosarg = math.Cos(arg)
|
||||||
|
dp += 2179*sinarg + -2*cosarg
|
||||||
|
de += -1129*cosarg + -2*sinarg
|
||||||
|
|
||||||
|
arg = math.Mod(L+Lp+2*F+2*Om, twopi)
|
||||||
|
sinarg = math.Sin(arg)
|
||||||
|
cosarg = math.Cos(arg)
|
||||||
|
dp += 2481*sinarg + -7*cosarg
|
||||||
|
de += -1062*cosarg + -3*sinarg
|
||||||
|
|
||||||
|
arg = math.Mod(-2*L+Om, twopi)
|
||||||
|
sinarg = math.Sin(arg)
|
||||||
|
cosarg = math.Cos(arg)
|
||||||
|
dp += -2294*sinarg + -10*cosarg
|
||||||
|
de += 1266*cosarg + -4*sinarg
|
||||||
|
|
||||||
|
arg = math.Mod(-1*Lp+2*F+2*D+2*Om, twopi)
|
||||||
|
sinarg = math.Sin(arg)
|
||||||
|
cosarg = math.Cos(arg)
|
||||||
|
dp += -2647*sinarg + 11*cosarg
|
||||||
|
de += 1129*cosarg + 5*sinarg
|
||||||
|
|
||||||
|
arg = math.Mod(-1*L+2*F, twopi)
|
||||||
|
sinarg = math.Sin(arg)
|
||||||
|
cosarg = math.Cos(arg)
|
||||||
|
dp += -4056*sinarg + 5*cosarg
|
||||||
|
de += 40*cosarg + -2*sinarg
|
||||||
|
|
||||||
|
arg = math.Mod(-1*L+-1*Lp+2*F+2*D+2*Om, twopi)
|
||||||
|
sinarg = math.Sin(arg)
|
||||||
|
cosarg = math.Cos(arg)
|
||||||
|
dp += -2819*sinarg + 7*cosarg
|
||||||
|
de += 1207*cosarg + 3*sinarg
|
||||||
|
|
||||||
|
arg = math.Mod(D, twopi)
|
||||||
|
sinarg = math.Sin(arg)
|
||||||
|
cosarg = math.Cos(arg)
|
||||||
|
dp += -4230*sinarg + 5*cosarg
|
||||||
|
de += -20*cosarg + -2*sinarg
|
||||||
|
|
||||||
|
arg = math.Mod(L+-1*Lp+2*F+2*Om, twopi)
|
||||||
|
sinarg = math.Sin(arg)
|
||||||
|
cosarg = math.Cos(arg)
|
||||||
|
dp += -2878*sinarg + 8*cosarg
|
||||||
|
de += 1232*cosarg + 4*sinarg
|
||||||
|
|
||||||
|
arg = math.Mod(-1*Lp+2*D, twopi)
|
||||||
|
sinarg = math.Sin(arg)
|
||||||
|
cosarg = math.Cos(arg)
|
||||||
|
dp += 4348*sinarg + -10*cosarg
|
||||||
|
de += -81*cosarg + 2*sinarg
|
||||||
|
|
||||||
|
arg = math.Mod(3*L+2*F+2*Om, twopi)
|
||||||
|
sinarg = math.Sin(arg)
|
||||||
|
cosarg = math.Cos(arg)
|
||||||
|
dp += -2904*sinarg + 15*cosarg
|
||||||
|
de += 1233*cosarg + 7*sinarg
|
||||||
|
|
||||||
|
arg = math.Mod(-2*L+2*F+2*Om, twopi)
|
||||||
|
sinarg = math.Sin(arg)
|
||||||
|
cosarg = math.Cos(arg)
|
||||||
|
dp += -3075*sinarg + -2*cosarg
|
||||||
|
de += 1313*cosarg + -1*sinarg
|
||||||
|
|
||||||
|
arg = math.Mod(L+-1*Lp, twopi)
|
||||||
|
sinarg = math.Sin(arg)
|
||||||
|
cosarg = math.Cos(arg)
|
||||||
|
dp += 4725*sinarg + -6*cosarg
|
||||||
|
de += -41*cosarg + 3*sinarg
|
||||||
|
|
||||||
|
arg = math.Mod(Lp+2*F+-2*D+Om, twopi)
|
||||||
|
sinarg = math.Sin(arg)
|
||||||
|
cosarg = math.Cos(arg)
|
||||||
|
dp += 3579*sinarg + 5*cosarg
|
||||||
|
de += -1900*cosarg + 1*sinarg
|
||||||
|
|
||||||
|
arg = math.Mod(L+2*D, twopi)
|
||||||
|
sinarg = math.Sin(arg)
|
||||||
|
cosarg = math.Cos(arg)
|
||||||
|
dp += 6579*sinarg + -24*cosarg
|
||||||
|
de += -199*cosarg + 2*sinarg
|
||||||
|
|
||||||
|
arg = math.Mod(2*L+-2*D+Om, twopi)
|
||||||
|
sinarg = math.Sin(arg)
|
||||||
|
cosarg = math.Cos(arg)
|
||||||
|
dp += 4065*sinarg + 6*cosarg
|
||||||
|
de += -2206*cosarg + 1*sinarg
|
||||||
|
|
||||||
|
arg = math.Mod(-1*L+-1*Lp+2*D, twopi)
|
||||||
|
sinarg = math.Sin(arg)
|
||||||
|
cosarg = math.Cos(arg)
|
||||||
|
dp += 7350*sinarg + -8*cosarg
|
||||||
|
de += -51*cosarg + 4*sinarg
|
||||||
|
|
||||||
|
arg = math.Mod(-2*D+Om, twopi)
|
||||||
|
sinarg = math.Sin(arg)
|
||||||
|
cosarg = math.Cos(arg)
|
||||||
|
dp += (-4940+-11*T)*sinarg + -21*cosarg
|
||||||
|
de += 2720*cosarg + -9*sinarg
|
||||||
|
|
||||||
|
arg = math.Mod(-1*Lp+2*F+-2*D+Om, twopi)
|
||||||
|
sinarg = math.Sin(arg)
|
||||||
|
cosarg = math.Cos(arg)
|
||||||
|
dp += (-4752+-11*T)*sinarg + -3*cosarg
|
||||||
|
de += 2719*cosarg + -3*sinarg
|
||||||
|
|
||||||
|
arg = math.Mod(2*L+2*F+Om, twopi)
|
||||||
|
sinarg = math.Sin(arg)
|
||||||
|
cosarg = math.Cos(arg)
|
||||||
|
dp += -5350*sinarg + 21*cosarg
|
||||||
|
de += 2695*cosarg + 12*sinarg
|
||||||
|
|
||||||
|
arg = math.Mod(-2*L+2*D+Om, twopi)
|
||||||
|
sinarg = math.Sin(arg)
|
||||||
|
cosarg = math.Cos(arg)
|
||||||
|
dp += (-5774+-11*T)*sinarg + -15*cosarg
|
||||||
|
de += 3041*cosarg + -5*sinarg
|
||||||
|
|
||||||
|
arg = math.Mod(2*L+2*F+-2*D+2*Om, twopi)
|
||||||
|
sinarg = math.Sin(arg)
|
||||||
|
cosarg = math.Cos(arg)
|
||||||
|
dp += 6443*sinarg + -7*cosarg
|
||||||
|
de += -2768*cosarg + -4*sinarg
|
||||||
|
|
||||||
|
arg = math.Mod(L+2*F+-2*D+Om, twopi)
|
||||||
|
sinarg = math.Sin(arg)
|
||||||
|
cosarg = math.Cos(arg)
|
||||||
|
dp += (5800+10*T)*sinarg + 2*cosarg
|
||||||
|
de += -3045*cosarg + -1*sinarg
|
||||||
|
|
||||||
|
arg = math.Mod(2*D+Om, twopi)
|
||||||
|
sinarg = math.Sin(arg)
|
||||||
|
cosarg = math.Cos(arg)
|
||||||
|
dp += (-6302+-11*T)*sinarg + 2*cosarg
|
||||||
|
de += 3272*cosarg + 4*sinarg
|
||||||
|
|
||||||
|
arg = math.Mod(-1*Lp+2*F+2*Om, twopi)
|
||||||
|
sinarg = math.Sin(arg)
|
||||||
|
cosarg = math.Cos(arg)
|
||||||
|
dp += (-7141+21*T)*sinarg + 8*cosarg
|
||||||
|
de += 3070*cosarg + 4*sinarg
|
||||||
|
|
||||||
|
arg = math.Mod(2*F+2*D+Om, twopi)
|
||||||
|
sinarg = math.Sin(arg)
|
||||||
|
cosarg = math.Cos(arg)
|
||||||
|
dp += (-6637+-11*T)*sinarg + 25*cosarg
|
||||||
|
de += 3353*cosarg + 14*sinarg
|
||||||
|
|
||||||
|
arg = math.Mod(Lp+2*F+2*Om, twopi)
|
||||||
|
sinarg = math.Sin(arg)
|
||||||
|
cosarg = math.Cos(arg)
|
||||||
|
dp += (7566+-21*T)*sinarg + -11*cosarg
|
||||||
|
de += -3250*cosarg + -5*sinarg
|
||||||
|
|
||||||
|
arg = math.Mod(-2*L+2*F, twopi)
|
||||||
|
sinarg = math.Sin(arg)
|
||||||
|
cosarg = math.Cos(arg)
|
||||||
|
dp += -11024*sinarg + -14*cosarg
|
||||||
|
de += 104*cosarg + 2*sinarg
|
||||||
|
|
||||||
|
arg = math.Mod(L+2*F+2*D+2*Om, twopi)
|
||||||
|
sinarg = math.Sin(arg)
|
||||||
|
cosarg = math.Cos(arg)
|
||||||
|
dp += -7691*sinarg + 44*cosarg
|
||||||
|
de += 3268*cosarg + 19*sinarg
|
||||||
|
|
||||||
|
arg = math.Mod(2*Lp, twopi)
|
||||||
|
sinarg = math.Sin(arg)
|
||||||
|
cosarg = math.Cos(arg)
|
||||||
|
dp += (16707+-85*T)*sinarg + -10*cosarg
|
||||||
|
de += (168+-1*T)*cosarg + 10*sinarg
|
||||||
|
|
||||||
|
arg = math.Mod(-1*L+2*F+2*D+Om, twopi)
|
||||||
|
sinarg = math.Sin(arg)
|
||||||
|
cosarg = math.Cos(arg)
|
||||||
|
dp += -10204*sinarg + 25*cosarg
|
||||||
|
de += 5222*cosarg + 15*sinarg
|
||||||
|
|
||||||
|
arg = math.Mod(-1*Lp+Om, twopi)
|
||||||
|
sinarg = math.Sin(arg)
|
||||||
|
cosarg = math.Cos(arg)
|
||||||
|
dp += (-12654+11*T)*sinarg + 63*cosarg
|
||||||
|
de += 6415*cosarg + 26*sinarg
|
||||||
|
|
||||||
|
arg = math.Mod(L+-2*D+Om, twopi)
|
||||||
|
sinarg = math.Sin(arg)
|
||||||
|
cosarg = math.Cos(arg)
|
||||||
|
dp += (-12873+-10*T)*sinarg + -37*cosarg
|
||||||
|
de += 6953*cosarg + -14*sinarg
|
||||||
|
|
||||||
|
arg = math.Mod(-2*F+2*D, twopi)
|
||||||
|
sinarg = math.Sin(arg)
|
||||||
|
cosarg = math.Cos(arg)
|
||||||
|
dp += 21783*sinarg + 13*cosarg
|
||||||
|
de += -167*cosarg + 13*sinarg
|
||||||
|
|
||||||
|
arg = math.Mod(2*Lp+2*F+-2*D+2*Om, twopi)
|
||||||
|
sinarg = math.Sin(arg)
|
||||||
|
cosarg = math.Cos(arg)
|
||||||
|
dp += (-15794+72*T)*sinarg + -16*cosarg
|
||||||
|
de += (6850+-42*T)*cosarg + -5*sinarg
|
||||||
|
|
||||||
|
arg = math.Mod(-1*L+2*D+Om, twopi)
|
||||||
|
sinarg = math.Sin(arg)
|
||||||
|
cosarg = math.Cos(arg)
|
||||||
|
dp += (15164+10*T)*sinarg + 11*cosarg
|
||||||
|
de += -8001*cosarg + -1*sinarg
|
||||||
|
|
||||||
|
arg = math.Mod(Lp+Om, twopi)
|
||||||
|
sinarg = math.Sin(arg)
|
||||||
|
cosarg = math.Cos(arg)
|
||||||
|
dp += (-14053+-25*T)*sinarg + 79*cosarg
|
||||||
|
de += (8551+-2*T)*cosarg + -45*sinarg
|
||||||
|
|
||||||
|
arg = math.Mod(2*F, twopi)
|
||||||
|
sinarg = math.Sin(arg)
|
||||||
|
cosarg = math.Cos(arg)
|
||||||
|
dp += 25887*sinarg + -66*cosarg
|
||||||
|
de += -550*cosarg + 11*sinarg
|
||||||
|
|
||||||
|
arg = math.Mod(2*L, twopi)
|
||||||
|
sinarg = math.Sin(arg)
|
||||||
|
cosarg = math.Cos(arg)
|
||||||
|
dp += 29243*sinarg + -74*cosarg
|
||||||
|
de += -609*cosarg + 13*sinarg
|
||||||
|
|
||||||
|
arg = math.Mod(-1*L+2*F+Om, twopi)
|
||||||
|
sinarg = math.Sin(arg)
|
||||||
|
cosarg = math.Cos(arg)
|
||||||
|
dp += (20441+21*T)*sinarg + 10*cosarg
|
||||||
|
de += -10758*cosarg + -3*sinarg
|
||||||
|
|
||||||
|
arg = math.Mod(L+2*F+-2*D+2*Om, twopi)
|
||||||
|
sinarg = math.Sin(arg)
|
||||||
|
cosarg = math.Cos(arg)
|
||||||
|
dp += 28593*sinarg + -1*cosarg
|
||||||
|
de += (-12338+10*T)*cosarg + -3*sinarg
|
||||||
|
|
||||||
|
arg = math.Mod(2*L+2*F+2*Om, twopi)
|
||||||
|
sinarg = math.Sin(arg)
|
||||||
|
cosarg = math.Cos(arg)
|
||||||
|
dp += (-31046+-1*T)*sinarg + 131*cosarg
|
||||||
|
de += (13238+-11*T)*cosarg + 59*sinarg
|
||||||
|
|
||||||
|
arg = math.Mod(-2*L+2*D, twopi)
|
||||||
|
sinarg = math.Sin(arg)
|
||||||
|
cosarg = math.Cos(arg)
|
||||||
|
dp += -47722*sinarg + -18*cosarg
|
||||||
|
de += 477*cosarg + -25*sinarg
|
||||||
|
|
||||||
|
arg = math.Mod(-2*Lp+2*F+-2*D+2*Om, twopi)
|
||||||
|
sinarg = math.Sin(arg)
|
||||||
|
cosarg = math.Cos(arg)
|
||||||
|
dp += 32481 * sinarg
|
||||||
|
de += -13870 * cosarg
|
||||||
|
|
||||||
|
arg = math.Mod(2*F+2*D+2*Om, twopi)
|
||||||
|
sinarg = math.Sin(arg)
|
||||||
|
cosarg = math.Cos(arg)
|
||||||
|
dp += (-38571+-1*T)*sinarg + 158*cosarg
|
||||||
|
de += (16452+-11*T)*cosarg + 68*sinarg
|
||||||
|
|
||||||
|
arg = math.Mod(2*D, twopi)
|
||||||
|
sinarg = math.Sin(arg)
|
||||||
|
cosarg = math.Cos(arg)
|
||||||
|
dp += (63384+11*T)*sinarg + -150*cosarg
|
||||||
|
de += -1220*cosarg + 29*sinarg
|
||||||
|
|
||||||
|
arg = math.Mod(-2*L+2*F+Om, twopi)
|
||||||
|
sinarg = math.Sin(arg)
|
||||||
|
cosarg = math.Cos(arg)
|
||||||
|
dp += (45893+50*T)*sinarg + 31*cosarg
|
||||||
|
de += (-24236+-10*T)*cosarg + 20*sinarg
|
||||||
|
|
||||||
|
arg = math.Mod(L+2*F+Om, twopi)
|
||||||
|
sinarg = math.Sin(arg)
|
||||||
|
cosarg = math.Cos(arg)
|
||||||
|
dp += (-51613+-42*T)*sinarg + 129*cosarg
|
||||||
|
de += 26366*cosarg + 78*sinarg
|
||||||
|
|
||||||
|
arg = math.Mod(-1*L+2*F+2*D+2*Om, twopi)
|
||||||
|
sinarg = math.Sin(arg)
|
||||||
|
cosarg = math.Cos(arg)
|
||||||
|
dp += (-59641+-11*T)*sinarg + 149*cosarg
|
||||||
|
de += (25543+-11*T)*cosarg + 66*sinarg
|
||||||
|
|
||||||
|
arg = math.Mod(-1*L+Om, twopi)
|
||||||
|
sinarg = math.Sin(arg)
|
||||||
|
cosarg = math.Cos(arg)
|
||||||
|
dp += (-57976+-63*T)*sinarg + -189*cosarg
|
||||||
|
de += 31429*cosarg + -75*sinarg
|
||||||
|
|
||||||
|
arg = math.Mod(L+Om, twopi)
|
||||||
|
sinarg = math.Sin(arg)
|
||||||
|
cosarg = math.Cos(arg)
|
||||||
|
dp += (63110+63*T)*sinarg + 27*cosarg
|
||||||
|
de += -33228*cosarg + -9*sinarg
|
||||||
|
|
||||||
|
arg = math.Mod(-1*L+2*D, twopi)
|
||||||
|
sinarg = math.Sin(arg)
|
||||||
|
cosarg = math.Cos(arg)
|
||||||
|
dp += (156994+10*T)*sinarg + -168*cosarg
|
||||||
|
de += -1235*cosarg + 82*sinarg
|
||||||
|
|
||||||
|
arg = math.Mod(-1*L+2*F+2*Om, twopi)
|
||||||
|
sinarg = math.Sin(arg)
|
||||||
|
cosarg = math.Cos(arg)
|
||||||
|
dp += (123457+11*T)*sinarg + 19*cosarg
|
||||||
|
de += (-53311+32*T)*cosarg + -4*sinarg
|
||||||
|
|
||||||
|
arg = math.Mod(2*F+-2*D+Om, twopi)
|
||||||
|
sinarg = math.Sin(arg)
|
||||||
|
cosarg = math.Cos(arg)
|
||||||
|
dp += (128227+137*T)*sinarg + 181*cosarg
|
||||||
|
de += (-68982+-9*T)*cosarg + 39*sinarg
|
||||||
|
|
||||||
|
arg = math.Mod(-1*Lp+2*F+-2*D+2*Om, twopi)
|
||||||
|
sinarg = math.Sin(arg)
|
||||||
|
cosarg = math.Cos(arg)
|
||||||
|
dp += (215829+-494*T)*sinarg + 111*cosarg
|
||||||
|
de += (-95929+299*T)*cosarg + 132*sinarg
|
||||||
|
|
||||||
|
arg = math.Mod(L+2*F+2*Om, twopi)
|
||||||
|
sinarg = math.Sin(arg)
|
||||||
|
cosarg = math.Cos(arg)
|
||||||
|
dp += (-301461+-36*T)*sinarg + 816*cosarg
|
||||||
|
de += (129025+-63*T)*cosarg + 367*sinarg
|
||||||
|
|
||||||
|
arg = math.Mod(2*F+Om, twopi)
|
||||||
|
sinarg = math.Sin(arg)
|
||||||
|
cosarg = math.Cos(arg)
|
||||||
|
dp += (-387298+-367*T)*sinarg + 380*cosarg
|
||||||
|
de += (200728+18*T)*cosarg + 318*sinarg
|
||||||
|
|
||||||
|
arg = math.Mod(L, twopi)
|
||||||
|
sinarg = math.Sin(arg)
|
||||||
|
cosarg = math.Cos(arg)
|
||||||
|
dp += (711159+73*T)*sinarg + -872*cosarg
|
||||||
|
de += -6750*cosarg + 358*sinarg
|
||||||
|
|
||||||
|
arg = math.Mod(Lp+2*F+-2*D+2*Om, twopi)
|
||||||
|
sinarg = math.Sin(arg)
|
||||||
|
cosarg = math.Cos(arg)
|
||||||
|
dp += (-516821+1226*T)*sinarg + -524*cosarg
|
||||||
|
de += (224386+-677*T)*cosarg + -174*sinarg
|
||||||
|
|
||||||
|
arg = math.Mod(Lp, twopi)
|
||||||
|
sinarg = math.Sin(arg)
|
||||||
|
cosarg = math.Cos(arg)
|
||||||
|
dp += (1475877+-3633*T)*sinarg + 11817*cosarg
|
||||||
|
de += (73871+-184*T)*cosarg + -1924*sinarg
|
||||||
|
|
||||||
|
arg = math.Mod(2*Om, twopi)
|
||||||
|
sinarg = math.Sin(arg)
|
||||||
|
cosarg = math.Cos(arg)
|
||||||
|
dp += (2074554+207*T)*sinarg + -698*cosarg
|
||||||
|
de += (-897492+470*T)*cosarg + -291*sinarg
|
||||||
|
|
||||||
|
arg = math.Mod(2*F+2*Om, twopi)
|
||||||
|
sinarg = math.Sin(arg)
|
||||||
|
cosarg = math.Cos(arg)
|
||||||
|
dp += (-2276413+-234*T)*sinarg + 2796*cosarg
|
||||||
|
de += (978459+-485*T)*cosarg + 1374*sinarg
|
||||||
|
|
||||||
|
arg = math.Mod(2*F+-2*D+2*Om, twopi)
|
||||||
|
sinarg = math.Sin(arg)
|
||||||
|
cosarg = math.Cos(arg)
|
||||||
|
dp += (-13170906+-1675*T)*sinarg + -13696*cosarg
|
||||||
|
de += (5730336+-3015*T)*cosarg + -4587*sinarg
|
||||||
|
|
||||||
|
arg = math.Mod(Om, twopi)
|
||||||
|
sinarg = math.Sin(arg)
|
||||||
|
cosarg = math.Cos(arg)
|
||||||
|
dp += (-172064161+-174666*T)*sinarg + 33386*cosarg
|
||||||
|
de += (92052331+9086*T)*cosarg + 15377*sinarg
|
||||||
|
|
||||||
|
de *= as2r / 1e7
|
||||||
|
dp *= as2r / 1e7
|
||||||
|
|
||||||
|
// 行星章动修正
|
||||||
|
dp += -0.135 * (as2r / 1e3)
|
||||||
|
de += 0.388 * (as2r / 1e3)
|
||||||
|
|
||||||
|
return dp * deg, de * deg
|
||||||
|
}
|
||||||
@@ -0,0 +1,233 @@
|
|||||||
|
package basic
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math"
|
||||||
|
|
||||||
|
"b612.me/astro/planet"
|
||||||
|
. "b612.me/astro/tools"
|
||||||
|
)
|
||||||
|
|
||||||
|
var orbitJ2000Obliquity = EclipticObliquity(orbitReferenceJD, false)
|
||||||
|
|
||||||
|
// OrbitHeliocentricXYZJ2000 返回日心 J2000 平黄道直角坐标,单位 AU。
|
||||||
|
func OrbitHeliocentricXYZJ2000(jd float64, elements OrbitElements) Vector3 {
|
||||||
|
trueAnomaly, radius, resolved, ok := orbitTrueAnomalyAndRadius(jd, elements)
|
||||||
|
if !ok {
|
||||||
|
nan := math.NaN()
|
||||||
|
return Vector3{nan, nan, nan}
|
||||||
|
}
|
||||||
|
|
||||||
|
ascendingNode := resolved.Omega * rad
|
||||||
|
argumentLatitude := resolved.W*rad + trueAnomaly
|
||||||
|
inclination := resolved.I * rad
|
||||||
|
|
||||||
|
sinAscendingNode, cosAscendingNode := math.Sincos(ascendingNode)
|
||||||
|
sinArgumentLatitude, cosArgumentLatitude := math.Sincos(argumentLatitude)
|
||||||
|
sinInclination, cosInclination := math.Sincos(inclination)
|
||||||
|
|
||||||
|
return Vector3{
|
||||||
|
radius * (cosAscendingNode*cosArgumentLatitude - sinAscendingNode*sinArgumentLatitude*cosInclination),
|
||||||
|
radius * (sinAscendingNode*cosArgumentLatitude + cosAscendingNode*sinArgumentLatitude*cosInclination),
|
||||||
|
radius * sinArgumentLatitude * sinInclination,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// OrbitHeliocentricEclipticJ2000 返回日心 J2000 平黄道球坐标,单位度/AU。
|
||||||
|
func OrbitHeliocentricEclipticJ2000(jd float64, elements OrbitElements) (lon, lat, distance float64) {
|
||||||
|
return orbitVectorToEcliptic(OrbitHeliocentricXYZJ2000(jd, elements))
|
||||||
|
}
|
||||||
|
|
||||||
|
// OrbitHeliocentricXYZ 返回日心历元黄道直角坐标,单位 AU。
|
||||||
|
func OrbitHeliocentricXYZ(jd float64, elements OrbitElements) Vector3 {
|
||||||
|
return eclipticVectorAtReferenceEpoch(OrbitHeliocentricXYZJ2000(jd, elements), orbitReferenceJD, jd)
|
||||||
|
}
|
||||||
|
|
||||||
|
// OrbitHeliocentricEcliptic 返回日心历元黄道球坐标,单位度/AU。
|
||||||
|
func OrbitHeliocentricEcliptic(jd float64, elements OrbitElements) (lon, lat, distance float64) {
|
||||||
|
return orbitVectorToEcliptic(OrbitHeliocentricXYZ(jd, elements))
|
||||||
|
}
|
||||||
|
|
||||||
|
// OrbitGeocentricXYZJ2000 返回地心 J2000 平黄道直角坐标,单位 AU。
|
||||||
|
func OrbitGeocentricXYZJ2000(jd float64, elements OrbitElements) Vector3 {
|
||||||
|
objectVector := OrbitHeliocentricXYZJ2000(jd, elements)
|
||||||
|
earthVector := earthHeliocentricVectorJ2000(jd)
|
||||||
|
return Vector3{
|
||||||
|
objectVector[0] - earthVector[0],
|
||||||
|
objectVector[1] - earthVector[1],
|
||||||
|
objectVector[2] - earthVector[2],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// OrbitGeocentricEclipticJ2000 返回地心 J2000 平黄道球坐标,单位度/AU。
|
||||||
|
func OrbitGeocentricEclipticJ2000(jd float64, elements OrbitElements) (lon, lat, distance float64) {
|
||||||
|
return orbitVectorToEcliptic(OrbitGeocentricXYZJ2000(jd, elements))
|
||||||
|
}
|
||||||
|
|
||||||
|
// OrbitGeocentricXYZ 返回地心历元黄道直角坐标,单位 AU。
|
||||||
|
func OrbitGeocentricXYZ(jd float64, elements OrbitElements) Vector3 {
|
||||||
|
objectVector := OrbitHeliocentricXYZ(jd, elements)
|
||||||
|
earthVector := earthHeliocentricVectorOfDate(jd)
|
||||||
|
return Vector3{
|
||||||
|
objectVector[0] - earthVector[0],
|
||||||
|
objectVector[1] - earthVector[1],
|
||||||
|
objectVector[2] - earthVector[2],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// OrbitGeocentricEcliptic 返回地心历元黄道球坐标,单位度/AU。
|
||||||
|
func OrbitGeocentricEcliptic(jd float64, elements OrbitElements) (lon, lat, distance float64) {
|
||||||
|
return orbitVectorToEcliptic(OrbitGeocentricXYZ(jd, elements))
|
||||||
|
}
|
||||||
|
|
||||||
|
// OrbitGeocentricEquatorialJ2000 返回地心 J2000 平赤道球坐标,单位度/AU。
|
||||||
|
func OrbitGeocentricEquatorialJ2000(jd float64, elements OrbitElements) (ra, dec, distance float64) {
|
||||||
|
vector := rotateEclipticToEquatorial(OrbitGeocentricXYZJ2000(jd, elements), orbitJ2000Obliquity)
|
||||||
|
return orbitVectorToEquatorial(vector)
|
||||||
|
}
|
||||||
|
|
||||||
|
// OrbitGeocentricEquatorial 返回地心历元平赤道球坐标,单位度/AU。
|
||||||
|
func OrbitGeocentricEquatorial(jd float64, elements OrbitElements) (ra, dec, distance float64) {
|
||||||
|
vector := rotateEclipticToEquatorial(OrbitGeocentricXYZ(jd, elements), EclipticObliquity(jd, false))
|
||||||
|
return orbitVectorToEquatorial(vector)
|
||||||
|
}
|
||||||
|
|
||||||
|
// OrbitAstrometricGeocentricXYZJ2000 返回光行时修正后的地心 J2000 平黄道直角坐标,单位 AU。
|
||||||
|
func OrbitAstrometricGeocentricXYZJ2000(jd float64, elements OrbitElements) Vector3 {
|
||||||
|
if !isFinite(jd) {
|
||||||
|
nan := math.NaN()
|
||||||
|
return Vector3{nan, nan, nan}
|
||||||
|
}
|
||||||
|
earthVector := earthHeliocentricVectorJ2000(jd)
|
||||||
|
lightTime := 0.0
|
||||||
|
result := Vector3{}
|
||||||
|
for i := 0; i < 8; i++ {
|
||||||
|
objectVector := OrbitHeliocentricXYZJ2000(jd-lightTime, elements)
|
||||||
|
result = Vector3{
|
||||||
|
objectVector[0] - earthVector[0],
|
||||||
|
objectVector[1] - earthVector[1],
|
||||||
|
objectVector[2] - earthVector[2],
|
||||||
|
}
|
||||||
|
nextLightTime := lightTimeDaysPerAU * orbitVectorNorm(result)
|
||||||
|
if math.Abs(nextLightTime-lightTime) < 1e-12 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
lightTime = nextLightTime
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
// OrbitAstrometricGeocentricEquatorialJ2000 返回光行时修正后的地心 J2000 赤道坐标,单位度/AU。
|
||||||
|
func OrbitAstrometricGeocentricEquatorialJ2000(jd float64, elements OrbitElements) (ra, dec, distance float64) {
|
||||||
|
vector := rotateEclipticToEquatorial(OrbitAstrometricGeocentricXYZJ2000(jd, elements), orbitJ2000Obliquity)
|
||||||
|
return orbitVectorToEquatorial(vector)
|
||||||
|
}
|
||||||
|
|
||||||
|
// OrbitApparentGeocentricEcliptic 返回光行时与章动修正后的地心视黄道坐标,单位度/AU。
|
||||||
|
func OrbitApparentGeocentricEcliptic(jd float64, elements OrbitElements) (lon, lat, distance float64) {
|
||||||
|
vectorDate := eclipticVectorAtReferenceEpoch(OrbitAstrometricGeocentricXYZJ2000(jd, elements), orbitReferenceJD, jd)
|
||||||
|
lon, lat, distance = orbitVectorToEcliptic(vectorDate)
|
||||||
|
if math.IsNaN(lon) {
|
||||||
|
return math.NaN(), math.NaN(), math.NaN()
|
||||||
|
}
|
||||||
|
lon = Limit360(lon + Nutation2000Bi(jd))
|
||||||
|
return lon, lat, distance
|
||||||
|
}
|
||||||
|
|
||||||
|
// OrbitApparentGeocentricEquatorial 返回光行时与章动修正后的地心视赤道坐标,单位度/AU。
|
||||||
|
func OrbitApparentGeocentricEquatorial(jd float64, elements OrbitElements) (ra, dec, distance float64) {
|
||||||
|
lon, lat, distance := OrbitApparentGeocentricEcliptic(jd, elements)
|
||||||
|
if math.IsNaN(lon) {
|
||||||
|
return math.NaN(), math.NaN(), math.NaN()
|
||||||
|
}
|
||||||
|
ra, dec = LoBoToRaDec(jd, lon, lat)
|
||||||
|
return ra, dec, distance
|
||||||
|
}
|
||||||
|
|
||||||
|
// OrbitApparentTopocentricEquatorial 返回光行时、章动与站心修正后的视赤道坐标,单位度/AU。
|
||||||
|
func OrbitApparentTopocentricEquatorial(jd, observerLon, observerLat, observerHeight float64, elements OrbitElements) (ra, dec, distance float64) {
|
||||||
|
geocentricRA, geocentricDec, geocentricDistance := OrbitApparentGeocentricEquatorial(jd, elements)
|
||||||
|
if math.IsNaN(geocentricRA) {
|
||||||
|
return math.NaN(), math.NaN(), math.NaN()
|
||||||
|
}
|
||||||
|
geocentricVector := orbitEquatorialVector(geocentricRA, geocentricDec, geocentricDistance)
|
||||||
|
observerVector := orbitObserverEquatorialVectorOfDate(TD2UT(jd, false), observerLon, observerLat, observerHeight)
|
||||||
|
topocentricVector := Vector3{
|
||||||
|
geocentricVector[0] - observerVector[0],
|
||||||
|
geocentricVector[1] - observerVector[1],
|
||||||
|
geocentricVector[2] - observerVector[2],
|
||||||
|
}
|
||||||
|
return orbitVectorToEquatorial(topocentricVector)
|
||||||
|
}
|
||||||
|
|
||||||
|
func earthHeliocentricVectorOfDate(jd float64) Vector3 {
|
||||||
|
return eclipticCartesian(
|
||||||
|
planet.WherePlanet(-1, 0, jd),
|
||||||
|
planet.WherePlanet(-1, 1, jd),
|
||||||
|
planet.WherePlanet(-1, 2, jd),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func earthHeliocentricVectorJ2000(jd float64) Vector3 {
|
||||||
|
return eclipticVectorAtReferenceEpoch(earthHeliocentricVectorOfDate(jd), jd, orbitReferenceJD)
|
||||||
|
}
|
||||||
|
|
||||||
|
func orbitVectorToEcliptic(vector Vector3) (lon, lat, distance float64) {
|
||||||
|
distance = orbitVectorNorm(vector)
|
||||||
|
if math.IsNaN(distance) || math.IsInf(distance, 0) {
|
||||||
|
return math.NaN(), math.NaN(), math.NaN()
|
||||||
|
}
|
||||||
|
if distance == 0 {
|
||||||
|
return 0, 0, 0
|
||||||
|
}
|
||||||
|
lon = Limit360(math.Atan2(vector[1], vector[0]) * deg)
|
||||||
|
lat = math.Asin(orbitClampUnit(vector[2]/distance)) * deg
|
||||||
|
return lon, lat, distance
|
||||||
|
}
|
||||||
|
|
||||||
|
func orbitVectorToEquatorial(vector Vector3) (ra, dec, distance float64) {
|
||||||
|
distance = orbitVectorNorm(vector)
|
||||||
|
if math.IsNaN(distance) || math.IsInf(distance, 0) {
|
||||||
|
return math.NaN(), math.NaN(), math.NaN()
|
||||||
|
}
|
||||||
|
if distance == 0 {
|
||||||
|
return 0, 0, 0
|
||||||
|
}
|
||||||
|
ra = Limit360(math.Atan2(vector[1], vector[0]) * deg)
|
||||||
|
dec = math.Asin(orbitClampUnit(vector[2]/distance)) * deg
|
||||||
|
return ra, dec, distance
|
||||||
|
}
|
||||||
|
|
||||||
|
func orbitEquatorialVector(ra, dec, distance float64) Vector3 {
|
||||||
|
cosDec := Cos(dec)
|
||||||
|
return Vector3{
|
||||||
|
distance * cosDec * Cos(ra),
|
||||||
|
distance * cosDec * Sin(ra),
|
||||||
|
distance * Sin(dec),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func orbitObserverEquatorialVectorOfDate(jdUT, observerLon, observerLat, observerHeight float64) Vector3 {
|
||||||
|
localApparentSiderealLongitude := Limit360(ApparentSiderealTime(jdUT)*15 + observerLon)
|
||||||
|
observerScaleAU := Sin(0.0024427777777)
|
||||||
|
rhoCosPhiPrime := pcosi(observerLat, observerHeight)
|
||||||
|
rhoSinPhiPrime := psini(observerLat, observerHeight)
|
||||||
|
return Vector3{
|
||||||
|
observerScaleAU * rhoCosPhiPrime * Cos(localApparentSiderealLongitude),
|
||||||
|
observerScaleAU * rhoCosPhiPrime * Sin(localApparentSiderealLongitude),
|
||||||
|
observerScaleAU * rhoSinPhiPrime,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func orbitVectorNorm(vector Vector3) float64 {
|
||||||
|
return math.Sqrt(vector[0]*vector[0] + vector[1]*vector[1] + vector[2]*vector[2])
|
||||||
|
}
|
||||||
|
|
||||||
|
func orbitClampUnit(value float64) float64 {
|
||||||
|
if value > 1 {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
if value < -1 {
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
return value
|
||||||
|
}
|
||||||
@@ -0,0 +1,96 @@
|
|||||||
|
package basic
|
||||||
|
|
||||||
|
import "math"
|
||||||
|
|
||||||
|
const (
|
||||||
|
orbitReferenceJD = 2451545.0
|
||||||
|
gaussianGravitationalConstant = 0.01720209895 // rad/day
|
||||||
|
lightTimeDaysPerAU = 0.0057755183
|
||||||
|
orbitParabolicTolerance = 1e-12
|
||||||
|
)
|
||||||
|
|
||||||
|
// OrbitElements 日心二体圆锥曲线根数,参考系为 J2000 平黄道/平春分点。
|
||||||
|
// EpochJD 与 TpJD 使用 TT/TDB 对应的儒略日。
|
||||||
|
//
|
||||||
|
// 两种输入形式:
|
||||||
|
// 1. 椭圆经典根数:A/E/I/Omega/W/M0(原有形式)
|
||||||
|
// 2. 近日点形式:Q/E/I/Omega/W/TpJD,用于高偏心、抛物或双曲轨道
|
||||||
|
//
|
||||||
|
// 线性 rates 仅作用于经典根数形式,单位均为“每天变化量”。
|
||||||
|
type OrbitElements struct {
|
||||||
|
EpochJD float64
|
||||||
|
A float64 // 半长径 / semi-major axis in AU.
|
||||||
|
E float64 // 离心率 / eccentricity.
|
||||||
|
I float64 // 轨道倾角 / inclination in degrees.
|
||||||
|
Omega float64 // 升交点黄经 / longitude of ascending node in degrees.
|
||||||
|
W float64 // 近日点幅角 / argument of perihelion in degrees.
|
||||||
|
M0 float64 // 历元平近点角 / mean anomaly at epoch in degrees.
|
||||||
|
Q float64 // 近日点距离 / perihelion distance in AU.
|
||||||
|
TpJD float64 // 近日点通过时刻 / perihelion passage TT/TDB Julian day.
|
||||||
|
|
||||||
|
ADot float64 // 半长径日变化 / daily rate of A in AU/day.
|
||||||
|
EDot float64 // 离心率日变化 / daily rate of E per day.
|
||||||
|
IDot float64 // 倾角日变化 / daily rate of I in deg/day.
|
||||||
|
OmegaDot float64 // 升交点黄经日变化 / daily rate of Omega in deg/day.
|
||||||
|
WDot float64 // 近日点幅角日变化 / daily rate of W in deg/day.
|
||||||
|
MDot float64 // 平近点角日变化 / daily rate of M in deg/day.
|
||||||
|
}
|
||||||
|
|
||||||
|
func (elements OrbitElements) usesPerihelionForm() bool {
|
||||||
|
return isFinitePositive(elements.Q) && isFinite(elements.TpJD)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (elements OrbitElements) validOrientation() bool {
|
||||||
|
angles := [...]float64{elements.I, elements.Omega, elements.W}
|
||||||
|
for _, angle := range angles {
|
||||||
|
if !isFinite(angle) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (elements OrbitElements) validEllipticClassical() bool {
|
||||||
|
if !isFinite(elements.EpochJD) || !isFinitePositive(elements.A) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if !isFinite(elements.E) || elements.E < 0 || elements.E >= 1 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if !isFinite(elements.M0) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return elements.validOrientation()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (elements OrbitElements) validPerihelionForm() bool {
|
||||||
|
if !elements.usesPerihelionForm() {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if !isFinite(elements.E) || elements.E < 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return elements.validOrientation()
|
||||||
|
}
|
||||||
|
|
||||||
|
func orbitElementsAt(jd float64, elements OrbitElements) OrbitElements {
|
||||||
|
if elements.usesPerihelionForm() || !isFinite(jd) || !isFinite(elements.EpochJD) {
|
||||||
|
return elements
|
||||||
|
}
|
||||||
|
deltaDays := jd - elements.EpochJD
|
||||||
|
updated := elements
|
||||||
|
updated.A += updated.ADot * deltaDays
|
||||||
|
updated.E += updated.EDot * deltaDays
|
||||||
|
updated.I += updated.IDot * deltaDays
|
||||||
|
updated.Omega += updated.OmegaDot * deltaDays
|
||||||
|
updated.W += updated.WDot * deltaDays
|
||||||
|
return updated
|
||||||
|
}
|
||||||
|
|
||||||
|
func isFinite(value float64) bool {
|
||||||
|
return !(math.IsNaN(value) || math.IsInf(value, 0))
|
||||||
|
}
|
||||||
|
|
||||||
|
func isFinitePositive(value float64) bool {
|
||||||
|
return isFinite(value) && value > 0
|
||||||
|
}
|
||||||
@@ -0,0 +1,238 @@
|
|||||||
|
package basic
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math"
|
||||||
|
|
||||||
|
. "b612.me/astro/tools"
|
||||||
|
)
|
||||||
|
|
||||||
|
// OrbitMeanMotion 返回历元平均角速度,单位度/日。
|
||||||
|
// 对近日点形式的抛物/双曲轨道返回 NaN。
|
||||||
|
func OrbitMeanMotion(elements OrbitElements) float64 {
|
||||||
|
if elements.usesPerihelionForm() {
|
||||||
|
if !elements.validPerihelionForm() || elements.E >= 1 {
|
||||||
|
return math.NaN()
|
||||||
|
}
|
||||||
|
semiMajorAxis := elements.Q / (1 - elements.E)
|
||||||
|
if !isFinitePositive(semiMajorAxis) {
|
||||||
|
return math.NaN()
|
||||||
|
}
|
||||||
|
if isFinite(elements.MDot) && elements.MDot != 0 {
|
||||||
|
return elements.MDot
|
||||||
|
}
|
||||||
|
return gaussianGravitationalConstant / math.Pow(semiMajorAxis, 1.5) * deg
|
||||||
|
}
|
||||||
|
if !elements.validEllipticClassical() {
|
||||||
|
return math.NaN()
|
||||||
|
}
|
||||||
|
if isFinite(elements.MDot) && elements.MDot != 0 {
|
||||||
|
return elements.MDot
|
||||||
|
}
|
||||||
|
return gaussianGravitationalConstant / math.Pow(elements.A, 1.5) * deg
|
||||||
|
}
|
||||||
|
|
||||||
|
// OrbitMeanAnomaly 返回给定 TT/TDB 儒略日的平近点角,单位度。
|
||||||
|
// 对抛物/双曲轨道返回 NaN。
|
||||||
|
func OrbitMeanAnomaly(jd float64, elements OrbitElements) float64 {
|
||||||
|
meanAnomalyDeg, ok := orbitMeanAnomalyDegAt(jd, elements)
|
||||||
|
if !ok {
|
||||||
|
return math.NaN()
|
||||||
|
}
|
||||||
|
return Limit360(meanAnomalyDeg)
|
||||||
|
}
|
||||||
|
|
||||||
|
// OrbitEccentricAnomaly 返回给定 TT/TDB 儒略日的偏近点角,单位度。
|
||||||
|
// 仅适用于椭圆轨道;对抛物/双曲轨道返回 NaN。
|
||||||
|
func OrbitEccentricAnomaly(jd float64, elements OrbitElements) float64 {
|
||||||
|
resolved := orbitElementsAt(jd, elements)
|
||||||
|
meanAnomalyDeg, ok := orbitMeanAnomalyDegAt(jd, elements)
|
||||||
|
if !ok || !resolved.validEllipticClassical() && !(resolved.validPerihelionForm() && resolved.E < 1) {
|
||||||
|
return math.NaN()
|
||||||
|
}
|
||||||
|
eccentricAnomaly, ok := orbitEccentricAnomalyRad(meanAnomalyDeg*rad, resolved.E)
|
||||||
|
if !ok {
|
||||||
|
return math.NaN()
|
||||||
|
}
|
||||||
|
return Limit360(eccentricAnomaly * deg)
|
||||||
|
}
|
||||||
|
|
||||||
|
// OrbitTrueAnomaly 返回给定 TT/TDB 儒略日的真近点角,单位度。
|
||||||
|
func OrbitTrueAnomaly(jd float64, elements OrbitElements) float64 {
|
||||||
|
trueAnomaly, _, _, ok := orbitTrueAnomalyAndRadius(jd, elements)
|
||||||
|
if !ok {
|
||||||
|
return math.NaN()
|
||||||
|
}
|
||||||
|
return Limit360(trueAnomaly * deg)
|
||||||
|
}
|
||||||
|
|
||||||
|
func orbitMeanAnomalyDegAt(jd float64, elements OrbitElements) (float64, bool) {
|
||||||
|
if !isFinite(jd) {
|
||||||
|
return math.NaN(), false
|
||||||
|
}
|
||||||
|
if elements.usesPerihelionForm() {
|
||||||
|
if !elements.validPerihelionForm() || elements.E >= 1 {
|
||||||
|
return math.NaN(), false
|
||||||
|
}
|
||||||
|
semiMajorAxis := elements.Q / (1 - elements.E)
|
||||||
|
if !isFinitePositive(semiMajorAxis) {
|
||||||
|
return math.NaN(), false
|
||||||
|
}
|
||||||
|
meanMotion := elements.MDot
|
||||||
|
if !isFinite(meanMotion) || meanMotion == 0 {
|
||||||
|
meanMotion = gaussianGravitationalConstant / math.Pow(semiMajorAxis, 1.5) * deg
|
||||||
|
}
|
||||||
|
return meanMotion * (jd - elements.TpJD), true
|
||||||
|
}
|
||||||
|
|
||||||
|
resolved := orbitElementsAt(jd, elements)
|
||||||
|
if !resolved.validEllipticClassical() {
|
||||||
|
return math.NaN(), false
|
||||||
|
}
|
||||||
|
if isFinite(elements.MDot) && elements.MDot != 0 {
|
||||||
|
return elements.M0 + elements.MDot*(jd-elements.EpochJD), true
|
||||||
|
}
|
||||||
|
meanMotion := gaussianGravitationalConstant / math.Pow(resolved.A, 1.5) * deg
|
||||||
|
return resolved.M0 + meanMotion*(jd-elements.EpochJD), true
|
||||||
|
}
|
||||||
|
|
||||||
|
func orbitEccentricAnomalyRad(meanAnomalyRad, eccentricity float64) (float64, bool) {
|
||||||
|
if !isFinite(meanAnomalyRad) || !isFinite(eccentricity) || eccentricity < 0 || eccentricity >= 1 {
|
||||||
|
return math.NaN(), false
|
||||||
|
}
|
||||||
|
if meanAnomalyRad > math.Pi {
|
||||||
|
meanAnomalyRad -= 2 * math.Pi
|
||||||
|
} else if meanAnomalyRad < -math.Pi {
|
||||||
|
meanAnomalyRad += 2 * math.Pi
|
||||||
|
}
|
||||||
|
|
||||||
|
eccentricAnomaly := meanAnomalyRad
|
||||||
|
if eccentricity >= 0.8 {
|
||||||
|
eccentricAnomaly = math.Pi
|
||||||
|
if meanAnomalyRad < 0 {
|
||||||
|
eccentricAnomaly = -math.Pi
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i < 32; i++ {
|
||||||
|
sinE, cosE := math.Sincos(eccentricAnomaly)
|
||||||
|
delta := (eccentricAnomaly - eccentricity*sinE - meanAnomalyRad) / (1 - eccentricity*cosE)
|
||||||
|
eccentricAnomaly -= delta
|
||||||
|
if math.Abs(delta) < 1e-14 {
|
||||||
|
return eccentricAnomaly, true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return eccentricAnomaly, true
|
||||||
|
}
|
||||||
|
|
||||||
|
func orbitHyperbolicAnomaly(meanAnomaly, eccentricity float64) (float64, bool) {
|
||||||
|
if !isFinite(meanAnomaly) || !isFinite(eccentricity) || eccentricity <= 1 {
|
||||||
|
return math.NaN(), false
|
||||||
|
}
|
||||||
|
|
||||||
|
hyperbolicAnomaly := math.Asinh(meanAnomaly / eccentricity)
|
||||||
|
if hyperbolicAnomaly == 0 && meanAnomaly != 0 {
|
||||||
|
hyperbolicAnomaly = math.Log(2*math.Abs(meanAnomaly)/eccentricity + 1.8)
|
||||||
|
if meanAnomaly < 0 {
|
||||||
|
hyperbolicAnomaly = -hyperbolicAnomaly
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i < 32; i++ {
|
||||||
|
sinhH := math.Sinh(hyperbolicAnomaly)
|
||||||
|
coshH := math.Cosh(hyperbolicAnomaly)
|
||||||
|
delta := (eccentricity*sinhH - hyperbolicAnomaly - meanAnomaly) / (eccentricity*coshH - 1)
|
||||||
|
hyperbolicAnomaly -= delta
|
||||||
|
if math.Abs(delta) < 1e-14 {
|
||||||
|
return hyperbolicAnomaly, true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return hyperbolicAnomaly, true
|
||||||
|
}
|
||||||
|
|
||||||
|
func orbitTrueAnomalyAndRadius(jd float64, elements OrbitElements) (trueAnomaly, radius float64, resolved OrbitElements, ok bool) {
|
||||||
|
resolved = orbitElementsAt(jd, elements)
|
||||||
|
if resolved.usesPerihelionForm() {
|
||||||
|
if !resolved.validPerihelionForm() {
|
||||||
|
return math.NaN(), math.NaN(), resolved, false
|
||||||
|
}
|
||||||
|
switch {
|
||||||
|
case math.Abs(resolved.E-1) <= orbitParabolicTolerance:
|
||||||
|
return orbitParabolicTrueAnomalyAndRadius(jd, resolved)
|
||||||
|
case resolved.E < 1:
|
||||||
|
return orbitEllipticTrueAnomalyAndRadiusFromPerihelion(jd, resolved)
|
||||||
|
default:
|
||||||
|
return orbitHyperbolicTrueAnomalyAndRadius(jd, resolved)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !resolved.validEllipticClassical() {
|
||||||
|
return math.NaN(), math.NaN(), resolved, false
|
||||||
|
}
|
||||||
|
meanAnomalyDeg, ok := orbitMeanAnomalyDegAt(jd, elements)
|
||||||
|
if !ok {
|
||||||
|
return math.NaN(), math.NaN(), resolved, false
|
||||||
|
}
|
||||||
|
trueAnomaly, radius, ok = orbitEllipticTrueAnomalyAndRadius(meanAnomalyDeg*rad, resolved.A, resolved.E)
|
||||||
|
return trueAnomaly, radius, resolved, ok
|
||||||
|
}
|
||||||
|
|
||||||
|
func orbitEllipticTrueAnomalyAndRadiusFromPerihelion(jd float64, elements OrbitElements) (trueAnomaly, radius float64, resolved OrbitElements, ok bool) {
|
||||||
|
semiMajorAxis := elements.Q / (1 - elements.E)
|
||||||
|
if !isFinitePositive(semiMajorAxis) {
|
||||||
|
return math.NaN(), math.NaN(), elements, false
|
||||||
|
}
|
||||||
|
meanAnomalyDeg, ok := orbitMeanAnomalyDegAt(jd, elements)
|
||||||
|
if !ok {
|
||||||
|
return math.NaN(), math.NaN(), elements, false
|
||||||
|
}
|
||||||
|
trueAnomaly, radius, ok = orbitEllipticTrueAnomalyAndRadius(meanAnomalyDeg*rad, semiMajorAxis, elements.E)
|
||||||
|
if !ok {
|
||||||
|
return math.NaN(), math.NaN(), elements, false
|
||||||
|
}
|
||||||
|
resolved = elements
|
||||||
|
resolved.A = semiMajorAxis
|
||||||
|
return trueAnomaly, radius, resolved, true
|
||||||
|
}
|
||||||
|
|
||||||
|
func orbitEllipticTrueAnomalyAndRadius(meanAnomalyRad, semiMajorAxis, eccentricity float64) (float64, float64, bool) {
|
||||||
|
eccentricAnomaly, ok := orbitEccentricAnomalyRad(meanAnomalyRad, eccentricity)
|
||||||
|
if !ok {
|
||||||
|
return math.NaN(), math.NaN(), false
|
||||||
|
}
|
||||||
|
sinE, cosE := math.Sincos(eccentricAnomaly)
|
||||||
|
radius := semiMajorAxis * (1 - eccentricity*cosE)
|
||||||
|
trueAnomaly := math.Atan2(math.Sqrt(1-eccentricity*eccentricity)*sinE, cosE-eccentricity)
|
||||||
|
return trueAnomaly, radius, true
|
||||||
|
}
|
||||||
|
|
||||||
|
func orbitParabolicTrueAnomalyAndRadius(jd float64, elements OrbitElements) (trueAnomaly, radius float64, resolved OrbitElements, ok bool) {
|
||||||
|
if !isFinitePositive(elements.Q) || !isFinite(elements.TpJD) {
|
||||||
|
return math.NaN(), math.NaN(), elements, false
|
||||||
|
}
|
||||||
|
w := 1.5 * gaussianGravitationalConstant * (jd - elements.TpJD) / (math.Sqrt2 * math.Pow(elements.Q, 1.5))
|
||||||
|
y := math.Cbrt(w + math.Sqrt(w*w+1))
|
||||||
|
if y == 0 {
|
||||||
|
return 0, elements.Q, elements, true
|
||||||
|
}
|
||||||
|
d := y - 1/y
|
||||||
|
trueAnomaly = 2 * math.Atan(d)
|
||||||
|
radius = elements.Q * (1 + d*d)
|
||||||
|
resolved = elements
|
||||||
|
return trueAnomaly, radius, resolved, true
|
||||||
|
}
|
||||||
|
|
||||||
|
func orbitHyperbolicTrueAnomalyAndRadius(jd float64, elements OrbitElements) (trueAnomaly, radius float64, resolved OrbitElements, ok bool) {
|
||||||
|
if !isFinitePositive(elements.Q) || !isFinite(elements.TpJD) || !isFinite(elements.E) || elements.E <= 1 {
|
||||||
|
return math.NaN(), math.NaN(), elements, false
|
||||||
|
}
|
||||||
|
semiMajorAxis := elements.Q / (elements.E - 1)
|
||||||
|
meanAnomaly := gaussianGravitationalConstant * (jd - elements.TpJD) / math.Pow(semiMajorAxis, 1.5)
|
||||||
|
hyperbolicAnomaly, ok := orbitHyperbolicAnomaly(meanAnomaly, elements.E)
|
||||||
|
if !ok {
|
||||||
|
return math.NaN(), math.NaN(), elements, false
|
||||||
|
}
|
||||||
|
radius = semiMajorAxis * (elements.E*math.Cosh(hyperbolicAnomaly) - 1)
|
||||||
|
trueAnomaly = 2 * math.Atan(math.Sqrt((elements.E+1)/(elements.E-1))*math.Tanh(hyperbolicAnomaly/2))
|
||||||
|
resolved = elements
|
||||||
|
resolved.A = -semiMajorAxis
|
||||||
|
return trueAnomaly, radius, resolved, true
|
||||||
|
}
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
package basic
|
||||||
|
|
||||||
|
import "math"
|
||||||
|
|
||||||
|
// OrbitAsteroidMagnitudeHG 返回小行星 H-G 模型的视星等。
|
||||||
|
func OrbitAsteroidMagnitudeHG(jd float64, elements OrbitElements, absoluteMagnitude, slopeParameter float64) float64 {
|
||||||
|
if !isFinite(jd) || !isFinite(absoluteMagnitude) || !isFinite(slopeParameter) {
|
||||||
|
return math.NaN()
|
||||||
|
}
|
||||||
|
|
||||||
|
sunDistance := OrbitSunDistance(jd, elements)
|
||||||
|
earthDistance := OrbitEarthDistance(jd, elements)
|
||||||
|
phaseAngle := OrbitPhaseAngle(jd, elements)
|
||||||
|
if !isFinitePositive(sunDistance) || !isFinitePositive(earthDistance) || !isFinite(phaseAngle) {
|
||||||
|
return math.NaN()
|
||||||
|
}
|
||||||
|
|
||||||
|
phaseBlend := orbitHGSlopeBlend(phaseAngle, slopeParameter)
|
||||||
|
if phaseBlend == 0 {
|
||||||
|
return math.Inf(1)
|
||||||
|
}
|
||||||
|
if !isFinitePositive(phaseBlend) {
|
||||||
|
return math.NaN()
|
||||||
|
}
|
||||||
|
|
||||||
|
return absoluteMagnitude + 5*math.Log10(sunDistance*earthDistance) - 2.5*math.Log10(phaseBlend)
|
||||||
|
}
|
||||||
|
|
||||||
|
func orbitHGSlopeBlend(phaseAngle, slopeParameter float64) float64 {
|
||||||
|
phi1 := orbitHGPhaseFunction1(phaseAngle)
|
||||||
|
phi2 := orbitHGPhaseFunction2(phaseAngle)
|
||||||
|
return (1-slopeParameter)*phi1 + slopeParameter*phi2
|
||||||
|
}
|
||||||
|
|
||||||
|
func orbitHGPhaseFunction1(phaseAngle float64) float64 {
|
||||||
|
return math.Exp(-3.33 * math.Pow(orbitHGTanHalfPhaseAngle(phaseAngle), 0.63))
|
||||||
|
}
|
||||||
|
|
||||||
|
func orbitHGPhaseFunction2(phaseAngle float64) float64 {
|
||||||
|
return math.Exp(-1.87 * math.Pow(orbitHGTanHalfPhaseAngle(phaseAngle), 1.22))
|
||||||
|
}
|
||||||
|
|
||||||
|
func orbitHGTanHalfPhaseAngle(phaseAngle float64) float64 {
|
||||||
|
return math.Tan((phaseAngle * math.Pi / 180) / 2)
|
||||||
|
}
|
||||||
@@ -0,0 +1,136 @@
|
|||||||
|
package basic
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math"
|
||||||
|
|
||||||
|
. "b612.me/astro/tools"
|
||||||
|
)
|
||||||
|
|
||||||
|
func orbitTopocentricObservation(jde, observerLon, observerLat, observerHeight, timezone float64, elements OrbitElements) (ra, dec, distance float64) {
|
||||||
|
utcJde := jde - timezone/24.0
|
||||||
|
return OrbitApparentTopocentricEquatorial(TD2UT(utcJde, true), observerLon, observerLat, observerHeight, elements)
|
||||||
|
}
|
||||||
|
|
||||||
|
// OrbitHeight 返回轨道目标在观测者所在地的视高度角,单位度。
|
||||||
|
func OrbitHeight(jde, observerLon, observerLat, timezone, observerHeight float64, elements OrbitElements) float64 {
|
||||||
|
ra, dec, _ := orbitTopocentricObservation(jde, observerLon, observerLat, observerHeight, timezone, elements)
|
||||||
|
st := Limit360(ApparentSiderealTime(jde-timezone/24.0)*15 + observerLon)
|
||||||
|
hourAngle := Limit360(st - ra)
|
||||||
|
sinHeight := Sin(observerLat)*Sin(dec) + Cos(dec)*Cos(observerLat)*Cos(hourAngle)
|
||||||
|
return ArcSin(sinHeight)
|
||||||
|
}
|
||||||
|
|
||||||
|
// OrbitAzimuth 返回轨道目标在观测者所在地的视方位角,按正北为 0°、向东增加。
|
||||||
|
func OrbitAzimuth(jde, observerLon, observerLat, timezone, observerHeight float64, elements OrbitElements) float64 {
|
||||||
|
ra, dec, _ := orbitTopocentricObservation(jde, observerLon, observerLat, observerHeight, timezone, elements)
|
||||||
|
st := Limit360(ApparentSiderealTime(jde-timezone/24.0)*15 + observerLon)
|
||||||
|
hourAngle := Limit360(st - ra)
|
||||||
|
tanAzimuth := Sin(hourAngle) / (Cos(hourAngle)*Sin(observerLat) - Tan(dec)*Cos(observerLat))
|
||||||
|
azimuth := ArcTan(tanAzimuth)
|
||||||
|
if azimuth < 0 {
|
||||||
|
if hourAngle/15 < 12 {
|
||||||
|
return azimuth + 360
|
||||||
|
}
|
||||||
|
return azimuth + 180
|
||||||
|
}
|
||||||
|
if hourAngle/15 < 12 {
|
||||||
|
return azimuth + 180
|
||||||
|
}
|
||||||
|
return azimuth
|
||||||
|
}
|
||||||
|
|
||||||
|
// OrbitHourAngle 返回轨道目标的站心视时角,单位度。
|
||||||
|
func OrbitHourAngle(jde, observerLon, observerLat, timezone, observerHeight float64, elements OrbitElements) float64 {
|
||||||
|
ra, _, _ := orbitTopocentricObservation(jde, observerLon, observerLat, observerHeight, timezone, elements)
|
||||||
|
st := Limit360(ApparentSiderealTime(jde-timezone/24.0)*15 + observerLon)
|
||||||
|
hourAngle := st - ra
|
||||||
|
if hourAngle < 0 {
|
||||||
|
hourAngle += 360
|
||||||
|
}
|
||||||
|
return hourAngle
|
||||||
|
}
|
||||||
|
|
||||||
|
// OrbitCulminationTime 返回轨道目标的中天时刻,输入输出均沿用本仓库现有观测函数的 JD 语义。
|
||||||
|
func OrbitCulminationTime(jde, observerLon, observerLat, timezone, observerHeight float64, elements OrbitElements) float64 {
|
||||||
|
jde = math.Floor(jde) + 0.5
|
||||||
|
estimateJD := jde + Limit360(360-OrbitHourAngle(jde, observerLon, observerLat, timezone, observerHeight, elements))/15.0/24.0*0.99726851851851851851
|
||||||
|
normalizedHourAngle := func(jde float64) float64 {
|
||||||
|
currentHourAngle := OrbitHourAngle(jde, observerLon, observerLat, timezone, observerHeight, elements)
|
||||||
|
if currentHourAngle < 180 {
|
||||||
|
currentHourAngle += 360
|
||||||
|
}
|
||||||
|
return currentHourAngle
|
||||||
|
}
|
||||||
|
for {
|
||||||
|
prevJD := estimateJD
|
||||||
|
hourAngleDelta := normalizedHourAngle(prevJD) - 360
|
||||||
|
hourAngleSlope := (normalizedHourAngle(prevJD+0.000005) - normalizedHourAngle(prevJD-0.000005)) / 0.00001
|
||||||
|
estimateJD = prevJD - hourAngleDelta/hourAngleSlope
|
||||||
|
if math.Abs(estimateJD-prevJD) <= 0.00001 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return estimateJD
|
||||||
|
}
|
||||||
|
|
||||||
|
// OrbitRiseTime 返回轨道目标在给定当地日期的升起时刻。
|
||||||
|
func OrbitRiseTime(jde, observerLon, observerLat, timezone, aeroCorrection, observerHeight float64, elements OrbitElements) (float64, error) {
|
||||||
|
return orbitRiseDown(jde, observerLon, observerLat, timezone, aeroCorrection, observerHeight, elements, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
// OrbitSetTime 返回轨道目标在给定当地日期的落下时刻。
|
||||||
|
func OrbitSetTime(jde, observerLon, observerLat, timezone, aeroCorrection, observerHeight float64, elements OrbitElements) (float64, error) {
|
||||||
|
return orbitRiseDown(jde, observerLon, observerLat, timezone, aeroCorrection, observerHeight, elements, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
func orbitRiseDown(jde, observerLon, observerLat, timezone, aeroCorrection, observerHeight float64, elements OrbitElements, isRise bool) (float64, error) {
|
||||||
|
localTimezone := math.Round(observerLon / 15)
|
||||||
|
targetAltitude := StandardAltitudePlanet(aeroCorrection, observerHeight, observerLat)
|
||||||
|
|
||||||
|
culminationJD := OrbitCulminationTime(jde, observerLon, observerLat, localTimezone, observerHeight, elements)
|
||||||
|
if OrbitHeight(culminationJD, observerLon, observerLat, localTimezone, observerHeight, elements) < targetAltitude {
|
||||||
|
return 0, ErrNeverRise
|
||||||
|
}
|
||||||
|
if OrbitHeight(culminationJD-0.5, observerLon, observerLat, localTimezone, observerHeight, elements) > targetAltitude {
|
||||||
|
return 0, ErrNeverSet
|
||||||
|
}
|
||||||
|
|
||||||
|
_, dec, _ := orbitTopocentricObservation(culminationJD, observerLon, observerLat, observerHeight, localTimezone, elements)
|
||||||
|
cosHourAngle := (Sin(targetAltitude) - Sin(dec)*Sin(observerLat)) / (Cos(dec) * Cos(observerLat))
|
||||||
|
|
||||||
|
var eventJD float64
|
||||||
|
if math.Abs(cosHourAngle) <= 1 {
|
||||||
|
hourOffset := ArcCos(cosHourAngle) / 15
|
||||||
|
if isRise {
|
||||||
|
eventJD = culminationJD - hourOffset/24 - 25.0/24.0/60.0
|
||||||
|
} else {
|
||||||
|
eventJD = culminationJD + hourOffset/24 - 25.0/24.0/60.0
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
eventJD = culminationJD
|
||||||
|
steps := 0
|
||||||
|
for OrbitHeight(eventJD, observerLon, observerLat, localTimezone, observerHeight, elements) > targetAltitude {
|
||||||
|
steps++
|
||||||
|
if isRise {
|
||||||
|
eventJD -= 15.0 / 60.0 / 24.0
|
||||||
|
} else {
|
||||||
|
eventJD += 15.0 / 60.0 / 24.0
|
||||||
|
}
|
||||||
|
if steps > 48 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
estimateJD := eventJD
|
||||||
|
for {
|
||||||
|
prevJD := estimateJD
|
||||||
|
altitudeDelta := OrbitHeight(prevJD, observerLon, observerLat, localTimezone, observerHeight, elements) - targetAltitude
|
||||||
|
altitudeSlope := (OrbitHeight(prevJD+0.000005, observerLon, observerLat, localTimezone, observerHeight, elements) - OrbitHeight(prevJD-0.000005, observerLon, observerLat, localTimezone, observerHeight, elements)) / 0.00001
|
||||||
|
estimateJD = prevJD - altitudeDelta/altitudeSlope
|
||||||
|
if math.Abs(estimateJD-prevJD) <= 0.00001 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return estimateJD - localTimezone/24 + timezone/24, nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
package basic
|
||||||
|
|
||||||
|
import . "b612.me/astro/tools"
|
||||||
|
|
||||||
|
// OrbitSunDistance 返回轨道目标在给定 TT/TDB 儒略日的日心距离,单位 AU。
|
||||||
|
func OrbitSunDistance(jd float64, elements OrbitElements) float64 {
|
||||||
|
_, _, distance := OrbitHeliocentricEclipticJ2000(jd, elements)
|
||||||
|
return distance
|
||||||
|
}
|
||||||
|
|
||||||
|
// OrbitEarthDistance 返回轨道目标在给定 TT/TDB 儒略日的地心距离,单位 AU。
|
||||||
|
func OrbitEarthDistance(jd float64, elements OrbitElements) float64 {
|
||||||
|
_, _, distance := OrbitGeocentricEclipticJ2000(jd, elements)
|
||||||
|
return distance
|
||||||
|
}
|
||||||
|
|
||||||
|
// OrbitPhaseAngle 返回轨道目标的相位角,单位度。
|
||||||
|
func OrbitPhaseAngle(jd float64, elements OrbitElements) float64 {
|
||||||
|
return ArcCos(orbitPhaseCosine(jd, elements))
|
||||||
|
}
|
||||||
|
|
||||||
|
// OrbitIlluminatedFraction 返回轨道目标的被照亮比例。
|
||||||
|
func OrbitIlluminatedFraction(jd float64, elements OrbitElements) float64 {
|
||||||
|
return (1 + orbitPhaseCosine(jd, elements)) / 2
|
||||||
|
}
|
||||||
|
|
||||||
|
// OrbitElongation 返回轨道目标相对于太阳的地心视角距,单位度。
|
||||||
|
func OrbitElongation(jd float64, elements OrbitElements) float64 {
|
||||||
|
lon, lat, _ := OrbitApparentGeocentricEcliptic(jd, elements)
|
||||||
|
return StarAngularSeparation(lon, lat, HSunApparentLo(jd), HSunTrueBo(jd))
|
||||||
|
}
|
||||||
|
|
||||||
|
func orbitPhaseCosine(jd float64, elements OrbitElements) float64 {
|
||||||
|
sunDistance := OrbitSunDistance(jd, elements)
|
||||||
|
earthDistance := OrbitEarthDistance(jd, elements)
|
||||||
|
earthSunDistance := EarthAway(jd)
|
||||||
|
cosine := (sunDistance*sunDistance + earthDistance*earthDistance - earthSunDistance*earthSunDistance) / (2 * sunDistance * earthDistance)
|
||||||
|
return clampUnit(cosine)
|
||||||
|
}
|
||||||
@@ -0,0 +1,126 @@
|
|||||||
|
package basic
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestOrbitCircularStateAtEpoch(t *testing.T) {
|
||||||
|
elements := OrbitElements{
|
||||||
|
EpochJD: orbitReferenceJD,
|
||||||
|
A: 2,
|
||||||
|
E: 0,
|
||||||
|
I: 0,
|
||||||
|
Omega: 0,
|
||||||
|
W: 0,
|
||||||
|
M0: 0,
|
||||||
|
}
|
||||||
|
|
||||||
|
vector := OrbitHeliocentricXYZJ2000(orbitReferenceJD, elements)
|
||||||
|
assertSameFloat(t, "x", vector[0], 2)
|
||||||
|
assertSameFloat(t, "y", vector[1], 0)
|
||||||
|
assertSameFloat(t, "z", vector[2], 0)
|
||||||
|
|
||||||
|
lon, lat, distance := OrbitHeliocentricEclipticJ2000(orbitReferenceJD, elements)
|
||||||
|
assertSameFloat(t, "lon", lon, 0)
|
||||||
|
assertSameFloat(t, "lat", lat, 0)
|
||||||
|
assertSameFloat(t, "distance", distance, 2)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestOrbitQuarterPeriodOnCircularOrbit(t *testing.T) {
|
||||||
|
elements := OrbitElements{
|
||||||
|
EpochJD: orbitReferenceJD,
|
||||||
|
A: 1,
|
||||||
|
E: 0,
|
||||||
|
I: 0,
|
||||||
|
Omega: 0,
|
||||||
|
W: 0,
|
||||||
|
M0: 0,
|
||||||
|
}
|
||||||
|
quarterPeriodDays := 90 / OrbitMeanMotion(elements)
|
||||||
|
vector := OrbitHeliocentricXYZJ2000(orbitReferenceJD+quarterPeriodDays, elements)
|
||||||
|
|
||||||
|
if math.Abs(vector[0]) > 1e-10 || math.Abs(vector[1]-1) > 1e-10 || math.Abs(vector[2]) > 1e-10 {
|
||||||
|
t.Fatalf("quarter-period vector mismatch: got %+v", vector)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestOrbitMeanAndTrueAnomalyCircularMatch(t *testing.T) {
|
||||||
|
elements := OrbitElements{
|
||||||
|
EpochJD: orbitReferenceJD,
|
||||||
|
A: 1.523679,
|
||||||
|
E: 0,
|
||||||
|
I: 1.85,
|
||||||
|
Omega: 49.5,
|
||||||
|
W: 286.5,
|
||||||
|
M0: 123.4,
|
||||||
|
}
|
||||||
|
jd := orbitReferenceJD + 123.456
|
||||||
|
meanAnomaly := OrbitMeanAnomaly(jd, elements)
|
||||||
|
trueAnomaly := OrbitTrueAnomaly(jd, elements)
|
||||||
|
if math.Abs(meanAnomaly-trueAnomaly) > 1e-12 {
|
||||||
|
t.Fatalf("circular mean/true anomaly mismatch: mean=%.18f true=%.18f", meanAnomaly, trueAnomaly)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPerihelionFormMatchesClassicalEllipticState(t *testing.T) {
|
||||||
|
classical := OrbitElements{
|
||||||
|
EpochJD: 2457305.5,
|
||||||
|
A: 3.462249489765068,
|
||||||
|
E: 0.6409081306555051,
|
||||||
|
I: 7.040294906760007,
|
||||||
|
Omega: 50.13557380441372,
|
||||||
|
W: 12.79824973415729,
|
||||||
|
M0: 8.859927418758764,
|
||||||
|
}
|
||||||
|
perihelion := OrbitElements{
|
||||||
|
Q: 1.243265641416762,
|
||||||
|
E: classical.E,
|
||||||
|
I: classical.I,
|
||||||
|
Omega: classical.Omega,
|
||||||
|
W: classical.W,
|
||||||
|
TpJD: 2457247.588657863465,
|
||||||
|
}
|
||||||
|
jd := 2457308.5
|
||||||
|
classicalVector := OrbitHeliocentricXYZJ2000(jd, classical)
|
||||||
|
perihelionVector := OrbitHeliocentricXYZJ2000(jd, perihelion)
|
||||||
|
for i := range classicalVector {
|
||||||
|
if math.Abs(classicalVector[i]-perihelionVector[i]) > 1e-11 {
|
||||||
|
t.Fatalf("component %d mismatch: classical=%.18f perihelion=%.18f", i, classicalVector[i], perihelionVector[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParabolicPerihelionStateAtTp(t *testing.T) {
|
||||||
|
elements := OrbitElements{Q: 1, E: 1, I: 0, Omega: 0, W: 0, TpJD: orbitReferenceJD}
|
||||||
|
vector := OrbitHeliocentricXYZJ2000(orbitReferenceJD, elements)
|
||||||
|
assertSameFloat(t, "x", vector[0], 1)
|
||||||
|
assertSameFloat(t, "y", vector[1], 0)
|
||||||
|
assertSameFloat(t, "z", vector[2], 0)
|
||||||
|
if !math.IsNaN(OrbitMeanAnomaly(orbitReferenceJD, elements)) {
|
||||||
|
t.Fatalf("parabolic mean anomaly should be NaN")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHyperbolicOrbitProducesFiniteState(t *testing.T) {
|
||||||
|
elements := OrbitElements{Q: 0.5, E: 1.2, I: 12, Omega: 30, W: 45, TpJD: orbitReferenceJD}
|
||||||
|
vector := OrbitHeliocentricXYZJ2000(orbitReferenceJD+20, elements)
|
||||||
|
for i, component := range vector {
|
||||||
|
if math.IsNaN(component) || math.IsInf(component, 0) {
|
||||||
|
t.Fatalf("component %d not finite: %.18f", i, component)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestOrbitInvalidEllipticElementsReturnNaN(t *testing.T) {
|
||||||
|
elements := OrbitElements{EpochJD: orbitReferenceJD, A: 1, E: 1.1}
|
||||||
|
if !math.IsNaN(OrbitMeanMotion(elements)) {
|
||||||
|
t.Fatalf("expected NaN mean motion for invalid elements")
|
||||||
|
}
|
||||||
|
vector := OrbitHeliocentricXYZJ2000(orbitReferenceJD, elements)
|
||||||
|
for i, component := range vector {
|
||||||
|
if !math.IsNaN(component) {
|
||||||
|
t.Fatalf("component %d expected NaN, got %.18f", i, component)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,289 @@
|
|||||||
|
package basic
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math"
|
||||||
|
|
||||||
|
"b612.me/astro/planet"
|
||||||
|
. "b612.me/astro/tools"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// 月球和内行星轨道角速度更快,取更小的中心差分步长。
|
||||||
|
orbitalNodeStepFastBodyDays = 0.005
|
||||||
|
// 外行星保留更稳健的 0.01 day 步长,避免无意义地放大数值噪声。
|
||||||
|
orbitalNodeStepSlowBodyDays = 0.01
|
||||||
|
)
|
||||||
|
|
||||||
|
// MoonAscendingNode 月球升交点黄经 / ascending node longitude of the Moon.
|
||||||
|
func MoonAscendingNode(jd float64) float64 {
|
||||||
|
return MoonAscendingNodeN(jd, -1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MoonAscendingNodeN 月球升交点黄经(截断版) / truncated ascending node longitude of the Moon.
|
||||||
|
func MoonAscendingNodeN(jd float64, n int) float64 {
|
||||||
|
return orbitalAscendingNodeLongitude(jd, n, orbitalNodeStepFastBodyDays, moonGeocentricNodePositionN)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MoonDescendingNode 月球降交点黄经 / descending node longitude of the Moon.
|
||||||
|
func MoonDescendingNode(jd float64) float64 {
|
||||||
|
return MoonDescendingNodeN(jd, -1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MoonDescendingNodeN 月球降交点黄经(截断版) / truncated descending node longitude of the Moon.
|
||||||
|
func MoonDescendingNodeN(jd float64, n int) float64 {
|
||||||
|
return Limit360(MoonAscendingNodeN(jd, n) + 180)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MercuryAscendingNode 水星升交点黄经 / ascending node longitude of Mercury.
|
||||||
|
func MercuryAscendingNode(jd float64) float64 {
|
||||||
|
return MercuryAscendingNodeN(jd, -1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MercuryAscendingNodeN 水星升交点黄经(截断版) / truncated ascending node longitude of Mercury.
|
||||||
|
func MercuryAscendingNodeN(jd float64, n int) float64 {
|
||||||
|
return planetAscendingNodeLongitudeN(1, jd, n)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MercuryDescendingNode 水星降交点黄经 / descending node longitude of Mercury.
|
||||||
|
func MercuryDescendingNode(jd float64) float64 {
|
||||||
|
return MercuryDescendingNodeN(jd, -1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MercuryDescendingNodeN 水星降交点黄经(截断版) / truncated descending node longitude of Mercury.
|
||||||
|
func MercuryDescendingNodeN(jd float64, n int) float64 {
|
||||||
|
return Limit360(MercuryAscendingNodeN(jd, n) + 180)
|
||||||
|
}
|
||||||
|
|
||||||
|
// VenusAscendingNode 金星升交点黄经 / ascending node longitude of Venus.
|
||||||
|
func VenusAscendingNode(jd float64) float64 {
|
||||||
|
return VenusAscendingNodeN(jd, -1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// VenusAscendingNodeN 金星升交点黄经(截断版) / truncated ascending node longitude of Venus.
|
||||||
|
func VenusAscendingNodeN(jd float64, n int) float64 {
|
||||||
|
return planetAscendingNodeLongitudeN(2, jd, n)
|
||||||
|
}
|
||||||
|
|
||||||
|
// VenusDescendingNode 金星降交点黄经 / descending node longitude of Venus.
|
||||||
|
func VenusDescendingNode(jd float64) float64 {
|
||||||
|
return VenusDescendingNodeN(jd, -1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// VenusDescendingNodeN 金星降交点黄经(截断版) / truncated descending node longitude of Venus.
|
||||||
|
func VenusDescendingNodeN(jd float64, n int) float64 {
|
||||||
|
return Limit360(VenusAscendingNodeN(jd, n) + 180)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarsAscendingNode 火星升交点黄经 / ascending node longitude of Mars.
|
||||||
|
func MarsAscendingNode(jd float64) float64 {
|
||||||
|
return MarsAscendingNodeN(jd, -1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarsAscendingNodeN 火星升交点黄经(截断版) / truncated ascending node longitude of Mars.
|
||||||
|
func MarsAscendingNodeN(jd float64, n int) float64 {
|
||||||
|
return planetAscendingNodeLongitudeN(3, jd, n)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarsDescendingNode 火星降交点黄经 / descending node longitude of Mars.
|
||||||
|
func MarsDescendingNode(jd float64) float64 {
|
||||||
|
return MarsDescendingNodeN(jd, -1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarsDescendingNodeN 火星降交点黄经(截断版) / truncated descending node longitude of Mars.
|
||||||
|
func MarsDescendingNodeN(jd float64, n int) float64 {
|
||||||
|
return Limit360(MarsAscendingNodeN(jd, n) + 180)
|
||||||
|
}
|
||||||
|
|
||||||
|
// JupiterAscendingNode 木星升交点黄经 / ascending node longitude of Jupiter.
|
||||||
|
func JupiterAscendingNode(jd float64) float64 {
|
||||||
|
return JupiterAscendingNodeN(jd, -1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// JupiterAscendingNodeN 木星升交点黄经(截断版) / truncated ascending node longitude of Jupiter.
|
||||||
|
func JupiterAscendingNodeN(jd float64, n int) float64 {
|
||||||
|
return planetAscendingNodeLongitudeN(4, jd, n)
|
||||||
|
}
|
||||||
|
|
||||||
|
// JupiterDescendingNode 木星降交点黄经 / descending node longitude of Jupiter.
|
||||||
|
func JupiterDescendingNode(jd float64) float64 {
|
||||||
|
return JupiterDescendingNodeN(jd, -1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// JupiterDescendingNodeN 木星降交点黄经(截断版) / truncated descending node longitude of Jupiter.
|
||||||
|
func JupiterDescendingNodeN(jd float64, n int) float64 {
|
||||||
|
return Limit360(JupiterAscendingNodeN(jd, n) + 180)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SaturnAscendingNode 土星升交点黄经 / ascending node longitude of Saturn.
|
||||||
|
func SaturnAscendingNode(jd float64) float64 {
|
||||||
|
return SaturnAscendingNodeN(jd, -1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SaturnAscendingNodeN 土星升交点黄经(截断版) / truncated ascending node longitude of Saturn.
|
||||||
|
func SaturnAscendingNodeN(jd float64, n int) float64 {
|
||||||
|
return planetAscendingNodeLongitudeN(5, jd, n)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SaturnDescendingNode 土星降交点黄经 / descending node longitude of Saturn.
|
||||||
|
func SaturnDescendingNode(jd float64) float64 {
|
||||||
|
return SaturnDescendingNodeN(jd, -1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SaturnDescendingNodeN 土星降交点黄经(截断版) / truncated descending node longitude of Saturn.
|
||||||
|
func SaturnDescendingNodeN(jd float64, n int) float64 {
|
||||||
|
return Limit360(SaturnAscendingNodeN(jd, n) + 180)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UranusAscendingNode 天王星升交点黄经 / ascending node longitude of Uranus.
|
||||||
|
func UranusAscendingNode(jd float64) float64 {
|
||||||
|
return UranusAscendingNodeN(jd, -1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UranusAscendingNodeN 天王星升交点黄经(截断版) / truncated ascending node longitude of Uranus.
|
||||||
|
func UranusAscendingNodeN(jd float64, n int) float64 {
|
||||||
|
return planetAscendingNodeLongitudeN(6, jd, n)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UranusDescendingNode 天王星降交点黄经 / descending node longitude of Uranus.
|
||||||
|
func UranusDescendingNode(jd float64) float64 {
|
||||||
|
return UranusDescendingNodeN(jd, -1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UranusDescendingNodeN 天王星降交点黄经(截断版) / truncated descending node longitude of Uranus.
|
||||||
|
func UranusDescendingNodeN(jd float64, n int) float64 {
|
||||||
|
return Limit360(UranusAscendingNodeN(jd, n) + 180)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NeptuneAscendingNode 海王星升交点黄经 / ascending node longitude of Neptune.
|
||||||
|
func NeptuneAscendingNode(jd float64) float64 {
|
||||||
|
return NeptuneAscendingNodeN(jd, -1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NeptuneAscendingNodeN 海王星升交点黄经(截断版) / truncated ascending node longitude of Neptune.
|
||||||
|
func NeptuneAscendingNodeN(jd float64, n int) float64 {
|
||||||
|
return planetAscendingNodeLongitudeN(7, jd, n)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NeptuneDescendingNode 海王星降交点黄经 / descending node longitude of Neptune.
|
||||||
|
func NeptuneDescendingNode(jd float64) float64 {
|
||||||
|
return NeptuneDescendingNodeN(jd, -1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NeptuneDescendingNodeN 海王星降交点黄经(截断版) / truncated descending node longitude of Neptune.
|
||||||
|
func NeptuneDescendingNodeN(jd float64, n int) float64 {
|
||||||
|
return Limit360(NeptuneAscendingNodeN(jd, n) + 180)
|
||||||
|
}
|
||||||
|
|
||||||
|
func planetAscendingNodeLongitudeN(planetIndex int, jd float64, n int) float64 {
|
||||||
|
step := orbitalNodeStepSlowBodyDays
|
||||||
|
if planetIndex <= 3 {
|
||||||
|
step = orbitalNodeStepFastBodyDays
|
||||||
|
}
|
||||||
|
return orbitalAscendingNodeLongitude(jd, n, step, func(sampleJD float64, seriesTerms int) Vector3 {
|
||||||
|
return planetHeliocentricNodePositionN(planetIndex, sampleJD, seriesTerms)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func orbitalAscendingNodeLongitude(jd float64, n int, step float64, position func(float64, int) Vector3) float64 {
|
||||||
|
current := eclipticVectorAtReferenceEpoch(position(jd, n), jd, jd)
|
||||||
|
previous := eclipticVectorAtReferenceEpoch(position(jd-step, n), jd-step, jd)
|
||||||
|
next := eclipticVectorAtReferenceEpoch(position(jd+step, n), jd+step, jd)
|
||||||
|
|
||||||
|
velocity := Vector3{
|
||||||
|
(next[0] - previous[0]) / (2 * step),
|
||||||
|
(next[1] - previous[1]) / (2 * step),
|
||||||
|
(next[2] - previous[2]) / (2 * step),
|
||||||
|
}
|
||||||
|
angularMomentum := pxp(current, velocity)
|
||||||
|
nodeVector, magnitude := pn(Vector3{-angularMomentum[1], angularMomentum[0], 0})
|
||||||
|
if magnitude == 0 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return Limit360(math.Atan2(nodeVector[1], nodeVector[0]) * deg)
|
||||||
|
}
|
||||||
|
|
||||||
|
func eclipticVectorAtReferenceEpoch(vector Vector3, sampleJD, referenceJD float64) Vector3 {
|
||||||
|
if sampleJD == referenceJD {
|
||||||
|
return vector
|
||||||
|
}
|
||||||
|
|
||||||
|
sampleEquatorial := rotateEclipticToEquatorial(vector, EclipticObliquity(sampleJD, false))
|
||||||
|
precessedEquatorial := applyMatrix3(precessionMatrix(sampleJD, referenceJD), sampleEquatorial)
|
||||||
|
return rotateEquatorialToEcliptic(precessedEquatorial, EclipticObliquity(referenceJD, false))
|
||||||
|
}
|
||||||
|
|
||||||
|
func rotateEclipticToEquatorial(vector Vector3, obliquity float64) Vector3 {
|
||||||
|
epsilon := obliquity * rad
|
||||||
|
cosEpsilon := math.Cos(epsilon)
|
||||||
|
sinEpsilon := math.Sin(epsilon)
|
||||||
|
return Vector3{
|
||||||
|
vector[0],
|
||||||
|
vector[1]*cosEpsilon - vector[2]*sinEpsilon,
|
||||||
|
vector[1]*sinEpsilon + vector[2]*cosEpsilon,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func rotateEquatorialToEcliptic(vector Vector3, obliquity float64) Vector3 {
|
||||||
|
epsilon := obliquity * rad
|
||||||
|
cosEpsilon := math.Cos(epsilon)
|
||||||
|
sinEpsilon := math.Sin(epsilon)
|
||||||
|
return Vector3{
|
||||||
|
vector[0],
|
||||||
|
vector[1]*cosEpsilon + vector[2]*sinEpsilon,
|
||||||
|
-vector[1]*sinEpsilon + vector[2]*cosEpsilon,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func precessionMatrix(jdFrom, jdTo float64) Matrix3 {
|
||||||
|
epjFrom := 2000.0 + (jdFrom-2451545.0)/365.25
|
||||||
|
epjTo := 2000.0 + (jdTo-2451545.0)/365.25
|
||||||
|
|
||||||
|
rpFrom := ltpPMAT(epjFrom)
|
||||||
|
rpFromInv := Matrix3{
|
||||||
|
{rpFrom[0][0], rpFrom[1][0], rpFrom[2][0]},
|
||||||
|
{rpFrom[0][1], rpFrom[1][1], rpFrom[2][1]},
|
||||||
|
{rpFrom[0][2], rpFrom[1][2], rpFrom[2][2]},
|
||||||
|
}
|
||||||
|
rpTo := ltpPMAT(epjTo)
|
||||||
|
|
||||||
|
var result Matrix3
|
||||||
|
for i := 0; i < 3; i++ {
|
||||||
|
for j := 0; j < 3; j++ {
|
||||||
|
for k := 0; k < 3; k++ {
|
||||||
|
result[i][j] += rpTo[i][k] * rpFromInv[k][j]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func applyMatrix3(matrix Matrix3, vector Vector3) Vector3 {
|
||||||
|
return Vector3{
|
||||||
|
matrix[0][0]*vector[0] + matrix[0][1]*vector[1] + matrix[0][2]*vector[2],
|
||||||
|
matrix[1][0]*vector[0] + matrix[1][1]*vector[1] + matrix[1][2]*vector[2],
|
||||||
|
matrix[2][0]*vector[0] + matrix[2][1]*vector[1] + matrix[2][2]*vector[2],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func planetHeliocentricNodePositionN(planetIndex int, jd float64, n int) Vector3 {
|
||||||
|
longitude := planet.WherePlanetN(planetIndex, 0, jd, n)
|
||||||
|
latitude := planet.WherePlanetN(planetIndex, 1, jd, n)
|
||||||
|
radius := planet.WherePlanetN(planetIndex, 2, jd, n)
|
||||||
|
return eclipticCartesian(longitude, latitude, radius)
|
||||||
|
}
|
||||||
|
|
||||||
|
func moonGeocentricNodePositionN(jd float64, n int) Vector3 {
|
||||||
|
longitude := HMoonTrueLoN(jd, n)
|
||||||
|
latitude := HMoonTrueBoN(jd, n)
|
||||||
|
radius := HMoonAwayN(jd, n)
|
||||||
|
return eclipticCartesian(longitude, latitude, radius)
|
||||||
|
}
|
||||||
|
|
||||||
|
func eclipticCartesian(longitude, latitude, radius float64) Vector3 {
|
||||||
|
cosLatitude := Cos(latitude)
|
||||||
|
return Vector3{
|
||||||
|
radius * cosLatitude * Cos(longitude),
|
||||||
|
radius * cosLatitude * Sin(longitude),
|
||||||
|
radius * Sin(latitude),
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,77 @@
|
|||||||
|
package basic
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestOrbitalNodesDescendingOpposesAscending(t *testing.T) {
|
||||||
|
jde := 2461157.5
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
ascending func(float64) float64
|
||||||
|
descending func(float64) float64
|
||||||
|
}{
|
||||||
|
{name: "moon", ascending: MoonAscendingNode, descending: MoonDescendingNode},
|
||||||
|
{name: "mercury", ascending: MercuryAscendingNode, descending: MercuryDescendingNode},
|
||||||
|
{name: "venus", ascending: VenusAscendingNode, descending: VenusDescendingNode},
|
||||||
|
{name: "mars", ascending: MarsAscendingNode, descending: MarsDescendingNode},
|
||||||
|
{name: "jupiter", ascending: JupiterAscendingNode, descending: JupiterDescendingNode},
|
||||||
|
{name: "saturn", ascending: SaturnAscendingNode, descending: SaturnDescendingNode},
|
||||||
|
{name: "uranus", ascending: UranusAscendingNode, descending: UranusDescendingNode},
|
||||||
|
{name: "neptune", ascending: NeptuneAscendingNode, descending: NeptuneDescendingNode},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
ascending := tc.ascending(jde)
|
||||||
|
descending := tc.descending(jde)
|
||||||
|
want := ascending + 180
|
||||||
|
if want >= 360 {
|
||||||
|
want -= 360
|
||||||
|
}
|
||||||
|
if diff := angularDifference(descending, ascending+180); diff > 1e-10 {
|
||||||
|
t.Fatalf("descending node mismatch: got %.12f want %.12f diff=%.12g", descending, want, diff)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestOrbitalNodesNFullMatchesDefault(t *testing.T) {
|
||||||
|
jde := 2461157.5
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
defaultFn func(float64) float64
|
||||||
|
truncatedN func(float64, int) float64
|
||||||
|
}{
|
||||||
|
{name: "moon", defaultFn: MoonAscendingNode, truncatedN: MoonAscendingNodeN},
|
||||||
|
{name: "mercury", defaultFn: MercuryAscendingNode, truncatedN: MercuryAscendingNodeN},
|
||||||
|
{name: "venus", defaultFn: VenusAscendingNode, truncatedN: VenusAscendingNodeN},
|
||||||
|
{name: "mars", defaultFn: MarsAscendingNode, truncatedN: MarsAscendingNodeN},
|
||||||
|
{name: "jupiter", defaultFn: JupiterAscendingNode, truncatedN: JupiterAscendingNodeN},
|
||||||
|
{name: "saturn", defaultFn: SaturnAscendingNode, truncatedN: SaturnAscendingNodeN},
|
||||||
|
{name: "uranus", defaultFn: UranusAscendingNode, truncatedN: UranusAscendingNodeN},
|
||||||
|
{name: "neptune", defaultFn: NeptuneAscendingNode, truncatedN: NeptuneAscendingNodeN},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
got := tc.defaultFn(jde)
|
||||||
|
gotN := tc.truncatedN(jde, -1)
|
||||||
|
if diff := angularDifference(got, gotN); diff > 1e-10 {
|
||||||
|
t.Fatalf("full-series N mismatch: got %.12f want %.12f diff=%.12g", gotN, got, diff)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func angularDifference(a, b float64) float64 {
|
||||||
|
diff := math.Mod(a-b, 360)
|
||||||
|
if diff < -180 {
|
||||||
|
diff += 360
|
||||||
|
}
|
||||||
|
if diff > 180 {
|
||||||
|
diff -= 360
|
||||||
|
}
|
||||||
|
return math.Abs(diff)
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
package basic
|
||||||
|
|
||||||
|
import "testing"
|
||||||
|
|
||||||
|
func benchmarkOuterPlanetEventFamily(b *testing.B, plan outerPlanetEventPlan, cases []outerPlanetEventCase) {
|
||||||
|
samples := plan.samples()
|
||||||
|
var sink float64
|
||||||
|
b.ReportAllocs()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
for _, sample := range samples {
|
||||||
|
jd := outerPlanetEventSampleTTJD(sample)
|
||||||
|
for _, event := range cases {
|
||||||
|
sink += event.fn(jd)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ = sink
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkOuterPlanetEventFamilies(b *testing.B) {
|
||||||
|
for _, plan := range outerPlanetEventPlans() {
|
||||||
|
plan := plan
|
||||||
|
b.Run(plan.planet+"PhaseFamily", func(b *testing.B) {
|
||||||
|
benchmarkOuterPlanetEventFamily(b, plan, plan.phaseCases)
|
||||||
|
})
|
||||||
|
b.Run(plan.planet+"RetrogradeFamily", func(b *testing.B) {
|
||||||
|
benchmarkOuterPlanetEventFamily(b, plan, plan.retroCases)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,84 @@
|
|||||||
|
package basic
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestOuterPlanetExactEventBoundaryIncludesCurrent(t *testing.T) {
|
||||||
|
cases := []struct {
|
||||||
|
name string
|
||||||
|
seed float64
|
||||||
|
lastFn func(float64) float64
|
||||||
|
nextFn func(float64) float64
|
||||||
|
}{
|
||||||
|
{name: "JupiterConjunction", seed: NextJupiterConjunction(ttjdUTC(2026, 1, 1, 0, 0, 0)), lastFn: LastJupiterConjunction, nextFn: NextJupiterConjunction},
|
||||||
|
{name: "JupiterOpposition", seed: NextJupiterOpposition(ttjdUTC(2026, 1, 1, 0, 0, 0)), lastFn: LastJupiterOpposition, nextFn: NextJupiterOpposition},
|
||||||
|
{name: "JupiterEasternQuadrature", seed: NextJupiterEasternQuadrature(ttjdUTC(2026, 1, 1, 0, 0, 0)), lastFn: LastJupiterEasternQuadrature, nextFn: NextJupiterEasternQuadrature},
|
||||||
|
{name: "JupiterWesternQuadrature", seed: NextJupiterWesternQuadrature(ttjdUTC(2026, 1, 1, 0, 0, 0)), lastFn: LastJupiterWesternQuadrature, nextFn: NextJupiterWesternQuadrature},
|
||||||
|
{name: "JupiterP2R", seed: NextJupiterProgradeToRetrograde(ttjdUTC(2026, 1, 1, 0, 0, 0)), lastFn: LastJupiterProgradeToRetrograde, nextFn: NextJupiterProgradeToRetrograde},
|
||||||
|
{name: "JupiterR2P", seed: NextJupiterRetrogradeToPrograde(ttjdUTC(2026, 1, 1, 0, 0, 0)), lastFn: LastJupiterRetrogradeToPrograde, nextFn: NextJupiterRetrogradeToPrograde},
|
||||||
|
{name: "SaturnOpposition", seed: NextSaturnOpposition(ttjdUTC(2025, 1, 1, 0, 0, 0)), lastFn: LastSaturnOpposition, nextFn: NextSaturnOpposition},
|
||||||
|
{name: "SaturnP2R", seed: NextSaturnProgradeToRetrograde(ttjdUTC(2025, 1, 1, 0, 0, 0)), lastFn: LastSaturnProgradeToRetrograde, nextFn: NextSaturnProgradeToRetrograde},
|
||||||
|
{name: "SaturnR2P", seed: NextSaturnRetrogradeToPrograde(ttjdUTC(2025, 1, 1, 0, 0, 0)), lastFn: LastSaturnRetrogradeToPrograde, nextFn: NextSaturnRetrogradeToPrograde},
|
||||||
|
{name: "UranusOpposition", seed: NextUranusOpposition(ttjdUTC(2025, 1, 1, 0, 0, 0)), lastFn: LastUranusOpposition, nextFn: NextUranusOpposition},
|
||||||
|
{name: "UranusP2R", seed: NextUranusProgradeToRetrograde(ttjdUTC(2025, 1, 1, 0, 0, 0)), lastFn: LastUranusProgradeToRetrograde, nextFn: NextUranusProgradeToRetrograde},
|
||||||
|
{name: "UranusR2P", seed: NextUranusRetrogradeToPrograde(ttjdUTC(2025, 1, 1, 0, 0, 0)), lastFn: LastUranusRetrogradeToPrograde, nextFn: NextUranusRetrogradeToPrograde},
|
||||||
|
{name: "NeptuneOpposition", seed: NextNeptuneOpposition(ttjdUTC(2026, 1, 1, 0, 0, 0)), lastFn: LastNeptuneOpposition, nextFn: NextNeptuneOpposition},
|
||||||
|
{name: "NeptuneP2R", seed: NextNeptuneProgradeToRetrograde(ttjdUTC(2026, 1, 1, 0, 0, 0)), lastFn: LastNeptuneProgradeToRetrograde, nextFn: NextNeptuneProgradeToRetrograde},
|
||||||
|
{name: "NeptuneR2P", seed: NextNeptuneRetrogradeToPrograde(ttjdUTC(2026, 1, 1, 0, 0, 0)), lastFn: LastNeptuneRetrogradeToPrograde, nextFn: NextNeptuneRetrogradeToPrograde},
|
||||||
|
{name: "MarsConjunction", seed: NextMarsConjunction(ttjdUTC(2026, 1, 1, 0, 0, 0)), lastFn: LastMarsConjunction, nextFn: NextMarsConjunction},
|
||||||
|
{name: "MarsOpposition", seed: NextMarsOpposition(ttjdUTC(2026, 1, 1, 0, 0, 0)), lastFn: LastMarsOpposition, nextFn: NextMarsOpposition},
|
||||||
|
{name: "MarsEasternQuadrature", seed: NextMarsEasternQuadrature(ttjdUTC(2026, 1, 1, 0, 0, 0)), lastFn: LastMarsEasternQuadrature, nextFn: NextMarsEasternQuadrature},
|
||||||
|
{name: "MarsWesternQuadrature", seed: NextMarsWesternQuadrature(ttjdUTC(2026, 1, 1, 0, 0, 0)), lastFn: LastMarsWesternQuadrature, nextFn: NextMarsWesternQuadrature},
|
||||||
|
{name: "MarsP2R", seed: NextMarsProgradeToRetrograde(ttjdUTC(2026, 1, 1, 0, 0, 0)), lastFn: LastMarsProgradeToRetrograde, nextFn: NextMarsProgradeToRetrograde},
|
||||||
|
{name: "MarsR2P", seed: NextMarsRetrogradeToPrograde(ttjdUTC(2025, 1, 1, 0, 0, 0)), lastFn: LastMarsRetrogradeToPrograde, nextFn: NextMarsRetrogradeToPrograde},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range cases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
queryTT := TD2UT(tc.seed, true)
|
||||||
|
last := tc.lastFn(queryTT)
|
||||||
|
next := tc.nextFn(queryTT)
|
||||||
|
if !sameEventJD(last, tc.seed) {
|
||||||
|
t.Fatalf("last exact boundary mismatch: got %.12f want %.12f", last, tc.seed)
|
||||||
|
}
|
||||||
|
if !sameEventJD(next, tc.seed) {
|
||||||
|
t.Fatalf("next exact boundary mismatch: got %.12f want %.12f", next, tc.seed)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestOuterPlanetNextEventAdvancesPastReturnedEvent(t *testing.T) {
|
||||||
|
cases := []struct {
|
||||||
|
name string
|
||||||
|
seed float64
|
||||||
|
next func(float64) float64
|
||||||
|
}{
|
||||||
|
{name: "MarsOpposition", seed: ttjdUTC(2026, 1, 1, 0, 0, 0), next: NextMarsOpposition},
|
||||||
|
{name: "MarsP2R", seed: ttjdUTC(2026, 1, 1, 0, 0, 0), next: NextMarsProgradeToRetrograde},
|
||||||
|
{name: "JupiterOpposition", seed: ttjdUTC(2026, 1, 1, 0, 0, 0), next: NextJupiterOpposition},
|
||||||
|
{name: "SaturnConjunction", seed: ttjdUTC(2026, 1, 1, 0, 0, 0), next: NextSaturnConjunction},
|
||||||
|
{name: "UranusP2R", seed: ttjdUTC(2026, 1, 1, 0, 0, 0), next: NextUranusProgradeToRetrograde},
|
||||||
|
{name: "NeptuneR2P", seed: ttjdUTC(2026, 1, 1, 0, 0, 0), next: NextNeptuneRetrogradeToPrograde},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range cases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
first := tc.next(tc.seed)
|
||||||
|
query := TD2UT(Date2JDE(JDE2DateByZone(first, time.UTC, false).Add(time.Second)), true)
|
||||||
|
next := tc.next(query)
|
||||||
|
if !eventUTQueryAfterOrEqual(next, query) {
|
||||||
|
t.Fatalf("next should be after query: first=%.12f query=%.12f next=%.12f", first, query, next)
|
||||||
|
}
|
||||||
|
if sameEventJD(next, first) {
|
||||||
|
t.Fatalf("next should advance past first event: first=%.12f next=%.12f", first, next)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ttjdUTC(year, month, day, hour, min, sec int) float64 {
|
||||||
|
return TD2UT(Date2JDE(time.Date(year, time.Month(month), day, hour, min, sec, 0, time.UTC)), true)
|
||||||
|
}
|
||||||
@@ -0,0 +1,193 @@
|
|||||||
|
package basic
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type outerPlanetEventFunc func(float64) float64
|
||||||
|
|
||||||
|
type outerPlanetEventCase struct {
|
||||||
|
name string
|
||||||
|
tolerance float64
|
||||||
|
fn outerPlanetEventFunc
|
||||||
|
}
|
||||||
|
|
||||||
|
type outerPlanetEventPlan struct {
|
||||||
|
planet string
|
||||||
|
baselineFile string
|
||||||
|
samples func() []time.Time
|
||||||
|
phaseCases []outerPlanetEventCase
|
||||||
|
retroCases []outerPlanetEventCase
|
||||||
|
}
|
||||||
|
|
||||||
|
func (plan outerPlanetEventPlan) allCases() []outerPlanetEventCase {
|
||||||
|
cases := make([]outerPlanetEventCase, 0, len(plan.phaseCases)+len(plan.retroCases))
|
||||||
|
cases = append(cases, plan.phaseCases...)
|
||||||
|
cases = append(cases, plan.retroCases...)
|
||||||
|
return cases
|
||||||
|
}
|
||||||
|
|
||||||
|
func outerPlanetEventSampleTTJD(date time.Time) float64 {
|
||||||
|
return TD2UT(Date2JDE(date.UTC()), true)
|
||||||
|
}
|
||||||
|
|
||||||
|
func jupiterEventSamples() []time.Time {
|
||||||
|
start := time.Date(1991, 3, 11, 5, 17, 23, 456000000, time.UTC)
|
||||||
|
samples := make([]time.Time, 0, 96)
|
||||||
|
for i := 0; i < 48; i++ {
|
||||||
|
d := start.AddDate(0, 0, i*227)
|
||||||
|
d = d.Add(time.Duration((i%8)*4)*time.Hour + time.Duration((i%11)*9)*time.Minute + time.Duration((i%13)*17)*time.Second)
|
||||||
|
samples = append(samples, d)
|
||||||
|
}
|
||||||
|
extraStart := start.AddDate(0, 0, 37)
|
||||||
|
for i := 0; i < 48; i++ {
|
||||||
|
d := extraStart.AddDate(0, 0, i*149)
|
||||||
|
d = d.Add(time.Duration((i%6)*7)*time.Hour + time.Duration((i%10)*13)*time.Minute + time.Duration((i%15)*23)*time.Second)
|
||||||
|
samples = append(samples, d)
|
||||||
|
}
|
||||||
|
return samples
|
||||||
|
}
|
||||||
|
|
||||||
|
func saturnEventSamples() []time.Time {
|
||||||
|
start := time.Date(1990, 7, 21, 8, 12, 34, 567000000, time.UTC)
|
||||||
|
samples := make([]time.Time, 0, 96)
|
||||||
|
for i := 0; i < 48; i++ {
|
||||||
|
d := start.AddDate(0, 0, i*233)
|
||||||
|
d = d.Add(time.Duration((i%9)*5)*time.Hour + time.Duration((i%10)*7)*time.Minute + time.Duration((i%12)*19)*time.Second)
|
||||||
|
samples = append(samples, d)
|
||||||
|
}
|
||||||
|
extraStart := start.AddDate(0, 0, 43)
|
||||||
|
for i := 0; i < 48; i++ {
|
||||||
|
d := extraStart.AddDate(0, 0, i*157)
|
||||||
|
d = d.Add(time.Duration((i%7)*6)*time.Hour + time.Duration((i%9)*11)*time.Minute + time.Duration((i%14)*29)*time.Second)
|
||||||
|
samples = append(samples, d)
|
||||||
|
}
|
||||||
|
return samples
|
||||||
|
}
|
||||||
|
|
||||||
|
func uranusEventSamples() []time.Time {
|
||||||
|
start := time.Date(1993, 11, 5, 10, 22, 33, 444000000, time.UTC)
|
||||||
|
samples := make([]time.Time, 0, 96)
|
||||||
|
for i := 0; i < 48; i++ {
|
||||||
|
d := start.AddDate(0, 0, i*239)
|
||||||
|
d = d.Add(time.Duration((i%7)*6)*time.Hour + time.Duration((i%9)*13)*time.Minute + time.Duration((i%14)*11)*time.Second)
|
||||||
|
samples = append(samples, d)
|
||||||
|
}
|
||||||
|
extraStart := start.AddDate(0, 0, 59)
|
||||||
|
for i := 0; i < 48; i++ {
|
||||||
|
d := extraStart.AddDate(0, 0, i*163)
|
||||||
|
d = d.Add(time.Duration((i%8)*5)*time.Hour + time.Duration((i%12)*17)*time.Minute + time.Duration((i%13)*31)*time.Second)
|
||||||
|
samples = append(samples, d)
|
||||||
|
}
|
||||||
|
return samples
|
||||||
|
}
|
||||||
|
|
||||||
|
func neptuneEventSamples() []time.Time {
|
||||||
|
start := time.Date(1996, 4, 17, 3, 14, 15, 926000000, time.UTC)
|
||||||
|
samples := make([]time.Time, 0, 96)
|
||||||
|
for i := 0; i < 48; i++ {
|
||||||
|
d := start.AddDate(0, 0, i*241)
|
||||||
|
d = d.Add(time.Duration((i%10)*3)*time.Hour + time.Duration((i%8)*17)*time.Minute + time.Duration((i%15)*7)*time.Second)
|
||||||
|
samples = append(samples, d)
|
||||||
|
}
|
||||||
|
extraStart := start.AddDate(0, 0, 67)
|
||||||
|
for i := 0; i < 48; i++ {
|
||||||
|
d := extraStart.AddDate(0, 0, i*167)
|
||||||
|
d = d.Add(time.Duration((i%9)*4)*time.Hour + time.Duration((i%11)*19)*time.Minute + time.Duration((i%14)*27)*time.Second)
|
||||||
|
samples = append(samples, d)
|
||||||
|
}
|
||||||
|
return samples
|
||||||
|
}
|
||||||
|
|
||||||
|
func outerPlanetEventPlans() []outerPlanetEventPlan {
|
||||||
|
const (
|
||||||
|
conjunctionTolerance = 0.00001
|
||||||
|
searchTolerance = 30.0 / 86400.0
|
||||||
|
)
|
||||||
|
return []outerPlanetEventPlan{
|
||||||
|
{
|
||||||
|
planet: "Jupiter",
|
||||||
|
baselineFile: "testdata/jupiter_event_baseline.json",
|
||||||
|
samples: jupiterEventSamples,
|
||||||
|
phaseCases: []outerPlanetEventCase{
|
||||||
|
{name: "LastJupiterConjunction", tolerance: conjunctionTolerance, fn: LastJupiterConjunction},
|
||||||
|
{name: "NextJupiterConjunction", tolerance: conjunctionTolerance, fn: NextJupiterConjunction},
|
||||||
|
{name: "LastJupiterOpposition", tolerance: conjunctionTolerance, fn: LastJupiterOpposition},
|
||||||
|
{name: "NextJupiterOpposition", tolerance: conjunctionTolerance, fn: NextJupiterOpposition},
|
||||||
|
{name: "LastJupiterEasternQuadrature", tolerance: conjunctionTolerance, fn: LastJupiterEasternQuadrature},
|
||||||
|
{name: "NextJupiterEasternQuadrature", tolerance: conjunctionTolerance, fn: NextJupiterEasternQuadrature},
|
||||||
|
{name: "LastJupiterWesternQuadrature", tolerance: conjunctionTolerance, fn: LastJupiterWesternQuadrature},
|
||||||
|
{name: "NextJupiterWesternQuadrature", tolerance: conjunctionTolerance, fn: NextJupiterWesternQuadrature},
|
||||||
|
},
|
||||||
|
retroCases: []outerPlanetEventCase{
|
||||||
|
{name: "LastJupiterProgradeToRetrograde", tolerance: searchTolerance, fn: LastJupiterProgradeToRetrograde},
|
||||||
|
{name: "NextJupiterProgradeToRetrograde", tolerance: searchTolerance, fn: NextJupiterProgradeToRetrograde},
|
||||||
|
{name: "LastJupiterRetrogradeToPrograde", tolerance: searchTolerance, fn: LastJupiterRetrogradeToPrograde},
|
||||||
|
{name: "NextJupiterRetrogradeToPrograde", tolerance: searchTolerance, fn: NextJupiterRetrogradeToPrograde},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
planet: "Saturn",
|
||||||
|
baselineFile: "testdata/saturn_event_baseline.json",
|
||||||
|
samples: saturnEventSamples,
|
||||||
|
phaseCases: []outerPlanetEventCase{
|
||||||
|
{name: "LastSaturnConjunction", tolerance: conjunctionTolerance, fn: LastSaturnConjunction},
|
||||||
|
{name: "NextSaturnConjunction", tolerance: conjunctionTolerance, fn: NextSaturnConjunction},
|
||||||
|
{name: "LastSaturnOpposition", tolerance: conjunctionTolerance, fn: LastSaturnOpposition},
|
||||||
|
{name: "NextSaturnOpposition", tolerance: conjunctionTolerance, fn: NextSaturnOpposition},
|
||||||
|
{name: "LastSaturnEasternQuadrature", tolerance: conjunctionTolerance, fn: LastSaturnEasternQuadrature},
|
||||||
|
{name: "NextSaturnEasternQuadrature", tolerance: conjunctionTolerance, fn: NextSaturnEasternQuadrature},
|
||||||
|
{name: "LastSaturnWesternQuadrature", tolerance: conjunctionTolerance, fn: LastSaturnWesternQuadrature},
|
||||||
|
{name: "NextSaturnWesternQuadrature", tolerance: conjunctionTolerance, fn: NextSaturnWesternQuadrature},
|
||||||
|
},
|
||||||
|
retroCases: []outerPlanetEventCase{
|
||||||
|
{name: "LastSaturnProgradeToRetrograde", tolerance: searchTolerance, fn: LastSaturnProgradeToRetrograde},
|
||||||
|
{name: "NextSaturnProgradeToRetrograde", tolerance: searchTolerance, fn: NextSaturnProgradeToRetrograde},
|
||||||
|
{name: "LastSaturnRetrogradeToPrograde", tolerance: searchTolerance, fn: LastSaturnRetrogradeToPrograde},
|
||||||
|
{name: "NextSaturnRetrogradeToPrograde", tolerance: searchTolerance, fn: NextSaturnRetrogradeToPrograde},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
planet: "Uranus",
|
||||||
|
baselineFile: "testdata/uranus_event_baseline.json",
|
||||||
|
samples: uranusEventSamples,
|
||||||
|
phaseCases: []outerPlanetEventCase{
|
||||||
|
{name: "LastUranusConjunction", tolerance: conjunctionTolerance, fn: LastUranusConjunction},
|
||||||
|
{name: "NextUranusConjunction", tolerance: conjunctionTolerance, fn: NextUranusConjunction},
|
||||||
|
{name: "LastUranusOpposition", tolerance: conjunctionTolerance, fn: LastUranusOpposition},
|
||||||
|
{name: "NextUranusOpposition", tolerance: conjunctionTolerance, fn: NextUranusOpposition},
|
||||||
|
{name: "LastUranusEasternQuadrature", tolerance: conjunctionTolerance, fn: LastUranusEasternQuadrature},
|
||||||
|
{name: "NextUranusEasternQuadrature", tolerance: conjunctionTolerance, fn: NextUranusEasternQuadrature},
|
||||||
|
{name: "LastUranusWesternQuadrature", tolerance: conjunctionTolerance, fn: LastUranusWesternQuadrature},
|
||||||
|
{name: "NextUranusWesternQuadrature", tolerance: conjunctionTolerance, fn: NextUranusWesternQuadrature},
|
||||||
|
},
|
||||||
|
retroCases: []outerPlanetEventCase{
|
||||||
|
{name: "LastUranusProgradeToRetrograde", tolerance: searchTolerance, fn: LastUranusProgradeToRetrograde},
|
||||||
|
{name: "NextUranusProgradeToRetrograde", tolerance: searchTolerance, fn: NextUranusProgradeToRetrograde},
|
||||||
|
{name: "LastUranusRetrogradeToPrograde", tolerance: searchTolerance, fn: LastUranusRetrogradeToPrograde},
|
||||||
|
{name: "NextUranusRetrogradeToPrograde", tolerance: searchTolerance, fn: NextUranusRetrogradeToPrograde},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
planet: "Neptune",
|
||||||
|
baselineFile: "testdata/neptune_event_baseline.json",
|
||||||
|
samples: neptuneEventSamples,
|
||||||
|
phaseCases: []outerPlanetEventCase{
|
||||||
|
{name: "LastNeptuneConjunction", tolerance: conjunctionTolerance, fn: LastNeptuneConjunction},
|
||||||
|
{name: "NextNeptuneConjunction", tolerance: conjunctionTolerance, fn: NextNeptuneConjunction},
|
||||||
|
{name: "LastNeptuneOpposition", tolerance: conjunctionTolerance, fn: LastNeptuneOpposition},
|
||||||
|
{name: "NextNeptuneOpposition", tolerance: conjunctionTolerance, fn: NextNeptuneOpposition},
|
||||||
|
{name: "LastNeptuneEasternQuadrature", tolerance: conjunctionTolerance, fn: LastNeptuneEasternQuadrature},
|
||||||
|
{name: "NextNeptuneEasternQuadrature", tolerance: conjunctionTolerance, fn: NextNeptuneEasternQuadrature},
|
||||||
|
{name: "LastNeptuneWesternQuadrature", tolerance: conjunctionTolerance, fn: LastNeptuneWesternQuadrature},
|
||||||
|
{name: "NextNeptuneWesternQuadrature", tolerance: conjunctionTolerance, fn: NextNeptuneWesternQuadrature},
|
||||||
|
},
|
||||||
|
retroCases: []outerPlanetEventCase{
|
||||||
|
{name: "LastNeptuneProgradeToRetrograde", tolerance: searchTolerance, fn: LastNeptuneProgradeToRetrograde},
|
||||||
|
{name: "NextNeptuneProgradeToRetrograde", tolerance: searchTolerance, fn: NextNeptuneProgradeToRetrograde},
|
||||||
|
{name: "LastNeptuneRetrogradeToPrograde", tolerance: searchTolerance, fn: LastNeptuneRetrogradeToPrograde},
|
||||||
|
{name: "NextNeptuneRetrogradeToPrograde", tolerance: searchTolerance, fn: NextNeptuneRetrogradeToPrograde},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,61 @@
|
|||||||
|
package basic
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"math"
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
type outerPlanetEventBaselineSample struct {
|
||||||
|
InputUTC string `json:"input_utc"`
|
||||||
|
TTJDBits uint64 `json:"tt_jd_bits"`
|
||||||
|
Events map[string]uint64 `json:"events"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type outerPlanetEventBaseline struct {
|
||||||
|
Samples []outerPlanetEventBaselineSample `json:"samples"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadOuterPlanetEventBaseline(t *testing.T, path string) outerPlanetEventBaseline {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
data, err := os.ReadFile(path)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var baseline outerPlanetEventBaseline
|
||||||
|
if err := json.Unmarshal(data, &baseline); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if len(baseline.Samples) == 0 {
|
||||||
|
t.Fatalf("empty baseline: %s", path)
|
||||||
|
}
|
||||||
|
return baseline
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestOuterPlanetEventBaselineRegression(t *testing.T) {
|
||||||
|
for _, plan := range outerPlanetEventPlans() {
|
||||||
|
t.Run(plan.planet, func(t *testing.T) {
|
||||||
|
baseline := loadOuterPlanetEventBaseline(t, plan.baselineFile)
|
||||||
|
cases := plan.allCases()
|
||||||
|
|
||||||
|
for _, sample := range baseline.Samples {
|
||||||
|
jd := math.Float64frombits(sample.TTJDBits)
|
||||||
|
for _, event := range cases {
|
||||||
|
wantBits, ok := sample.Events[event.name]
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("%s missing baseline event %s", sample.InputUTC, event.name)
|
||||||
|
}
|
||||||
|
want := math.Float64frombits(wantBits)
|
||||||
|
got := event.fn(jd)
|
||||||
|
diff := math.Abs(got - want)
|
||||||
|
if diff > event.tolerance {
|
||||||
|
t.Fatalf("%s %s diff %.12f > tolerance %.12f", sample.InputUTC, event.name, diff, event.tolerance)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user