Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
c8dd777a7b
|
|||
|
46b555cd49
|
|||
|
be3af3884c
|
|||
|
34ff6a36ae
|
|||
|
d40c4dfcd9
|
|||
|
bec7b8a0d8
|
|||
|
3ffdbe0034
|
@@ -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.
|
||||
+1842
File diff suppressed because it is too large
Load Diff
@@ -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(GetSunSetTime(jde, 115, 32, 8, 1, 10)))
|
||||
fmt.Println("当前晨影 -6:", JDE2Date(MorningTwilight(jde, 115, 32, 8, -6)))
|
||||
fmt.Println("当前晨影 -12:", JDE2Date(MorningTwilight(jde, 115, 32, 8, -12)))
|
||||
fmt.Println("当前昏影 -6:", JDE2Date(EveningTwilight(jde, 115, 32, 8, -6)))
|
||||
fmt.Println("当前昏影 -12:", JDE2Date(EveningTwilight(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(GetMoonSetTime(jde, 115, 32, 8, 1, 10)))
|
||||
fmt.Println("月相:", MoonPhase(jde-8.0/24.0))
|
||||
}
|
||||
+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,450 +0,0 @@
|
||||
package basic
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
var defDeltaTFn = DefaultDeltaTv2
|
||||
|
||||
// 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)
|
||||
}
|
||||
|
||||
/*
|
||||
@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 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)
|
||||
DT := 42.184
|
||||
for i := 0; i < n; i++ {
|
||||
if jd > jdLeaps[i] {
|
||||
DT += float64(n - i - 1)
|
||||
break
|
||||
}
|
||||
}
|
||||
return DT
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
+32
-211
@@ -1,249 +1,70 @@
|
||||
package basic
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"math"
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestGenerateMagic(t *testing.T) {
|
||||
generateMagicNumber()
|
||||
}
|
||||
func generateMagicNumber() {
|
||||
//0月份 00000 日期 0000闰月 0000000000000 农历信息
|
||||
var tz = 8.0000 / 24.000
|
||||
yearMap := make(map[int][][]int) // {1 month,1 leap,2 29/30}
|
||||
spYear := make(map[int][]int)
|
||||
var upper []uint16
|
||||
var lower []uint16
|
||||
var full []uint32
|
||||
//var info uint32 = 0
|
||||
for year := 1899; year <= 2401; year++ {
|
||||
fmt.Println(year)
|
||||
jieqi := GetJieqiLoops(year, 25) //一年的节气
|
||||
moon := GetMoonLoops(float64(year), 17) //一年朔月日
|
||||
winter1 := jieqi[0] - 8.0/24 + tz //第一年冬至日
|
||||
winter2 := jieqi[24] - 8.0/24 + tz //第二年冬至日
|
||||
for idx, v := range moon {
|
||||
if tz != 8.0/24 {
|
||||
v = v - 8.0/24 + tz
|
||||
}
|
||||
if v-math.Floor(v) > 0.5 {
|
||||
moon[idx] = math.Floor(v) + 0.5
|
||||
} else {
|
||||
moon[idx] = math.Floor(v) - 0.5
|
||||
}
|
||||
} //置闰月为0点
|
||||
for idx, v := range jieqi {
|
||||
if tz != 8.0/24 {
|
||||
v = v - 8.0/24 + tz
|
||||
}
|
||||
if v-math.Floor(v) > 0.5 {
|
||||
jieqi[idx] = math.Floor(v) + 0.5
|
||||
} else {
|
||||
jieqi[idx] = math.Floor(v) - 0.5
|
||||
}
|
||||
} //置节气为0点
|
||||
mooncount := 0 //年内朔望月计数
|
||||
var min, max int = 20, 0 //最大最小计数
|
||||
for i := 0; i < 15; i++ {
|
||||
if moon[i] >= winter1 && moon[i] < winter2 {
|
||||
if i <= min {
|
||||
min = i
|
||||
}
|
||||
if i >= max {
|
||||
max = i
|
||||
}
|
||||
mooncount++
|
||||
}
|
||||
}
|
||||
leapmonth := 20
|
||||
if mooncount == 13 { //存在闰月
|
||||
var j, i = 2, 0
|
||||
for i = min; i <= max; i++ {
|
||||
if !(moon[i] <= jieqi[j] && moon[i+1] > jieqi[j]) {
|
||||
break
|
||||
}
|
||||
j += 2
|
||||
}
|
||||
leapmonth = i - min + 1
|
||||
}
|
||||
month := 11
|
||||
for idx := min; idx <= max; idx++ {
|
||||
leap := 0
|
||||
if idx != leapmonth {
|
||||
month++
|
||||
if month > 12 {
|
||||
month -= 12
|
||||
}
|
||||
} else {
|
||||
leap = 1
|
||||
}
|
||||
if leap == 0 && month == 1 {
|
||||
cp := JDE2Date(moon[idx])
|
||||
spYear[year+1] = append(spYear[year+1], []int{int(cp.Month()), cp.Day()}...)
|
||||
}
|
||||
if idx < 6 && month > 10 {
|
||||
yearMap[year] = append(yearMap[year], []int{month, leap, int(moon[idx+1] - moon[idx])})
|
||||
} else {
|
||||
yearMap[year+1] = append(yearMap[year+1], []int{month, leap, int(moon[idx+1] - moon[idx])})
|
||||
}
|
||||
}
|
||||
}
|
||||
for year := 1900; year <= 2400; year++ {
|
||||
fmt.Println(year)
|
||||
magic := magicNumber(yearMap[year], spYear[year])
|
||||
up, low := magicNumberSpilt(magic)
|
||||
upper = append(upper, up)
|
||||
lower = append(lower, uint16(low))
|
||||
full = append(full, uint32(magic))
|
||||
}
|
||||
res := make(map[string]interface{})
|
||||
res["up"] = upper
|
||||
res["low"] = lower
|
||||
res["full"] = full
|
||||
d, _ := json.Marshal(res)
|
||||
os.WriteFile("test.json", d, 0644)
|
||||
}
|
||||
|
||||
func magicNumber(y1 [][]int, y2 []int) int32 {
|
||||
var res int32
|
||||
res = int32(y2[1]) << 18
|
||||
if y2[0] == 2 {
|
||||
res = res | 0x800000
|
||||
}
|
||||
for idx, v := range y1 {
|
||||
if v[2] == 30 {
|
||||
res = res | (1 << (13 - idx))
|
||||
}
|
||||
if v[1] == 1 {
|
||||
res = (res & 0xFC3FFF) | int32((v[0])<<14)
|
||||
}
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
func magicNumberSpilt(magic int32) (uint16, uint8) {
|
||||
var upper uint16
|
||||
var lower uint8
|
||||
lower = uint8(magic)
|
||||
upper = uint16(magic >> 8)
|
||||
return upper, lower
|
||||
}
|
||||
|
||||
func TestGetJQTime(t *testing.T) {
|
||||
originalFunc := func(Year, Angle int) float64 { //节气时间
|
||||
var j int = 1
|
||||
var Day int
|
||||
var tp float64
|
||||
if Angle%2 == 0 {
|
||||
Day = 18
|
||||
originalFunc := func(year, angle int) float64 {
|
||||
const iterations = 1
|
||||
|
||||
var day int
|
||||
if angle%2 == 0 {
|
||||
day = 18
|
||||
} else {
|
||||
Day = 3
|
||||
day = 3
|
||||
}
|
||||
if Angle%10 != 0 {
|
||||
tp = float64(Angle+15.0) / 30.0
|
||||
|
||||
month := 3.0
|
||||
if angle%10 != 0 {
|
||||
month += float64(angle+15) / 30.0
|
||||
} else {
|
||||
tp = float64(Angle) / 30.0
|
||||
month += float64(angle) / 30.0
|
||||
}
|
||||
Month := 3 + tp
|
||||
if Month > 12 {
|
||||
Month -= 12
|
||||
if month > 12 {
|
||||
month -= 12
|
||||
}
|
||||
JD1 := JDECalc(int(Year), int(Month), float64(Day))
|
||||
if Angle == 0 {
|
||||
Angle = 360
|
||||
|
||||
jd := JDECalc(year, int(month), float64(day))
|
||||
if angle == 0 {
|
||||
angle = 360
|
||||
}
|
||||
for i := 0; i < j; i++ {
|
||||
|
||||
for i := 0; i < iterations; i++ {
|
||||
for {
|
||||
JD0 := JD1
|
||||
stDegree := JQLospec(JD0, float64(Angle)) - float64(Angle)
|
||||
stDegreep := (JQLospec(JD0+0.000005, float64(Angle)) - JQLospec(JD0-0.000005, float64(Angle))) / 0.00001
|
||||
JD1 = JD0 - stDegree/stDegreep
|
||||
if math.Abs(JD1-JD0) <= 0.00001 {
|
||||
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
|
||||
}
|
||||
}
|
||||
JD1 -= 0.001
|
||||
jd -= 0.001
|
||||
}
|
||||
JD1 += 0.001
|
||||
return TD2UT(JD1, false)
|
||||
jd += 0.001
|
||||
return TD2UT(jd, false)
|
||||
}
|
||||
|
||||
// 测试数据:年份从1900-2200抽样,角度覆盖关键值
|
||||
testCases := []struct {
|
||||
year, angle int
|
||||
year int
|
||||
angle int
|
||||
}{
|
||||
// 边界年份
|
||||
{1900, 0}, {1900, 15}, {1900, 30}, {1900, 45}, {1900, 90},
|
||||
{1900, 180}, {1900, 270}, {1900, 360},
|
||||
|
||||
// 中间年份抽样
|
||||
{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},
|
||||
{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},
|
||||
}
|
||||
|
||||
// 执行测试
|
||||
allPassed := true
|
||||
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.Errorf("测试失败: year=%d, angle=%d\n原始结果: %.15f\n优化结果: %.15f\n差异: %.15f",
|
||||
t.Fatalf("GetJQTime mismatch: year=%d angle=%d original=%.15f optimized=%.15f diff=%.15f",
|
||||
tc.year, tc.angle, originalResult, optimizedResult, diff)
|
||||
allPassed = false
|
||||
} else {
|
||||
t.Logf("测试通过: year=%d, angle=%d, 结果: %.15f",
|
||||
tc.year, tc.angle, optimizedResult)
|
||||
}
|
||||
}
|
||||
|
||||
if allPassed {
|
||||
t.Log("所有测试用例通过!优化函数与原始函数结果完全一致")
|
||||
}
|
||||
}
|
||||
|
||||
func TestJQ(t *testing.T) {
|
||||
fmt.Println(GetJQTime(-721, 15))
|
||||
}
|
||||
|
||||
func TestCal6402(t *testing.T) {
|
||||
var year = 6402
|
||||
var tz = 8.00 / 24.00
|
||||
winterSolsticeDay := GetJQTime(year, 270) + tz
|
||||
firstNewMoonDay := TD2UT(CalcMoonSHByJDE(winterSolsticeDay-15, 0), false) + tz
|
||||
nextNewMoonDay := TD2UT(CalcMoonSHByJDE(firstNewMoonDay+28, 0), false) + tz
|
||||
fmt.Println(JDE2Date(firstNewMoonDay))
|
||||
fmt.Println(JDE2Date(nextNewMoonDay))
|
||||
fmt.Println(HSunTrueLo(TD2UT(nextNewMoonDay, false)))
|
||||
fmt.Println(HMoonTrueLo(TD2UT(nextNewMoonDay, false)))
|
||||
firstNewMoonDay = normalizeTimePoint(firstNewMoonDay)
|
||||
nextNewMoonDay = normalizeTimePoint(nextNewMoonDay)
|
||||
fmt.Println(JDE2Date(winterSolsticeDay))
|
||||
fmt.Println(JDE2Date(GetSolar(1984, 10, 2, true, 8.0/24.0)))
|
||||
fmt.Println(GetLunar(1992, 11, 24, 8.0/24.0))
|
||||
fmt.Println(GetLunar(6402, 12, 24, 8.0/24.0))
|
||||
for i := 1; i <= 12; i++ {
|
||||
fmt.Print("6403", i, "24 ---- ")
|
||||
fmt.Println(GetLunar(6403, i, 24, 8.0/24.0))
|
||||
}
|
||||
fmt.Println("-------")
|
||||
for _, v := range GetMoonLoops(float64(2132), 17) {
|
||||
fmt.Println(JDE2Date(v))
|
||||
}
|
||||
fmt.Println("-------")
|
||||
for _, v := range GetJieqiLoops(2132, 25) {
|
||||
fmt.Println(JDE2Date(v))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
)
|
||||
|
||||
func Test_Isxz(t *testing.T) {
|
||||
func TestConstellationNameZH(t *testing.T) {
|
||||
now := GetNowJDE()
|
||||
//finish on 30s
|
||||
for i := 0.00; i <= 360.00; i += 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()
|
||||
for i := 0; i < b.N; i++ {
|
||||
//GetNowJDE()
|
||||
WhichCst(11.11, 12.12, jde)
|
||||
ConstellationNameZH(11.11, 12.12, jde)
|
||||
}
|
||||
|
||||
}
|
||||
+25
-25
@@ -10,7 +10,7 @@ import (
|
||||
* 坐标变换,黄道转赤道
|
||||
*/
|
||||
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
|
||||
if ra < 0 {
|
||||
ra += 360
|
||||
@@ -19,13 +19,13 @@ func LoToRa(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
|
||||
}
|
||||
|
||||
func LoBoToRaDec(jde, lo, bo float64) (float64, float64) {
|
||||
dec := ArcSin(Sin(bo)*Cos(Sita(jde)) + Cos(bo)*Sin(Sita(jde))*Sin(lo))
|
||||
ra := math.Atan2(Sin(lo)*Cos(Sita(jde))-Tan(bo)*Sin(Sita(jde)), Cos(lo))
|
||||
dec := ArcSin(Sin(bo)*Cos(TrueObliquity(jde)) + Cos(bo)*Sin(TrueObliquity(jde))*Sin(lo))
|
||||
ra := math.Atan2(Sin(lo)*Cos(TrueObliquity(jde))-Tan(bo)*Sin(TrueObliquity(jde)), Cos(lo))
|
||||
ra = ra * 180 / math.Pi
|
||||
if ra < 0 {
|
||||
ra += 360
|
||||
@@ -36,9 +36,9 @@ func LoBoToRaDec(jde, lo, bo float64) (float64, float64) {
|
||||
func RaDecToLoBo(jde, ra, dec float64) (float64, float64) {
|
||||
//tan(λ) = (sin(α)*cos(ε) + tan(δ)*sin(ε)) / cos(α)
|
||||
//sin(β)=sin(δ)*cos(ε)-cos(δ)*sin(ε)*sin(α)
|
||||
sita := Sita(jde)
|
||||
sinBo := Sin(dec)*Cos(sita) - Cos(dec)*Sin(sita)*Sin(ra)
|
||||
lo := math.Atan2((Sin(ra)*Cos(sita) + Tan(dec)*Sin(sita)), Cos(ra))
|
||||
eps := TrueObliquity(jde)
|
||||
sinBo := Sin(dec)*Cos(eps) - Cos(dec)*Sin(eps)*Sin(ra)
|
||||
lo := math.Atan2((Sin(ra)*Cos(eps) + Tan(dec)*Sin(eps)), Cos(ra))
|
||||
lo = Limit360(lo * 180 / math.Pi)
|
||||
return lo, ArcSin(sinBo)
|
||||
}
|
||||
@@ -46,8 +46,8 @@ func RaDecToLoBo(jde, ra, dec float64) (float64, float64) {
|
||||
func RaToLo(jde, ra, dec float64) float64 {
|
||||
//tan(λ) = (sin(α)*cos(ε) + tan(δ)*sin(ε)) / cos(α)
|
||||
//sin(β)=sin(δ)*cos(ε)-cos(δ)*sin(ε)*sin(α)
|
||||
sita := Sita(jde)
|
||||
lo := math.Atan2((Sin(ra)*Cos(sita) + Tan(dec)*Sin(sita)), Cos(ra))
|
||||
eps := TrueObliquity(jde)
|
||||
lo := math.Atan2((Sin(ra)*Cos(eps) + Tan(dec)*Sin(eps)), Cos(ra))
|
||||
lo = Limit360(lo * 180 / math.Pi)
|
||||
return lo
|
||||
}
|
||||
@@ -55,8 +55,8 @@ func RaToLo(jde, ra, dec float64) float64 {
|
||||
func DecToBo(jde, ra, dec float64) float64 {
|
||||
//tan(λ) = (sin(α)*cos(ε) + tan(δ)*sin(ε)) / cos(α)
|
||||
//sin(β)=sin(δ)*cos(ε)-cos(δ)*sin(ε)*sin(α)
|
||||
sita := Sita(jde)
|
||||
sinBo := Sin(dec)*Cos(sita) - Cos(dec)*Sin(sita)*Sin(ra)
|
||||
eps := TrueObliquity(jde)
|
||||
sinBo := Sin(dec)*Cos(eps) - Cos(dec)*Sin(eps)*Sin(ra)
|
||||
return ArcSin(sinBo)
|
||||
}
|
||||
|
||||
@@ -80,7 +80,7 @@ func psini(lat, h float64) float64 {
|
||||
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
|
||||
pcosi := pcosi(lat, h)
|
||||
psini := psini(lat, h)
|
||||
@@ -91,14 +91,14 @@ func ZhanXinRaDec(ra, dec, lat, lon, jd, au, h float64) (float64, float64) {
|
||||
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
|
||||
pcosi := pcosi(lat, h)
|
||||
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
|
||||
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
|
||||
pcosi := pcosi(lat, h)
|
||||
@@ -110,26 +110,26 @@ func ZhanXinDec(ra, dec, lat, lon, jd, au, h float64) float64 { //jd为格林尼
|
||||
return ndec
|
||||
}
|
||||
|
||||
func ZhanXinLo(lo, bo, lat, lon, jd, au, h float64) float64 { //jd为格林尼治标准时
|
||||
C := pcosi(lat, h)
|
||||
S := psini(lat, h)
|
||||
func TopocentricLo(lo, bo, lat, lon, jd, au, h float64) float64 { //jd为格林尼治标准时
|
||||
c := pcosi(lat, h)
|
||||
s := psini(lat, h)
|
||||
sinpi := Sin(0.0024427777777) / au
|
||||
ra := LoToRa(jd, lo, bo)
|
||||
tH := Limit360(TD2UT(ApparentSiderealTime(jd), false)*15 + lon - ra)
|
||||
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
|
||||
n := Cos(lo)*Cos(bo) - c*sinpi*Cos(tH)
|
||||
nlo := math.Atan2(Sin(lo)*Cos(bo)-sinpi*(s*Sin(TrueObliquity(jd))+c*Cos(TrueObliquity(jd))*Sin(tH)), n) * 180 / math.Pi
|
||||
return nlo
|
||||
}
|
||||
|
||||
func ZhanXinBo(lo, bo, lat, lon, jd, au, h float64) float64 { //jd为格林尼治标准时
|
||||
C := pcosi(lat, h)
|
||||
S := psini(lat, h)
|
||||
func TopocentricBo(lo, bo, lat, lon, jd, au, h float64) float64 { //jd为格林尼治标准时
|
||||
c := pcosi(lat, h)
|
||||
s := psini(lat, h)
|
||||
sinpi := Sin(0.0024427777777) / au
|
||||
ra := LoToRa(jd, lo, bo)
|
||||
tH := Limit360(TD2UT(ApparentSiderealTime(jd), false)*15 + lon - ra)
|
||||
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
|
||||
nbo := math.Atan2(Cos(nlo)*(Sin(bo)-sinpi*(S*Cos(Sita(jd))-C*Sin(Sita(jd))*Sin(tH))), N) * 180 / math.Pi
|
||||
n := Cos(lo)*Cos(bo) - c*sinpi*Cos(tH)
|
||||
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(TrueObliquity(jd))-c*Sin(TrueObliquity(jd))*Sin(tH))), n) * 180 / math.Pi
|
||||
return nbo
|
||||
}
|
||||
|
||||
|
||||
@@ -1,46 +0,0 @@
|
||||
package basic
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
)
|
||||
|
||||
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 = Precess(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
|
||||
|
||||
import (
|
||||
. "b612.me/astro/tools"
|
||||
"math"
|
||||
|
||||
. "b612.me/astro/tools"
|
||||
)
|
||||
|
||||
//地球常数
|
||||
@@ -20,19 +21,19 @@ func HeightDistance(height float64) float64 {
|
||||
|
||||
// HeightDistance 高度(单位:米)与地平线下角度的关系(单位:度)
|
||||
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 不同纬度下高度与地平线距离的关系(单位:米)
|
||||
func HeightDistanceByLat(height, lat float64) float64 {
|
||||
raduis := GeocentricRadius(lat)
|
||||
return math.Acos((raduis)/(raduis+height)) * raduis
|
||||
radius := GeocentricRadius(lat)
|
||||
return math.Acos((radius)/(radius+height)) * radius
|
||||
}
|
||||
|
||||
// HeightDegreeByLat 不同纬度下高度(单位:米)与地平线下角度的关系(单位:度)
|
||||
func HeightDegreeByLat(height, lat float64) float64 {
|
||||
raduis := GeocentricRadius(lat)
|
||||
return math.Acos((raduis)/(raduis+height)) * 180 / math.Pi / 2
|
||||
radius := GeocentricRadius(lat)
|
||||
return math.Acos((radius)/(radius+height)) * 180 / math.Pi
|
||||
}
|
||||
|
||||
// GeocentricRadius 地心直径与纬度的关系
|
||||
|
||||
+26
-5
@@ -1,13 +1,34 @@
|
||||
package basic
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func Test_EarthFn(t *testing.T) {
|
||||
fmt.Println(HeightDistance(10000))
|
||||
//近似算法,差距在接受范围内?
|
||||
fmt.Println(math.Sqrt(((EARTH_AVERAGE_RADIUS)*2 + 10000) * 10000))
|
||||
func TestHeightDegreeMatchesSphericalArc(t *testing.T) {
|
||||
height := 10000.0
|
||||
got := HeightDegree(height)
|
||||
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"
|
||||
)
|
||||
|
||||
func JupiterL(JD float64) float64 {
|
||||
return planet.WherePlanet(4, 0, JD)
|
||||
func JupiterL(jd float64) float64 {
|
||||
return planet.WherePlanet(4, 0, jd)
|
||||
}
|
||||
|
||||
func JupiterB(JD float64) float64 {
|
||||
return planet.WherePlanet(4, 1, JD)
|
||||
func JupiterB(jd float64) float64 {
|
||||
return planet.WherePlanet(4, 1, jd)
|
||||
}
|
||||
func JupiterR(JD float64) float64 {
|
||||
return planet.WherePlanet(4, 2, JD)
|
||||
func JupiterR(jd float64) float64 {
|
||||
return planet.WherePlanet(4, 2, jd)
|
||||
}
|
||||
func AJupiterX(JD float64) float64 {
|
||||
l := JupiterL(JD)
|
||||
b := JupiterB(JD)
|
||||
r := JupiterR(JD)
|
||||
el := planet.WherePlanet(-1, 0, JD)
|
||||
eb := planet.WherePlanet(-1, 1, JD)
|
||||
er := planet.WherePlanet(-1, 2, JD)
|
||||
func AJupiterX(jd float64) float64 {
|
||||
l := JupiterL(jd)
|
||||
b := JupiterB(jd)
|
||||
r := JupiterR(jd)
|
||||
el := planet.WherePlanet(-1, 0, jd)
|
||||
eb := planet.WherePlanet(-1, 1, jd)
|
||||
er := planet.WherePlanet(-1, 2, jd)
|
||||
x := r*Cos(b)*Cos(l) - er*Cos(eb)*Cos(el)
|
||||
return x
|
||||
}
|
||||
|
||||
func AJupiterY(JD float64) float64 {
|
||||
func AJupiterY(jd float64) float64 {
|
||||
|
||||
l := JupiterL(JD)
|
||||
b := JupiterB(JD)
|
||||
r := JupiterR(JD)
|
||||
el := planet.WherePlanet(-1, 0, JD)
|
||||
eb := planet.WherePlanet(-1, 1, JD)
|
||||
er := planet.WherePlanet(-1, 2, JD)
|
||||
l := JupiterL(jd)
|
||||
b := JupiterB(jd)
|
||||
r := JupiterR(jd)
|
||||
el := planet.WherePlanet(-1, 0, jd)
|
||||
eb := planet.WherePlanet(-1, 1, jd)
|
||||
er := planet.WherePlanet(-1, 2, jd)
|
||||
y := r*Cos(b)*Sin(l) - er*Cos(eb)*Sin(el)
|
||||
return y
|
||||
}
|
||||
func AJupiterZ(JD float64) float64 {
|
||||
//l := JupiterL(JD)
|
||||
b := JupiterB(JD)
|
||||
r := JupiterR(JD)
|
||||
// el := planet.WherePlanet(-1, 0, JD)
|
||||
eb := planet.WherePlanet(-1, 1, JD)
|
||||
er := planet.WherePlanet(-1, 2, JD)
|
||||
func AJupiterZ(jd float64) float64 {
|
||||
//l := JupiterL(jd)
|
||||
b := JupiterB(jd)
|
||||
r := JupiterR(jd)
|
||||
// el := planet.WherePlanet(-1, 0, jd)
|
||||
eb := planet.WherePlanet(-1, 1, jd)
|
||||
er := planet.WherePlanet(-1, 2, jd)
|
||||
z := r*Sin(b) - er*Sin(eb)
|
||||
return z
|
||||
}
|
||||
|
||||
func AJupiterXYZ(JD float64) (float64, float64, float64) {
|
||||
l := JupiterL(JD)
|
||||
b := JupiterB(JD)
|
||||
r := JupiterR(JD)
|
||||
el := planet.WherePlanet(-1, 0, JD)
|
||||
eb := planet.WherePlanet(-1, 1, JD)
|
||||
er := planet.WherePlanet(-1, 2, JD)
|
||||
func AJupiterXYZ(jd float64) (float64, float64, float64) {
|
||||
l := JupiterL(jd)
|
||||
b := JupiterB(jd)
|
||||
r := JupiterR(jd)
|
||||
el := planet.WherePlanet(-1, 0, jd)
|
||||
eb := planet.WherePlanet(-1, 1, jd)
|
||||
er := planet.WherePlanet(-1, 2, jd)
|
||||
x := r*Cos(b)*Cos(l) - er*Cos(eb)*Cos(el)
|
||||
y := r*Cos(b)*Sin(l) - er*Cos(eb)*Sin(el)
|
||||
z := r*Sin(b) - er*Sin(eb)
|
||||
return x, y, z
|
||||
}
|
||||
|
||||
func JupiterApparentRa(JD float64) float64 {
|
||||
lo, bo := JupiterApparentLoBo(JD)
|
||||
sita := Sita(JD)
|
||||
ra := math.Atan2((Sin(lo)*Cos(sita) - Tan(bo)*Sin(sita)), Cos(lo))
|
||||
func JupiterApparentRa(jd float64) float64 {
|
||||
lo, bo := JupiterApparentLoBo(jd)
|
||||
eps := TrueObliquity(jd)
|
||||
ra := math.Atan2((Sin(lo)*Cos(eps) - Tan(bo)*Sin(eps)), Cos(lo))
|
||||
ra = ra * 180 / math.Pi
|
||||
return Limit360(ra)
|
||||
}
|
||||
func JupiterApparentDec(JD float64) float64 {
|
||||
lo, bo := JupiterApparentLoBo(JD)
|
||||
sita := Sita(JD)
|
||||
dec := ArcSin(Sin(bo)*Cos(sita) + Cos(bo)*Sin(sita)*Sin(lo))
|
||||
func JupiterApparentDec(jd float64) float64 {
|
||||
lo, bo := JupiterApparentLoBo(jd)
|
||||
eps := TrueObliquity(jd)
|
||||
dec := ArcSin(Sin(bo)*Cos(eps) + Cos(bo)*Sin(eps)*Sin(lo))
|
||||
return dec
|
||||
}
|
||||
|
||||
func JupiterApparentRaDec(JD float64) (float64, float64) {
|
||||
lo, bo := JupiterApparentLoBo(JD)
|
||||
sita := Sita(JD)
|
||||
ra := math.Atan2((Sin(lo)*Cos(sita) - Tan(bo)*Sin(sita)), Cos(lo))
|
||||
func JupiterApparentRaDec(jd float64) (float64, float64) {
|
||||
lo, bo := JupiterApparentLoBo(jd)
|
||||
eps := TrueObliquity(jd)
|
||||
ra := math.Atan2((Sin(lo)*Cos(eps) - Tan(bo)*Sin(eps)), Cos(lo))
|
||||
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
|
||||
}
|
||||
|
||||
func EarthJupiterAway(JD float64) float64 {
|
||||
x, y, z := AJupiterXYZ(JD)
|
||||
to := math.Sqrt(x*x + y*y + z*z)
|
||||
return to
|
||||
func EarthJupiterAway(jd float64) float64 {
|
||||
return planetEarthAwayExplicitN(4, jd, -1)
|
||||
}
|
||||
|
||||
func JupiterApparentLo(JD float64) float64 {
|
||||
x, y, z := AJupiterXYZ(JD)
|
||||
to := 0.0057755183 * math.Sqrt(x*x+y*y+z*z)
|
||||
x, y, z = AJupiterXYZ(JD - to)
|
||||
lo := math.Atan2(y, x)
|
||||
bo := math.Atan2(z, math.Sqrt(x*x+y*y))
|
||||
lo = lo * 180 / math.Pi
|
||||
bo = bo * 180 / math.Pi
|
||||
lo = Limit360(lo)
|
||||
//lo-=GXCLo(lo,bo,JD)/3600;
|
||||
//bo+=GXCBo(lo,bo,JD);
|
||||
lo += Nutation2000Bi(JD)
|
||||
return lo
|
||||
func JupiterApparentLo(jd float64) float64 {
|
||||
geo, _ := planetApparentGeocentricPositionN(4, jd, -1)
|
||||
return geo.lo
|
||||
}
|
||||
|
||||
func JupiterApparentBo(JD float64) float64 {
|
||||
x, y, z := AJupiterXYZ(JD)
|
||||
to := 0.0057755183 * math.Sqrt(x*x+y*y+z*z)
|
||||
x, y, z = AJupiterXYZ(JD - to)
|
||||
//lo := math.Atan2(y, x)
|
||||
bo := math.Atan2(z, math.Sqrt(x*x+y*y))
|
||||
//lo = lo * 180 / math.Pi
|
||||
bo = bo * 180 / math.Pi
|
||||
//lo+=GXCLo(lo,bo,JD);
|
||||
//bo+=GXCBo(lo,bo,JD)/3600;
|
||||
//lo+=Nutation2000Bi(JD);
|
||||
return bo
|
||||
func JupiterApparentBo(jd float64) float64 {
|
||||
geo, _ := planetApparentGeocentricPositionN(4, jd, -1)
|
||||
return geo.bo
|
||||
}
|
||||
|
||||
func JupiterApparentLoBo(JD float64) (float64, float64) {
|
||||
x, y, z := AJupiterXYZ(JD)
|
||||
to := 0.0057755183 * math.Sqrt(x*x+y*y+z*z)
|
||||
x, y, z = AJupiterXYZ(JD - to)
|
||||
lo := math.Atan2(y, x)
|
||||
bo := math.Atan2(z, math.Sqrt(x*x+y*y))
|
||||
lo = lo * 180 / math.Pi
|
||||
bo = bo * 180 / math.Pi
|
||||
lo = Limit360(lo)
|
||||
//lo-=GXCLo(lo,bo,JD)/3600;
|
||||
//bo+=GXCBo(lo,bo,JD);
|
||||
lo += Nutation2000Bi(JD)
|
||||
return lo, bo
|
||||
func JupiterApparentLoBo(jd float64) (float64, float64) {
|
||||
geo, _ := planetApparentGeocentricPositionN(4, jd, -1)
|
||||
return geo.lo, geo.bo
|
||||
}
|
||||
|
||||
func JupiterMag(JD float64) float64 {
|
||||
AwaySun := JupiterR(JD)
|
||||
AwayEarth := EarthJupiterAway(JD)
|
||||
Away := planet.WherePlanet(-1, 2, JD)
|
||||
i := (AwaySun*AwaySun + AwayEarth*AwayEarth - Away*Away) / (2 * AwaySun * AwayEarth)
|
||||
func JupiterMag(jd float64) float64 {
|
||||
sunDistance := JupiterR(jd)
|
||||
earthDistance := EarthJupiterAway(jd)
|
||||
earthSunDistance := planet.WherePlanet(-1, 2, jd)
|
||||
i := (sunDistance*sunDistance + earthDistance*earthDistance - earthSunDistance*earthSunDistance) / (2 * sunDistance * earthDistance)
|
||||
i = ArcCos(i)
|
||||
Mag := -9.40 + 5*math.Log10(AwaySun*AwayEarth) + 0.0005*i
|
||||
return FloatRound(Mag, 2)
|
||||
mag := -9.40 + 5*math.Log10(sunDistance*earthDistance) + 0.0005*i
|
||||
return FloatRound(mag, 2)
|
||||
}
|
||||
|
||||
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))
|
||||
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)
|
||||
sinHeight := 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(hourAngle)
|
||||
return ArcSin(sinHeight)
|
||||
}
|
||||
|
||||
@@ -167,271 +136,63 @@ func JupiterAzimuth(jde, lon, lat, timezone float64) float64 {
|
||||
ra, dec := JupiterApparentRaDec(TD2UT(utcJde, true))
|
||||
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))
|
||||
Azimuth := ArcTan(tanAzimuth)
|
||||
if Azimuth < 0 {
|
||||
if H/15 < 12 {
|
||||
return Azimuth + 360
|
||||
tanAzimuth := Sin(hourAngle) / (Cos(hourAngle)*Sin(lat) - Tan(dec)*Cos(lat))
|
||||
azimuth := ArcTan(tanAzimuth)
|
||||
if azimuth < 0 {
|
||||
if hourAngle/15 < 12 {
|
||||
return azimuth + 360
|
||||
}
|
||||
return Azimuth + 180
|
||||
return azimuth + 180
|
||||
}
|
||||
if H/15 < 12 {
|
||||
return Azimuth + 180
|
||||
if hourAngle/15 < 12 {
|
||||
return azimuth + 180
|
||||
}
|
||||
return Azimuth
|
||||
return azimuth
|
||||
}
|
||||
|
||||
func JupiterHourAngle(JD, Lon, TZ float64) float64 {
|
||||
startime := Limit360(ApparentSiderealTime(JD-TZ/24)*15 + Lon)
|
||||
timeangle := startime - JupiterApparentRa(TD2UT(JD-TZ/24.0, true))
|
||||
if timeangle < 0 {
|
||||
timeangle += 360
|
||||
func JupiterHourAngle(jd, lon, timezone float64) float64 {
|
||||
siderealLongitude := Limit360(ApparentSiderealTime(jd-timezone/24)*15 + lon)
|
||||
hourAngle := siderealLongitude - JupiterApparentRa(TD2UT(jd-timezone/24.0, true))
|
||||
if hourAngle < 0 {
|
||||
hourAngle += 360
|
||||
}
|
||||
return timeangle
|
||||
return hourAngle
|
||||
}
|
||||
|
||||
func JupiterCulminationTime(jde, lon, timezone float64) float64 {
|
||||
//jde 世界时,非力学时,当地时区 0时,无需转换力学时
|
||||
//ra,dec 瞬时天球座标,非J2000等时间天球坐标
|
||||
jde = math.Floor(jde) + 0.5
|
||||
JD1 := jde + Limit360(360-JupiterHourAngle(jde, lon, timezone))/15.0/24.0*0.99726851851851851851
|
||||
limitHA := func(jde, lon, timezone float64) float64 {
|
||||
ha := JupiterHourAngle(jde, lon, timezone)
|
||||
if ha < 180 {
|
||||
ha += 360
|
||||
estimateJD := jde + Limit360(360-JupiterHourAngle(jde, lon, timezone))/15.0/24.0*0.99726851851851851851
|
||||
normalizedHourAngle := func(jde, lon, timezone float64) float64 {
|
||||
currentHourAngle := JupiterHourAngle(jde, lon, timezone)
|
||||
if currentHourAngle < 180 {
|
||||
currentHourAngle += 360
|
||||
}
|
||||
return ha
|
||||
return currentHourAngle
|
||||
}
|
||||
for {
|
||||
JD0 := JD1
|
||||
stDegree := limitHA(JD0, lon, timezone) - 360
|
||||
stDegreep := (limitHA(JD0+0.000005, lon, timezone) - limitHA(JD0-0.000005, lon, timezone)) / 0.00001
|
||||
JD1 = JD0 - stDegree/stDegreep
|
||||
if math.Abs(JD1-JD0) <= 0.00001 {
|
||||
prevJD := estimateJD
|
||||
hourAngleDelta := normalizedHourAngle(prevJD, lon, timezone) - 360
|
||||
hourAngleSlope := (normalizedHourAngle(prevJD+0.000005, lon, timezone) - normalizedHourAngle(prevJD-0.000005, lon, timezone)) / 0.00001
|
||||
estimateJD = prevJD - hourAngleDelta/hourAngleSlope
|
||||
if math.Abs(estimateJD-prevJD) <= 0.00001 {
|
||||
break
|
||||
}
|
||||
}
|
||||
return JD1
|
||||
return estimateJD
|
||||
}
|
||||
|
||||
func JupiterRiseTime(JD, Lon, Lat, TZ, ZS, HEI float64) float64 {
|
||||
return jupiterRiseDown(JD, Lon, Lat, TZ, ZS, HEI, true)
|
||||
func JupiterRiseTime(jd, lon, lat, timezone, aeroCorrection, observerHeight float64) (float64, error) {
|
||||
return jupiterRiseDown(jd, lon, lat, timezone, aeroCorrection, observerHeight, true)
|
||||
}
|
||||
|
||||
func JupiterDownTime(JD, Lon, Lat, TZ, ZS, HEI float64) float64 {
|
||||
return jupiterRiseDown(JD, Lon, Lat, TZ, ZS, HEI, false)
|
||||
func JupiterSetTime(jd, lon, lat, timezone, aeroCorrection, observerHeight float64) (float64, error) {
|
||||
return jupiterRiseDown(jd, lon, lat, timezone, aeroCorrection, observerHeight, false)
|
||||
}
|
||||
|
||||
func jupiterRiseDown(JD, Lon, Lat, TZ, ZS, HEI float64, isRise bool) float64 {
|
||||
var An float64
|
||||
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
|
||||
func jupiterRiseDown(jd, lon, lat, timezone, aeroCorrection, observerHeight float64, isRise bool) (float64, error) {
|
||||
return planetRiseDown(jd, lon, lat, timezone, aeroCorrection, observerHeight, isRise, JupiterCulminationTime, JupiterHeight, JupiterApparentDec)
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
)
|
||||
|
||||
func MarsL(JD float64) float64 {
|
||||
return planet.WherePlanet(3, 0, JD)
|
||||
func MarsL(jd float64) float64 {
|
||||
return planet.WherePlanet(3, 0, jd)
|
||||
}
|
||||
|
||||
func MarsB(JD float64) float64 {
|
||||
return planet.WherePlanet(3, 1, JD)
|
||||
func MarsB(jd float64) float64 {
|
||||
return planet.WherePlanet(3, 1, jd)
|
||||
}
|
||||
func MarsR(JD float64) float64 {
|
||||
return planet.WherePlanet(3, 2, JD)
|
||||
func MarsR(jd float64) float64 {
|
||||
return planet.WherePlanet(3, 2, jd)
|
||||
}
|
||||
func AMarsX(JD float64) float64 {
|
||||
l := MarsL(JD)
|
||||
b := MarsB(JD)
|
||||
r := MarsR(JD)
|
||||
el := planet.WherePlanet(-1, 0, JD)
|
||||
eb := planet.WherePlanet(-1, 1, JD)
|
||||
er := planet.WherePlanet(-1, 2, JD)
|
||||
func AMarsX(jd float64) float64 {
|
||||
l := MarsL(jd)
|
||||
b := MarsB(jd)
|
||||
r := MarsR(jd)
|
||||
el := planet.WherePlanet(-1, 0, jd)
|
||||
eb := planet.WherePlanet(-1, 1, jd)
|
||||
er := planet.WherePlanet(-1, 2, jd)
|
||||
x := r*Cos(b)*Cos(l) - er*Cos(eb)*Cos(el)
|
||||
return x
|
||||
}
|
||||
|
||||
func AMarsY(JD float64) float64 {
|
||||
func AMarsY(jd float64) float64 {
|
||||
|
||||
l := MarsL(JD)
|
||||
b := MarsB(JD)
|
||||
r := MarsR(JD)
|
||||
el := planet.WherePlanet(-1, 0, JD)
|
||||
eb := planet.WherePlanet(-1, 1, JD)
|
||||
er := planet.WherePlanet(-1, 2, JD)
|
||||
l := MarsL(jd)
|
||||
b := MarsB(jd)
|
||||
r := MarsR(jd)
|
||||
el := planet.WherePlanet(-1, 0, jd)
|
||||
eb := planet.WherePlanet(-1, 1, jd)
|
||||
er := planet.WherePlanet(-1, 2, jd)
|
||||
y := r*Cos(b)*Sin(l) - er*Cos(eb)*Sin(el)
|
||||
return y
|
||||
}
|
||||
func AMarsZ(JD float64) float64 {
|
||||
//l := MarsL(JD)
|
||||
b := MarsB(JD)
|
||||
r := MarsR(JD)
|
||||
// el := planet.WherePlanet(-1, 0, JD)
|
||||
eb := planet.WherePlanet(-1, 1, JD)
|
||||
er := planet.WherePlanet(-1, 2, JD)
|
||||
func AMarsZ(jd float64) float64 {
|
||||
//l := MarsL(jd)
|
||||
b := MarsB(jd)
|
||||
r := MarsR(jd)
|
||||
// el := planet.WherePlanet(-1, 0, jd)
|
||||
eb := planet.WherePlanet(-1, 1, jd)
|
||||
er := planet.WherePlanet(-1, 2, jd)
|
||||
z := r*Sin(b) - er*Sin(eb)
|
||||
return z
|
||||
}
|
||||
|
||||
func AMarsXYZ(JD float64) (float64, float64, float64) {
|
||||
l := MarsL(JD)
|
||||
b := MarsB(JD)
|
||||
r := MarsR(JD)
|
||||
el := planet.WherePlanet(-1, 0, JD)
|
||||
eb := planet.WherePlanet(-1, 1, JD)
|
||||
er := planet.WherePlanet(-1, 2, JD)
|
||||
func AMarsXYZ(jd float64) (float64, float64, float64) {
|
||||
l := MarsL(jd)
|
||||
b := MarsB(jd)
|
||||
r := MarsR(jd)
|
||||
el := planet.WherePlanet(-1, 0, jd)
|
||||
eb := planet.WherePlanet(-1, 1, jd)
|
||||
er := planet.WherePlanet(-1, 2, jd)
|
||||
x := r*Cos(b)*Cos(l) - er*Cos(eb)*Cos(el)
|
||||
y := r*Cos(b)*Sin(l) - er*Cos(eb)*Sin(el)
|
||||
z := r*Sin(b) - er*Sin(eb)
|
||||
return x, y, z
|
||||
}
|
||||
|
||||
func MarsApparentRa(JD float64) float64 {
|
||||
lo, bo := MarsApparentLoBo(JD)
|
||||
sita := Sita(JD)
|
||||
ra := math.Atan2((Sin(lo)*Cos(sita) - Tan(bo)*Sin(sita)), Cos(lo))
|
||||
func MarsApparentRa(jd float64) float64 {
|
||||
lo, bo := MarsApparentLoBo(jd)
|
||||
eps := TrueObliquity(jd)
|
||||
ra := math.Atan2((Sin(lo)*Cos(eps) - Tan(bo)*Sin(eps)), Cos(lo))
|
||||
ra = ra * 180 / math.Pi
|
||||
return Limit360(ra)
|
||||
}
|
||||
func MarsApparentDec(JD float64) float64 {
|
||||
lo, bo := MarsApparentLoBo(JD)
|
||||
sita := Sita(JD)
|
||||
dec := ArcSin(Sin(bo)*Cos(sita) + Cos(bo)*Sin(sita)*Sin(lo))
|
||||
func MarsApparentDec(jd float64) float64 {
|
||||
lo, bo := MarsApparentLoBo(jd)
|
||||
eps := TrueObliquity(jd)
|
||||
dec := ArcSin(Sin(bo)*Cos(eps) + Cos(bo)*Sin(eps)*Sin(lo))
|
||||
return dec
|
||||
}
|
||||
|
||||
func MarsApparentRaDec(JD float64) (float64, float64) {
|
||||
lo, bo := MarsApparentLoBo(JD)
|
||||
sita := Sita(JD)
|
||||
ra := math.Atan2((Sin(lo)*Cos(sita) - Tan(bo)*Sin(sita)), Cos(lo))
|
||||
func MarsApparentRaDec(jd float64) (float64, float64) {
|
||||
lo, bo := MarsApparentLoBo(jd)
|
||||
eps := TrueObliquity(jd)
|
||||
ra := math.Atan2((Sin(lo)*Cos(eps) - Tan(bo)*Sin(eps)), Cos(lo))
|
||||
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
|
||||
}
|
||||
|
||||
func EarthMarsAway(JD float64) float64 {
|
||||
x, y, z := AMarsXYZ(JD)
|
||||
to := math.Sqrt(x*x + y*y + z*z)
|
||||
return to
|
||||
func EarthMarsAway(jd float64) float64 {
|
||||
return planetEarthAwayExplicitN(3, jd, -1)
|
||||
}
|
||||
|
||||
func MarsApparentLo(JD float64) float64 {
|
||||
x, y, z := AMarsXYZ(JD)
|
||||
to := 0.0057755183 * math.Sqrt(x*x+y*y+z*z)
|
||||
x, y, z = AMarsXYZ(JD - to)
|
||||
lo := math.Atan2(y, x)
|
||||
//bo := math.Atan2(z, math.Sqrt(x*x+y*y))
|
||||
lo = lo * 180.0 / math.Pi
|
||||
//bo = bo * 180 / math.Pi
|
||||
lo = Limit360(lo) + Nutation2000Bi(JD)
|
||||
//lo-=GXCLo(lo,bo,JD)/3600;
|
||||
//bo+=GXCBo(lo,bo,JD);
|
||||
return lo
|
||||
func MarsApparentLo(jd float64) float64 {
|
||||
geo, _ := planetApparentGeocentricPositionN(3, jd, -1)
|
||||
return geo.lo
|
||||
}
|
||||
|
||||
func MarsApparentBo(JD float64) float64 {
|
||||
x, y, z := AMarsXYZ(JD)
|
||||
to := 0.0057755183 * math.Sqrt(x*x+y*y+z*z)
|
||||
x, y, z = AMarsXYZ(JD - to)
|
||||
//lo := math.Atan2(y, x)
|
||||
bo := math.Atan2(z, math.Sqrt(x*x+y*y))
|
||||
//lo = lo * 180 / math.Pi
|
||||
bo = bo * 180 / math.Pi
|
||||
//lo+=GXCLo(lo,bo,JD);
|
||||
//bo+=GXCBo(lo,bo,JD)/3600;
|
||||
//lo+=Nutation2000Bi(JD);
|
||||
return bo
|
||||
func MarsApparentBo(jd float64) float64 {
|
||||
geo, _ := planetApparentGeocentricPositionN(3, jd, -1)
|
||||
return geo.bo
|
||||
}
|
||||
|
||||
func MarsApparentLoBo(JD float64) (float64, float64) {
|
||||
x, y, z := AMarsXYZ(JD)
|
||||
to := 0.0057755183 * math.Sqrt(x*x+y*y+z*z)
|
||||
x, y, z = AMarsXYZ(JD - to)
|
||||
lo := math.Atan2(y, x)
|
||||
bo := math.Atan2(z, math.Sqrt(x*x+y*y))
|
||||
lo = lo * 180 / math.Pi
|
||||
bo = bo * 180 / math.Pi
|
||||
lo = Limit360(lo)
|
||||
//lo -= GXCLo(lo, bo, JD) / 3600
|
||||
//bo += GXCBo(lo, bo, JD)
|
||||
lo += Nutation2000Bi(JD)
|
||||
return lo, bo
|
||||
func MarsApparentLoBo(jd float64) (float64, float64) {
|
||||
geo, _ := planetApparentGeocentricPositionN(3, jd, -1)
|
||||
return geo.lo, geo.bo
|
||||
}
|
||||
|
||||
func MarsTrueLoBo(JD float64) (float64, float64) {
|
||||
x, y, z := AMarsXYZ(JD)
|
||||
to := 0.0057755183 * math.Sqrt(x*x+y*y+z*z)
|
||||
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 MarsTrueLoBo(jd float64) (float64, float64) {
|
||||
geo, _ := planetTrueGeocentricPositionN(3, jd, -1)
|
||||
return geo.lo, geo.bo
|
||||
}
|
||||
|
||||
func MarsTrueLo(JD float64) float64 {
|
||||
x, y, _ := AMarsXYZ(JD)
|
||||
lo := math.Atan2(y, x)
|
||||
lo = lo * 180 / math.Pi
|
||||
lo = Limit360(lo)
|
||||
return lo
|
||||
func MarsTrueLo(jd float64) float64 {
|
||||
geo, _ := planetTrueGeocentricPositionN(3, jd, -1)
|
||||
return geo.lo
|
||||
}
|
||||
|
||||
func MarsMag(JD float64) float64 {
|
||||
AwaySun := MarsR(JD)
|
||||
AwayEarth := EarthMarsAway(JD)
|
||||
Away := planet.WherePlanet(-1, 2, JD)
|
||||
i := (AwaySun*AwaySun + AwayEarth*AwayEarth - Away*Away) / (2 * AwaySun * AwayEarth)
|
||||
func MarsMag(jd float64) float64 {
|
||||
sunDistance := MarsR(jd)
|
||||
earthDistance := EarthMarsAway(jd)
|
||||
earthSunDistance := planet.WherePlanet(-1, 2, jd)
|
||||
i := (sunDistance*sunDistance + earthDistance*earthDistance - earthSunDistance*earthSunDistance) / (2 * sunDistance * earthDistance)
|
||||
i = ArcCos(i)
|
||||
Mag := -1.52 + 5*math.Log10(AwaySun*AwayEarth) + 0.016*i
|
||||
return FloatRound(Mag, 2)
|
||||
mag := -1.52 + 5*math.Log10(sunDistance*earthDistance) + 0.016*i
|
||||
return FloatRound(mag, 2)
|
||||
}
|
||||
|
||||
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))
|
||||
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)
|
||||
sinHeight := 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(hourAngle)
|
||||
return ArcSin(sinHeight)
|
||||
}
|
||||
|
||||
@@ -186,271 +146,63 @@ func MarsAzimuth(jde, lon, lat, timezone float64) float64 {
|
||||
ra, dec := MarsApparentRaDec(TD2UT(utcJde, true))
|
||||
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))
|
||||
Azimuth := ArcTan(tanAzimuth)
|
||||
if Azimuth < 0 {
|
||||
if H/15 < 12 {
|
||||
return Azimuth + 360
|
||||
tanAzimuth := Sin(hourAngle) / (Cos(hourAngle)*Sin(lat) - Tan(dec)*Cos(lat))
|
||||
azimuth := ArcTan(tanAzimuth)
|
||||
if azimuth < 0 {
|
||||
if hourAngle/15 < 12 {
|
||||
return azimuth + 360
|
||||
}
|
||||
return Azimuth + 180
|
||||
return azimuth + 180
|
||||
}
|
||||
if H/15 < 12 {
|
||||
return Azimuth + 180
|
||||
if hourAngle/15 < 12 {
|
||||
return azimuth + 180
|
||||
}
|
||||
return Azimuth
|
||||
return azimuth
|
||||
}
|
||||
|
||||
func MarsHourAngle(JD, Lon, TZ float64) float64 {
|
||||
startime := Limit360(ApparentSiderealTime(JD-TZ/24)*15 + Lon)
|
||||
timeangle := startime - MarsApparentRa(TD2UT(JD-TZ/24.0, true))
|
||||
if timeangle < 0 {
|
||||
timeangle += 360
|
||||
func MarsHourAngle(jd, lon, timezone float64) float64 {
|
||||
siderealLongitude := Limit360(ApparentSiderealTime(jd-timezone/24)*15 + lon)
|
||||
hourAngle := siderealLongitude - MarsApparentRa(TD2UT(jd-timezone/24.0, true))
|
||||
if hourAngle < 0 {
|
||||
hourAngle += 360
|
||||
}
|
||||
return timeangle
|
||||
return hourAngle
|
||||
}
|
||||
|
||||
func MarsCulminationTime(jde, lon, timezone float64) float64 {
|
||||
//jde 世界时,非力学时,当地时区 0时,无需转换力学时
|
||||
//ra,dec 瞬时天球座标,非J2000等时间天球坐标
|
||||
jde = math.Floor(jde) + 0.5
|
||||
JD1 := jde + Limit360(360-MarsHourAngle(jde, lon, timezone))/15.0/24.0*0.99726851851851851851
|
||||
limitHA := func(jde, lon, timezone float64) float64 {
|
||||
ha := MarsHourAngle(jde, lon, timezone)
|
||||
if ha < 180 {
|
||||
ha += 360
|
||||
estimateJD := jde + Limit360(360-MarsHourAngle(jde, lon, timezone))/15.0/24.0*0.99726851851851851851
|
||||
normalizedHourAngle := func(jde, lon, timezone float64) float64 {
|
||||
currentHourAngle := MarsHourAngle(jde, lon, timezone)
|
||||
if currentHourAngle < 180 {
|
||||
currentHourAngle += 360
|
||||
}
|
||||
return ha
|
||||
return currentHourAngle
|
||||
}
|
||||
for {
|
||||
JD0 := JD1
|
||||
stDegree := limitHA(JD0, lon, timezone) - 360
|
||||
stDegreep := (limitHA(JD0+0.000005, lon, timezone) - limitHA(JD0-0.000005, lon, timezone)) / 0.00001
|
||||
JD1 = JD0 - stDegree/stDegreep
|
||||
if math.Abs(JD1-JD0) <= 0.00001 {
|
||||
prevJD := estimateJD
|
||||
hourAngleDelta := normalizedHourAngle(prevJD, lon, timezone) - 360
|
||||
hourAngleSlope := (normalizedHourAngle(prevJD+0.000005, lon, timezone) - normalizedHourAngle(prevJD-0.000005, lon, timezone)) / 0.00001
|
||||
estimateJD = prevJD - hourAngleDelta/hourAngleSlope
|
||||
if math.Abs(estimateJD-prevJD) <= 0.00001 {
|
||||
break
|
||||
}
|
||||
}
|
||||
return JD1
|
||||
return estimateJD
|
||||
}
|
||||
|
||||
func MarsRiseTime(JD, Lon, Lat, TZ, ZS, HEI float64) float64 {
|
||||
return marsRiseDown(JD, Lon, Lat, TZ, ZS, HEI, true)
|
||||
func MarsRiseTime(jd, lon, lat, timezone, aeroCorrection, observerHeight float64) (float64, error) {
|
||||
return marsRiseDown(jd, lon, lat, timezone, aeroCorrection, observerHeight, true)
|
||||
}
|
||||
|
||||
func MarsDownTime(JD, Lon, Lat, TZ, ZS, HEI float64) float64 {
|
||||
return marsRiseDown(JD, Lon, Lat, TZ, ZS, HEI, false)
|
||||
func MarsSetTime(jd, lon, lat, timezone, aeroCorrection, observerHeight float64) (float64, error) {
|
||||
return marsRiseDown(jd, lon, lat, timezone, aeroCorrection, observerHeight, false)
|
||||
}
|
||||
|
||||
func marsRiseDown(JD, Lon, Lat, TZ, ZS, HEI float64, isRise bool) float64 {
|
||||
var An float64
|
||||
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
|
||||
func marsRiseDown(jd, lon, lat, timezone, aeroCorrection, observerHeight float64, isRise bool) (float64, error) {
|
||||
return planetRiseDown(jd, lon, lat, timezone, aeroCorrection, observerHeight, isRise, MarsCulminationTime, MarsHeight, MarsApparentDec)
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
)
|
||||
|
||||
func MercuryL(JD float64) float64 {
|
||||
return planet.WherePlanet(1, 0, JD)
|
||||
func MercuryL(jd float64) float64 {
|
||||
return planet.WherePlanet(1, 0, jd)
|
||||
}
|
||||
|
||||
func MercuryB(JD float64) float64 {
|
||||
return planet.WherePlanet(1, 1, JD)
|
||||
func MercuryB(jd float64) float64 {
|
||||
return planet.WherePlanet(1, 1, jd)
|
||||
}
|
||||
func MercuryR(JD float64) float64 {
|
||||
return planet.WherePlanet(1, 2, JD)
|
||||
func MercuryR(jd float64) float64 {
|
||||
return planet.WherePlanet(1, 2, jd)
|
||||
}
|
||||
func AMercuryX(JD float64) float64 {
|
||||
l := MercuryL(JD)
|
||||
b := MercuryB(JD)
|
||||
r := MercuryR(JD)
|
||||
el := planet.WherePlanet(-1, 0, JD)
|
||||
eb := planet.WherePlanet(-1, 1, JD)
|
||||
er := planet.WherePlanet(-1, 2, JD)
|
||||
func AMercuryX(jd float64) float64 {
|
||||
l := MercuryL(jd)
|
||||
b := MercuryB(jd)
|
||||
r := MercuryR(jd)
|
||||
el := planet.WherePlanet(-1, 0, jd)
|
||||
eb := planet.WherePlanet(-1, 1, jd)
|
||||
er := planet.WherePlanet(-1, 2, jd)
|
||||
x := r*Cos(b)*Cos(l) - er*Cos(eb)*Cos(el)
|
||||
return x
|
||||
}
|
||||
|
||||
func AMercuryY(JD float64) float64 {
|
||||
func AMercuryY(jd float64) float64 {
|
||||
|
||||
l := MercuryL(JD)
|
||||
b := MercuryB(JD)
|
||||
r := MercuryR(JD)
|
||||
el := planet.WherePlanet(-1, 0, JD)
|
||||
eb := planet.WherePlanet(-1, 1, JD)
|
||||
er := planet.WherePlanet(-1, 2, JD)
|
||||
l := MercuryL(jd)
|
||||
b := MercuryB(jd)
|
||||
r := MercuryR(jd)
|
||||
el := planet.WherePlanet(-1, 0, jd)
|
||||
eb := planet.WherePlanet(-1, 1, jd)
|
||||
er := planet.WherePlanet(-1, 2, jd)
|
||||
y := r*Cos(b)*Sin(l) - er*Cos(eb)*Sin(el)
|
||||
return y
|
||||
}
|
||||
func AMercuryZ(JD float64) float64 {
|
||||
//l := MercuryL(JD)
|
||||
b := MercuryB(JD)
|
||||
r := MercuryR(JD)
|
||||
// el := planet.WherePlanet(-1, 0, JD)
|
||||
eb := planet.WherePlanet(-1, 1, JD)
|
||||
er := planet.WherePlanet(-1, 2, JD)
|
||||
func AMercuryZ(jd float64) float64 {
|
||||
//l := MercuryL(jd)
|
||||
b := MercuryB(jd)
|
||||
r := MercuryR(jd)
|
||||
// el := planet.WherePlanet(-1, 0, jd)
|
||||
eb := planet.WherePlanet(-1, 1, jd)
|
||||
er := planet.WherePlanet(-1, 2, jd)
|
||||
z := r*Sin(b) - er*Sin(eb)
|
||||
return z
|
||||
}
|
||||
|
||||
func AMercuryXYZ(JD float64) (float64, float64, float64) {
|
||||
l := MercuryL(JD)
|
||||
b := MercuryB(JD)
|
||||
r := MercuryR(JD)
|
||||
el := planet.WherePlanet(-1, 0, JD)
|
||||
eb := planet.WherePlanet(-1, 1, JD)
|
||||
er := planet.WherePlanet(-1, 2, JD)
|
||||
func AMercuryXYZ(jd float64) (float64, float64, float64) {
|
||||
l := MercuryL(jd)
|
||||
b := MercuryB(jd)
|
||||
r := MercuryR(jd)
|
||||
el := planet.WherePlanet(-1, 0, jd)
|
||||
eb := planet.WherePlanet(-1, 1, jd)
|
||||
er := planet.WherePlanet(-1, 2, jd)
|
||||
x := r*Cos(b)*Cos(l) - er*Cos(eb)*Cos(el)
|
||||
y := r*Cos(b)*Sin(l) - er*Cos(eb)*Sin(el)
|
||||
z := r*Sin(b) - er*Sin(eb)
|
||||
return x, y, z
|
||||
}
|
||||
|
||||
func MercuryApparentRa(JD float64) float64 {
|
||||
lo, bo := MercuryApparentLoBo(JD)
|
||||
return LoToRa(JD, lo, bo)
|
||||
func MercuryApparentRa(jd float64) float64 {
|
||||
lo, bo := MercuryApparentLoBo(jd)
|
||||
return LoToRa(jd, lo, bo)
|
||||
}
|
||||
func MercuryApparentDec(JD float64) float64 {
|
||||
lo, bo := MercuryApparentLoBo(JD)
|
||||
sita := Sita(JD)
|
||||
dec := ArcSin(Sin(bo)*Cos(sita) + Cos(bo)*Sin(sita)*Sin(lo))
|
||||
func MercuryApparentDec(jd float64) float64 {
|
||||
lo, bo := MercuryApparentLoBo(jd)
|
||||
eps := TrueObliquity(jd)
|
||||
dec := ArcSin(Sin(bo)*Cos(eps) + Cos(bo)*Sin(eps)*Sin(lo))
|
||||
return dec
|
||||
}
|
||||
|
||||
func MercuryApparentRaDec(JD float64) (float64, float64) {
|
||||
lo, bo := MercuryApparentLoBo(JD)
|
||||
return LoBoToRaDec(JD, lo, bo)
|
||||
func MercuryApparentRaDec(jd float64) (float64, float64) {
|
||||
lo, bo := MercuryApparentLoBo(jd)
|
||||
return LoBoToRaDec(jd, lo, bo)
|
||||
}
|
||||
|
||||
func EarthMercuryAway(JD float64) float64 {
|
||||
x, y, z := AMercuryXYZ(JD)
|
||||
to := math.Sqrt(x*x + y*y + z*z)
|
||||
return to
|
||||
func EarthMercuryAway(jd float64) float64 {
|
||||
return planetEarthAwayExplicitN(1, jd, -1)
|
||||
}
|
||||
|
||||
func MercuryApparentLo(JD float64) float64 {
|
||||
x, y, z := AMercuryXYZ(JD)
|
||||
to := 0.0057755183 * math.Sqrt(x*x+y*y+z*z)
|
||||
x, y, z = AMercuryXYZ(JD - to)
|
||||
lo := math.Atan2(y, x)
|
||||
bo := math.Atan2(z, math.Sqrt(x*x+y*y))
|
||||
lo = lo * 180 / math.Pi
|
||||
bo = bo * 180 / math.Pi
|
||||
lo = Limit360(lo)
|
||||
//lo-=GXCLo(lo,bo,JD)/3600;
|
||||
//bo+=GXCBo(lo,bo,JD);
|
||||
lo += Nutation2000Bi(JD)
|
||||
return lo
|
||||
func MercuryApparentLo(jd float64) float64 {
|
||||
geo, _ := planetApparentGeocentricPositionN(1, jd, -1)
|
||||
return geo.lo
|
||||
}
|
||||
|
||||
func MercuryApparentBo(JD float64) float64 {
|
||||
x, y, z := AMercuryXYZ(JD)
|
||||
to := 0.0057755183 * math.Sqrt(x*x+y*y+z*z)
|
||||
x, y, z = AMercuryXYZ(JD - to)
|
||||
//lo := math.Atan2(y, x)
|
||||
bo := math.Atan2(z, math.Sqrt(x*x+y*y))
|
||||
//lo = lo * 180 / math.Pi
|
||||
bo = bo * 180 / math.Pi
|
||||
//lo+=GXCLo(lo,bo,JD);
|
||||
//bo+=GXCBo(lo,bo,JD)/3600;
|
||||
//lo+=Nutation2000Bi(JD);
|
||||
return bo
|
||||
func MercuryApparentBo(jd float64) float64 {
|
||||
geo, _ := planetApparentGeocentricPositionN(1, jd, -1)
|
||||
return geo.bo
|
||||
}
|
||||
|
||||
func MercuryApparentLoBo(JD float64) (float64, float64) {
|
||||
x, y, z := AMercuryXYZ(JD)
|
||||
to := 0.0057755183 * math.Sqrt(x*x+y*y+z*z)
|
||||
x, y, z = AMercuryXYZ(JD - to)
|
||||
lo := math.Atan2(y, x)
|
||||
bo := math.Atan2(z, math.Sqrt(x*x+y*y))
|
||||
lo = lo * 180 / math.Pi
|
||||
bo = bo * 180 / math.Pi
|
||||
lo = Limit360(lo) + Nutation2000Bi(JD)
|
||||
//lo-=GXCLo(lo,bo,JD)/3600;
|
||||
//bo+=GXCBo(lo,bo,JD);
|
||||
return lo, bo
|
||||
func MercuryApparentLoBo(jd float64) (float64, float64) {
|
||||
geo, _ := planetApparentGeocentricPositionN(1, jd, -1)
|
||||
return geo.lo, geo.bo
|
||||
}
|
||||
|
||||
func MercuryMag(JD float64) float64 {
|
||||
AwaySun := MercuryR(JD)
|
||||
AwayEarth := EarthMercuryAway(JD)
|
||||
Away := planet.WherePlanet(-1, 2, JD)
|
||||
i := (AwaySun*AwaySun + AwayEarth*AwayEarth - Away*Away) / (2 * AwaySun * AwayEarth)
|
||||
func MercuryMag(jd float64) float64 {
|
||||
sunDistance := MercuryR(jd)
|
||||
earthDistance := EarthMercuryAway(jd)
|
||||
earthSunDistance := planet.WherePlanet(-1, 2, jd)
|
||||
i := (sunDistance*sunDistance + earthDistance*earthDistance - earthSunDistance*earthSunDistance) / (2 * sunDistance * earthDistance)
|
||||
i = ArcCos(i)
|
||||
Mag := -0.42 + 5*math.Log10(AwaySun*AwayEarth) + 0.0380*i - 0.000273*i*i + 0.000002*i*i*i
|
||||
return FloatRound(Mag, 2)
|
||||
mag := -0.42 + 5*math.Log10(sunDistance*earthDistance) + 0.0380*i - 0.000273*i*i + 0.000002*i*i*i
|
||||
return FloatRound(mag, 2)
|
||||
}
|
||||
|
||||
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))
|
||||
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)
|
||||
sinHeight := 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(hourAngle)
|
||||
return ArcSin(sinHeight)
|
||||
}
|
||||
|
||||
@@ -159,450 +129,63 @@ func MercuryAzimuth(jde, lon, lat, timezone float64) float64 {
|
||||
ra, dec := MercuryApparentRaDec(TD2UT(utcJde, true))
|
||||
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))
|
||||
Azimuth := ArcTan(tanAzimuth)
|
||||
if Azimuth < 0 {
|
||||
if H/15 < 12 {
|
||||
return Azimuth + 360
|
||||
tanAzimuth := Sin(hourAngle) / (Cos(hourAngle)*Sin(lat) - Tan(dec)*Cos(lat))
|
||||
azimuth := ArcTan(tanAzimuth)
|
||||
if azimuth < 0 {
|
||||
if hourAngle/15 < 12 {
|
||||
return azimuth + 360
|
||||
}
|
||||
return Azimuth + 180
|
||||
return azimuth + 180
|
||||
}
|
||||
if H/15 < 12 {
|
||||
return Azimuth + 180
|
||||
if hourAngle/15 < 12 {
|
||||
return azimuth + 180
|
||||
}
|
||||
return Azimuth
|
||||
return azimuth
|
||||
}
|
||||
|
||||
func MercuryHourAngle(JD, Lon, TZ float64) float64 {
|
||||
startime := Limit360(ApparentSiderealTime(JD-TZ/24)*15 + Lon)
|
||||
timeangle := startime - MercuryApparentRa(TD2UT(JD-TZ/24.0, true))
|
||||
if timeangle < 0 {
|
||||
timeangle += 360
|
||||
func MercuryHourAngle(jd, lon, timezone float64) float64 {
|
||||
siderealLongitude := Limit360(ApparentSiderealTime(jd-timezone/24)*15 + lon)
|
||||
hourAngle := siderealLongitude - MercuryApparentRa(TD2UT(jd-timezone/24.0, true))
|
||||
if hourAngle < 0 {
|
||||
hourAngle += 360
|
||||
}
|
||||
return timeangle
|
||||
return hourAngle
|
||||
}
|
||||
|
||||
func MercuryCulminationTime(jde, lon, timezone float64) float64 {
|
||||
//jde 世界时,非力学时,当地时区 0时,无需转换力学时
|
||||
//ra,dec 瞬时天球座标,非J2000等时间天球坐标
|
||||
jde = math.Floor(jde) + 0.5
|
||||
JD1 := jde + Limit360(360-MercuryHourAngle(jde, lon, timezone))/15.0/24.0*0.99726851851851851851
|
||||
limitHA := func(jde, lon, timezone float64) float64 {
|
||||
ha := MercuryHourAngle(jde, lon, timezone)
|
||||
if ha < 180 {
|
||||
ha += 360
|
||||
estimateJD := jde + Limit360(360-MercuryHourAngle(jde, lon, timezone))/15.0/24.0*0.99726851851851851851
|
||||
normalizedHourAngle := func(jde, lon, timezone float64) float64 {
|
||||
currentHourAngle := MercuryHourAngle(jde, lon, timezone)
|
||||
if currentHourAngle < 180 {
|
||||
currentHourAngle += 360
|
||||
}
|
||||
return ha
|
||||
return currentHourAngle
|
||||
}
|
||||
for {
|
||||
JD0 := JD1
|
||||
stDegree := limitHA(JD0, lon, timezone) - 360
|
||||
stDegreep := (limitHA(JD0+0.000005, lon, timezone) - limitHA(JD0-0.000005, lon, timezone)) / 0.00001
|
||||
JD1 = JD0 - stDegree/stDegreep
|
||||
if math.Abs(JD1-JD0) <= 0.00001 {
|
||||
prevJD := estimateJD
|
||||
hourAngleDelta := normalizedHourAngle(prevJD, lon, timezone) - 360
|
||||
hourAngleSlope := (normalizedHourAngle(prevJD+0.000005, lon, timezone) - normalizedHourAngle(prevJD-0.000005, lon, timezone)) / 0.00001
|
||||
estimateJD = prevJD - hourAngleDelta/hourAngleSlope
|
||||
if math.Abs(estimateJD-prevJD) <= 0.00001 {
|
||||
break
|
||||
}
|
||||
}
|
||||
return JD1
|
||||
return estimateJD
|
||||
}
|
||||
|
||||
func MercuryRiseTime(JD, Lon, Lat, TZ, ZS, HEI float64) float64 {
|
||||
return mercuryRiseDown(JD, Lon, Lat, TZ, ZS, HEI, true)
|
||||
func MercuryRiseTime(jd, lon, lat, timezone, aeroCorrection, observerHeight float64) (float64, error) {
|
||||
return mercuryRiseDown(jd, lon, lat, timezone, aeroCorrection, observerHeight, true)
|
||||
}
|
||||
|
||||
func MercuryDownTime(JD, Lon, Lat, TZ, ZS, HEI float64) float64 {
|
||||
return mercuryRiseDown(JD, Lon, Lat, TZ, ZS, HEI, false)
|
||||
func MercurySetTime(jd, lon, lat, timezone, aeroCorrection, observerHeight float64) (float64, error) {
|
||||
return mercuryRiseDown(jd, lon, lat, timezone, aeroCorrection, observerHeight, false)
|
||||
}
|
||||
|
||||
func mercuryRiseDown(JD, Lon, Lat, TZ, ZS, HEI float64, isRise bool) float64 {
|
||||
var An float64
|
||||
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
|
||||
func mercuryRiseDown(jd, lon, lat, timezone, aeroCorrection, observerHeight float64, isRise bool) (float64, error) {
|
||||
return planetRiseDown(jd, lon, lat, timezone, aeroCorrection, observerHeight, isRise, MercuryCulminationTime, MercuryHeight, MercuryApparentDec)
|
||||
}
|
||||
|
||||
@@ -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("")
|
||||
}
|
||||
}
|
||||
+47
-1730
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
+405
-467
File diff suppressed because it is too large
Load Diff
@@ -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"
|
||||
)
|
||||
|
||||
func NeptuneL(JD float64) float64 {
|
||||
return planet.WherePlanet(7, 0, JD)
|
||||
func NeptuneL(jd float64) float64 {
|
||||
return planet.WherePlanet(7, 0, jd)
|
||||
}
|
||||
|
||||
func NeptuneB(JD float64) float64 {
|
||||
return planet.WherePlanet(7, 1, JD)
|
||||
func NeptuneB(jd float64) float64 {
|
||||
return planet.WherePlanet(7, 1, jd)
|
||||
}
|
||||
func NeptuneR(JD float64) float64 {
|
||||
return planet.WherePlanet(7, 2, JD)
|
||||
func NeptuneR(jd float64) float64 {
|
||||
return planet.WherePlanet(7, 2, jd)
|
||||
}
|
||||
func ANeptuneX(JD float64) float64 {
|
||||
l := NeptuneL(JD)
|
||||
b := NeptuneB(JD)
|
||||
r := NeptuneR(JD)
|
||||
el := planet.WherePlanet(-1, 0, JD)
|
||||
eb := planet.WherePlanet(-1, 1, JD)
|
||||
er := planet.WherePlanet(-1, 2, JD)
|
||||
func ANeptuneX(jd float64) float64 {
|
||||
l := NeptuneL(jd)
|
||||
b := NeptuneB(jd)
|
||||
r := NeptuneR(jd)
|
||||
el := planet.WherePlanet(-1, 0, jd)
|
||||
eb := planet.WherePlanet(-1, 1, jd)
|
||||
er := planet.WherePlanet(-1, 2, jd)
|
||||
x := r*Cos(b)*Cos(l) - er*Cos(eb)*Cos(el)
|
||||
return x
|
||||
}
|
||||
|
||||
func ANeptuneY(JD float64) float64 {
|
||||
func ANeptuneY(jd float64) float64 {
|
||||
|
||||
l := NeptuneL(JD)
|
||||
b := NeptuneB(JD)
|
||||
r := NeptuneR(JD)
|
||||
el := planet.WherePlanet(-1, 0, JD)
|
||||
eb := planet.WherePlanet(-1, 1, JD)
|
||||
er := planet.WherePlanet(-1, 2, JD)
|
||||
l := NeptuneL(jd)
|
||||
b := NeptuneB(jd)
|
||||
r := NeptuneR(jd)
|
||||
el := planet.WherePlanet(-1, 0, jd)
|
||||
eb := planet.WherePlanet(-1, 1, jd)
|
||||
er := planet.WherePlanet(-1, 2, jd)
|
||||
y := r*Cos(b)*Sin(l) - er*Cos(eb)*Sin(el)
|
||||
return y
|
||||
}
|
||||
func ANeptuneZ(JD float64) float64 {
|
||||
//l := NeptuneL(JD)
|
||||
b := NeptuneB(JD)
|
||||
r := NeptuneR(JD)
|
||||
// el := planet.WherePlanet(-1, 0, JD)
|
||||
eb := planet.WherePlanet(-1, 1, JD)
|
||||
er := planet.WherePlanet(-1, 2, JD)
|
||||
func ANeptuneZ(jd float64) float64 {
|
||||
//l := NeptuneL(jd)
|
||||
b := NeptuneB(jd)
|
||||
r := NeptuneR(jd)
|
||||
// el := planet.WherePlanet(-1, 0, jd)
|
||||
eb := planet.WherePlanet(-1, 1, jd)
|
||||
er := planet.WherePlanet(-1, 2, jd)
|
||||
z := r*Sin(b) - er*Sin(eb)
|
||||
return z
|
||||
}
|
||||
|
||||
func ANeptuneXYZ(JD float64) (float64, float64, float64) {
|
||||
l := NeptuneL(JD)
|
||||
b := NeptuneB(JD)
|
||||
r := NeptuneR(JD)
|
||||
el := planet.WherePlanet(-1, 0, JD)
|
||||
eb := planet.WherePlanet(-1, 1, JD)
|
||||
er := planet.WherePlanet(-1, 2, JD)
|
||||
func ANeptuneXYZ(jd float64) (float64, float64, float64) {
|
||||
l := NeptuneL(jd)
|
||||
b := NeptuneB(jd)
|
||||
r := NeptuneR(jd)
|
||||
el := planet.WherePlanet(-1, 0, jd)
|
||||
eb := planet.WherePlanet(-1, 1, jd)
|
||||
er := planet.WherePlanet(-1, 2, jd)
|
||||
x := r*Cos(b)*Cos(l) - er*Cos(eb)*Cos(el)
|
||||
y := r*Cos(b)*Sin(l) - er*Cos(eb)*Sin(el)
|
||||
z := r*Sin(b) - er*Sin(eb)
|
||||
return x, y, z
|
||||
}
|
||||
|
||||
func NeptuneApparentRa(JD float64) float64 {
|
||||
lo, bo := NeptuneApparentLoBo(JD)
|
||||
sita := Sita(JD)
|
||||
ra := math.Atan2((Sin(lo)*Cos(sita) - Tan(bo)*Sin(sita)), Cos(lo))
|
||||
func NeptuneApparentRa(jd float64) float64 {
|
||||
lo, bo := NeptuneApparentLoBo(jd)
|
||||
eps := TrueObliquity(jd)
|
||||
ra := math.Atan2((Sin(lo)*Cos(eps) - Tan(bo)*Sin(eps)), Cos(lo))
|
||||
ra = ra * 180 / math.Pi
|
||||
return Limit360(ra)
|
||||
}
|
||||
func NeptuneApparentDec(JD float64) float64 {
|
||||
lo, bo := NeptuneApparentLoBo(JD)
|
||||
sita := Sita(JD)
|
||||
dec := ArcSin(Sin(bo)*Cos(sita) + Cos(bo)*Sin(sita)*Sin(lo))
|
||||
func NeptuneApparentDec(jd float64) float64 {
|
||||
lo, bo := NeptuneApparentLoBo(jd)
|
||||
eps := TrueObliquity(jd)
|
||||
dec := ArcSin(Sin(bo)*Cos(eps) + Cos(bo)*Sin(eps)*Sin(lo))
|
||||
return dec
|
||||
}
|
||||
|
||||
func NeptuneApparentRaDec(JD float64) (float64, float64) {
|
||||
lo, bo := NeptuneApparentLoBo(JD)
|
||||
sita := Sita(JD)
|
||||
ra := math.Atan2((Sin(lo)*Cos(sita) - Tan(bo)*Sin(sita)), Cos(lo))
|
||||
func NeptuneApparentRaDec(jd float64) (float64, float64) {
|
||||
lo, bo := NeptuneApparentLoBo(jd)
|
||||
eps := TrueObliquity(jd)
|
||||
ra := math.Atan2((Sin(lo)*Cos(eps) - Tan(bo)*Sin(eps)), Cos(lo))
|
||||
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
|
||||
}
|
||||
|
||||
func EarthNeptuneAway(JD float64) float64 {
|
||||
x, y, z := ANeptuneXYZ(JD)
|
||||
to := math.Sqrt(x*x + y*y + z*z)
|
||||
return to
|
||||
func EarthNeptuneAway(jd float64) float64 {
|
||||
return planetEarthAwayExplicitN(7, jd, -1)
|
||||
}
|
||||
|
||||
func NeptuneApparentLo(JD float64) float64 {
|
||||
x, y, z := ANeptuneXYZ(JD)
|
||||
to := 0.0057755183 * math.Sqrt(x*x+y*y+z*z)
|
||||
x, y, z = ANeptuneXYZ(JD - to)
|
||||
lo := math.Atan2(y, x)
|
||||
bo := math.Atan2(z, math.Sqrt(x*x+y*y))
|
||||
lo = lo * 180 / math.Pi
|
||||
bo = bo * 180 / math.Pi
|
||||
lo = Limit360(lo)
|
||||
//lo-=GXCLo(lo,bo,JD)/3600;
|
||||
//bo+=GXCBo(lo,bo,JD);
|
||||
lo += Nutation2000Bi(JD)
|
||||
return lo
|
||||
func NeptuneApparentLo(jd float64) float64 {
|
||||
geo, _ := planetApparentGeocentricPositionN(7, jd, -1)
|
||||
return geo.lo
|
||||
}
|
||||
|
||||
func NeptuneApparentBo(JD float64) float64 {
|
||||
x, y, z := ANeptuneXYZ(JD)
|
||||
to := 0.0057755183 * math.Sqrt(x*x+y*y+z*z)
|
||||
x, y, z = ANeptuneXYZ(JD - to)
|
||||
//lo := math.Atan2(y, x)
|
||||
bo := math.Atan2(z, math.Sqrt(x*x+y*y))
|
||||
//lo = lo * 180 / math.Pi
|
||||
bo = bo * 180 / math.Pi
|
||||
//lo+=GXCLo(lo,bo,JD);
|
||||
//bo+=GXCBo(lo,bo,JD)/3600;
|
||||
//lo+=Nutation2000Bi(JD);
|
||||
return bo
|
||||
func NeptuneApparentBo(jd float64) float64 {
|
||||
geo, _ := planetApparentGeocentricPositionN(7, jd, -1)
|
||||
return geo.bo
|
||||
}
|
||||
|
||||
func NeptuneApparentLoBo(JD float64) (float64, float64) {
|
||||
x, y, z := ANeptuneXYZ(JD)
|
||||
to := 0.0057755183 * math.Sqrt(x*x+y*y+z*z)
|
||||
x, y, z = ANeptuneXYZ(JD - to)
|
||||
lo := math.Atan2(y, x)
|
||||
bo := math.Atan2(z, math.Sqrt(x*x+y*y))
|
||||
lo = lo * 180 / math.Pi
|
||||
bo = bo * 180 / math.Pi
|
||||
lo = Limit360(lo)
|
||||
//lo-=GXCLo(lo,bo,JD)/3600;
|
||||
//bo+=GXCBo(lo,bo,JD);
|
||||
lo += Nutation2000Bi(JD)
|
||||
return lo, bo
|
||||
func NeptuneApparentLoBo(jd float64) (float64, float64) {
|
||||
geo, _ := planetApparentGeocentricPositionN(7, jd, -1)
|
||||
return geo.lo, geo.bo
|
||||
}
|
||||
|
||||
func NeptuneMag(JD float64) float64 {
|
||||
AwaySun := NeptuneR(JD)
|
||||
AwayEarth := EarthNeptuneAway(JD)
|
||||
Away := planet.WherePlanet(-1, 2, JD)
|
||||
i := (AwaySun*AwaySun + AwayEarth*AwayEarth - Away*Away) / (2 * AwaySun * AwayEarth)
|
||||
func NeptuneMag(jd float64) float64 {
|
||||
sunDistance := NeptuneR(jd)
|
||||
earthDistance := EarthNeptuneAway(jd)
|
||||
earthSunDistance := planet.WherePlanet(-1, 2, jd)
|
||||
i := (sunDistance*sunDistance + earthDistance*earthDistance - earthSunDistance*earthSunDistance) / (2 * sunDistance * earthDistance)
|
||||
i = ArcCos(i)
|
||||
Mag := -6.87 + 5*math.Log10(AwaySun*AwayEarth)
|
||||
return FloatRound(Mag, 2)
|
||||
mag := -6.87 + 5*math.Log10(sunDistance*earthDistance)
|
||||
return FloatRound(mag, 2)
|
||||
}
|
||||
|
||||
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))
|
||||
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)
|
||||
sinHeight := 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(hourAngle)
|
||||
return ArcSin(sinHeight)
|
||||
}
|
||||
|
||||
@@ -167,271 +136,63 @@ func NeptuneAzimuth(jde, lon, lat, timezone float64) float64 {
|
||||
ra, dec := NeptuneApparentRaDec(TD2UT(utcJde, true))
|
||||
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))
|
||||
Azimuth := ArcTan(tanAzimuth)
|
||||
if Azimuth < 0 {
|
||||
if H/15 < 12 {
|
||||
return Azimuth + 360
|
||||
tanAzimuth := Sin(hourAngle) / (Cos(hourAngle)*Sin(lat) - Tan(dec)*Cos(lat))
|
||||
azimuth := ArcTan(tanAzimuth)
|
||||
if azimuth < 0 {
|
||||
if hourAngle/15 < 12 {
|
||||
return azimuth + 360
|
||||
}
|
||||
return Azimuth + 180
|
||||
return azimuth + 180
|
||||
}
|
||||
if H/15 < 12 {
|
||||
return Azimuth + 180
|
||||
if hourAngle/15 < 12 {
|
||||
return azimuth + 180
|
||||
}
|
||||
return Azimuth
|
||||
return azimuth
|
||||
}
|
||||
|
||||
func NeptuneHourAngle(JD, Lon, TZ float64) float64 {
|
||||
startime := Limit360(ApparentSiderealTime(JD-TZ/24)*15 + Lon)
|
||||
timeangle := startime - NeptuneApparentRa(TD2UT(JD-TZ/24.0, true))
|
||||
if timeangle < 0 {
|
||||
timeangle += 360
|
||||
func NeptuneHourAngle(jd, lon, timezone float64) float64 {
|
||||
siderealLongitude := Limit360(ApparentSiderealTime(jd-timezone/24)*15 + lon)
|
||||
hourAngle := siderealLongitude - NeptuneApparentRa(TD2UT(jd-timezone/24.0, true))
|
||||
if hourAngle < 0 {
|
||||
hourAngle += 360
|
||||
}
|
||||
return timeangle
|
||||
return hourAngle
|
||||
}
|
||||
|
||||
func NeptuneCulminationTime(jde, lon, timezone float64) float64 {
|
||||
//jde 世界时,非力学时,当地时区 0时,无需转换力学时
|
||||
//ra,dec 瞬时天球座标,非J2000等时间天球坐标
|
||||
jde = math.Floor(jde) + 0.5
|
||||
JD1 := jde + Limit360(360-NeptuneHourAngle(jde, lon, timezone))/15.0/24.0*0.99726851851851851851
|
||||
limitHA := func(jde, lon, timezone float64) float64 {
|
||||
ha := NeptuneHourAngle(jde, lon, timezone)
|
||||
if ha < 180 {
|
||||
ha += 360
|
||||
estimateJD := jde + Limit360(360-NeptuneHourAngle(jde, lon, timezone))/15.0/24.0*0.99726851851851851851
|
||||
normalizedHourAngle := func(jde, lon, timezone float64) float64 {
|
||||
currentHourAngle := NeptuneHourAngle(jde, lon, timezone)
|
||||
if currentHourAngle < 180 {
|
||||
currentHourAngle += 360
|
||||
}
|
||||
return ha
|
||||
return currentHourAngle
|
||||
}
|
||||
for {
|
||||
JD0 := JD1
|
||||
stDegree := limitHA(JD0, lon, timezone) - 360
|
||||
stDegreep := (limitHA(JD0+0.000005, lon, timezone) - limitHA(JD0-0.000005, lon, timezone)) / 0.00001
|
||||
JD1 = JD0 - stDegree/stDegreep
|
||||
if math.Abs(JD1-JD0) <= 0.00001 {
|
||||
prevJD := estimateJD
|
||||
hourAngleDelta := normalizedHourAngle(prevJD, lon, timezone) - 360
|
||||
hourAngleSlope := (normalizedHourAngle(prevJD+0.000005, lon, timezone) - normalizedHourAngle(prevJD-0.000005, lon, timezone)) / 0.00001
|
||||
estimateJD = prevJD - hourAngleDelta/hourAngleSlope
|
||||
if math.Abs(estimateJD-prevJD) <= 0.00001 {
|
||||
break
|
||||
}
|
||||
}
|
||||
return JD1
|
||||
return estimateJD
|
||||
}
|
||||
|
||||
func NeptuneRiseTime(JD, Lon, Lat, TZ, ZS, HEI float64) float64 {
|
||||
return neptuneRiseDown(JD, Lon, Lat, TZ, ZS, HEI, true)
|
||||
func NeptuneRiseTime(jd, lon, lat, timezone, aeroCorrection, observerHeight float64) (float64, error) {
|
||||
return neptuneRiseDown(jd, lon, lat, timezone, aeroCorrection, observerHeight, true)
|
||||
}
|
||||
|
||||
func NeptuneDownTime(JD, Lon, Lat, TZ, ZS, HEI float64) float64 {
|
||||
return neptuneRiseDown(JD, Lon, Lat, TZ, ZS, HEI, false)
|
||||
func NeptuneSetTime(jd, lon, lat, timezone, aeroCorrection, observerHeight float64) (float64, error) {
|
||||
return neptuneRiseDown(jd, lon, lat, timezone, aeroCorrection, observerHeight, false)
|
||||
}
|
||||
|
||||
func neptuneRiseDown(JD, Lon, Lat, TZ, ZS, HEI float64, isRise bool) float64 {
|
||||
var An float64
|
||||
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
|
||||
func neptuneRiseDown(jd, lon, lat, timezone, aeroCorrection, observerHeight float64, isRise bool) (float64, error) {
|
||||
return planetRiseDown(jd, lon, lat, timezone, aeroCorrection, observerHeight, isRise, NeptuneCulminationTime, NeptuneHeight, NeptuneApparentDec)
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
+1
-1
@@ -15,7 +15,7 @@ func EclipticObliquity(jde float64, nutation bool) float64 {
|
||||
return eps
|
||||
}
|
||||
|
||||
func Sita(JD float64) float64 {
|
||||
func TrueObliquity(JD float64) float64 {
|
||||
return EclipticObliquity(JD, true)
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,181 @@
|
||||
package basic
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
type outerTruthBaselineFile struct {
|
||||
Events []outerTruthBaselineEvent `json:"events"`
|
||||
}
|
||||
|
||||
type outerTruthBaselineEvent struct {
|
||||
Planet string `json:"planet"`
|
||||
Kind string `json:"kind"`
|
||||
HintKind string `json:"hint_kind"`
|
||||
NAOJHintJST string `json:"naoj_hint_jst"`
|
||||
Precision string `json:"precision"`
|
||||
CandidateJST string `json:"candidate_jst"`
|
||||
VerifiedJST string `json:"verified_jst"`
|
||||
CandidateSource string `json:"candidate_source"`
|
||||
}
|
||||
|
||||
func loadOuterTruthBaseline(t *testing.T) outerTruthBaselineFile {
|
||||
t.Helper()
|
||||
|
||||
paths := [][]string{
|
||||
{
|
||||
"testdata/jpl_outer_event_baseline.json",
|
||||
"basic/testdata/jpl_outer_event_baseline.json",
|
||||
},
|
||||
{
|
||||
"testdata/jpl_outer_event_baseline_21c_sample.json",
|
||||
"basic/testdata/jpl_outer_event_baseline_21c_sample.json",
|
||||
},
|
||||
}
|
||||
|
||||
var merged outerTruthBaselineFile
|
||||
for index, candidates := range paths {
|
||||
var (
|
||||
data []byte
|
||||
err error
|
||||
)
|
||||
for _, path := range candidates {
|
||||
data, err = os.ReadFile(path)
|
||||
if err == nil {
|
||||
var baseline outerTruthBaselineFile
|
||||
if err := json.Unmarshal(data, &baseline); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
merged.Events = append(merged.Events, baseline.Events...)
|
||||
break
|
||||
}
|
||||
}
|
||||
if err != nil && index == 0 {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
if len(merged.Events) == 0 {
|
||||
t.Fatal("empty outer truth baseline file")
|
||||
}
|
||||
return merged
|
||||
}
|
||||
|
||||
func outerTruthTolerance(event outerTruthBaselineEvent) time.Duration {
|
||||
switch event.Kind {
|
||||
case "CONJ", "OPP", "EQE", "EQW":
|
||||
return 2 * time.Minute
|
||||
default:
|
||||
return 2 * time.Minute
|
||||
}
|
||||
}
|
||||
|
||||
func outerTruthEventFuncs(t *testing.T, event outerTruthBaselineEvent) (func(float64) float64, func(float64) float64) {
|
||||
t.Helper()
|
||||
switch event.Planet + ":" + event.Kind {
|
||||
case "Jupiter:CONJ":
|
||||
return LastJupiterConjunction, NextJupiterConjunction
|
||||
case "Jupiter:OPP":
|
||||
return LastJupiterOpposition, NextJupiterOpposition
|
||||
case "Jupiter:EQE":
|
||||
return LastJupiterEasternQuadrature, NextJupiterEasternQuadrature
|
||||
case "Jupiter:EQW":
|
||||
return LastJupiterWesternQuadrature, NextJupiterWesternQuadrature
|
||||
case "Jupiter:P2R":
|
||||
return LastJupiterProgradeToRetrograde, NextJupiterProgradeToRetrograde
|
||||
case "Jupiter:R2P":
|
||||
return LastJupiterRetrogradeToPrograde, NextJupiterRetrogradeToPrograde
|
||||
case "Saturn:CONJ":
|
||||
return LastSaturnConjunction, NextSaturnConjunction
|
||||
case "Saturn:OPP":
|
||||
return LastSaturnOpposition, NextSaturnOpposition
|
||||
case "Saturn:EQE":
|
||||
return LastSaturnEasternQuadrature, NextSaturnEasternQuadrature
|
||||
case "Saturn:EQW":
|
||||
return LastSaturnWesternQuadrature, NextSaturnWesternQuadrature
|
||||
case "Saturn:P2R":
|
||||
return LastSaturnProgradeToRetrograde, NextSaturnProgradeToRetrograde
|
||||
case "Saturn:R2P":
|
||||
return LastSaturnRetrogradeToPrograde, NextSaturnRetrogradeToPrograde
|
||||
case "Uranus:CONJ":
|
||||
return LastUranusConjunction, NextUranusConjunction
|
||||
case "Uranus:OPP":
|
||||
return LastUranusOpposition, NextUranusOpposition
|
||||
case "Uranus:EQE":
|
||||
return LastUranusEasternQuadrature, NextUranusEasternQuadrature
|
||||
case "Uranus:EQW":
|
||||
return LastUranusWesternQuadrature, NextUranusWesternQuadrature
|
||||
case "Uranus:P2R":
|
||||
return LastUranusProgradeToRetrograde, NextUranusProgradeToRetrograde
|
||||
case "Uranus:R2P":
|
||||
return LastUranusRetrogradeToPrograde, NextUranusRetrogradeToPrograde
|
||||
case "Neptune:CONJ":
|
||||
return LastNeptuneConjunction, NextNeptuneConjunction
|
||||
case "Neptune:OPP":
|
||||
return LastNeptuneOpposition, NextNeptuneOpposition
|
||||
case "Neptune:EQE":
|
||||
return LastNeptuneEasternQuadrature, NextNeptuneEasternQuadrature
|
||||
case "Neptune:EQW":
|
||||
return LastNeptuneWesternQuadrature, NextNeptuneWesternQuadrature
|
||||
case "Neptune:P2R":
|
||||
return LastNeptuneProgradeToRetrograde, NextNeptuneProgradeToRetrograde
|
||||
case "Neptune:R2P":
|
||||
return LastNeptuneRetrogradeToPrograde, NextNeptuneRetrogradeToPrograde
|
||||
default:
|
||||
t.Fatalf("unsupported outer event %s:%s", event.Planet, event.Kind)
|
||||
return nil, nil
|
||||
}
|
||||
}
|
||||
|
||||
func assertOuterTruthBaselineEvent(t *testing.T, event outerTruthBaselineEvent, lastFn, nextFn func(float64) float64) {
|
||||
t.Helper()
|
||||
when := parseInnerBaselineTime(t, event.VerifiedJST)
|
||||
before := when.Add(-7 * 24 * time.Hour)
|
||||
after := when.Add(7 * 24 * time.Hour)
|
||||
next := JDE2DateByZone(nextFn(toUTJD(before)), when.Location(), false)
|
||||
last := JDE2DateByZone(lastFn(toUTJD(after)), when.Location(), false)
|
||||
tolerance := outerTruthTolerance(event)
|
||||
|
||||
if diff := next.Sub(when); diff < -tolerance || diff > tolerance {
|
||||
t.Fatalf("%s %s next mismatch: got %s want %s tol=%s hint=%s candidate=%s via=%s", event.Planet, event.Kind, next, when, tolerance, event.NAOJHintJST, event.CandidateJST, event.CandidateSource)
|
||||
}
|
||||
if diff := last.Sub(when); diff < -tolerance || diff > tolerance {
|
||||
t.Fatalf("%s %s last mismatch: got %s want %s tol=%s hint=%s candidate=%s via=%s", event.Planet, event.Kind, last, when, tolerance, event.NAOJHintJST, event.CandidateJST, event.CandidateSource)
|
||||
}
|
||||
}
|
||||
|
||||
func TestOuterPlanetPhaseTruthAgainstJPL(t *testing.T) {
|
||||
baseline := loadOuterTruthBaseline(t)
|
||||
for _, event := range baseline.Events {
|
||||
event := event
|
||||
switch event.Kind {
|
||||
case "P2R", "R2P":
|
||||
// Station rows are retained as JPL apparent-RA reference data for
|
||||
// future refinement. Current station behavior is constrained by the
|
||||
// library's existing station baseline instead of these reference rows.
|
||||
continue
|
||||
}
|
||||
name := strings.Join([]string{event.Planet, event.Kind, event.VerifiedJST}, "_")
|
||||
t.Run(name, func(t *testing.T) {
|
||||
lastFn, nextFn := outerTruthEventFuncs(t, event)
|
||||
assertOuterTruthBaselineEvent(t, event, lastFn, nextFn)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestOuterPlanetStationJPLReferenceLoaded(t *testing.T) {
|
||||
baseline := loadOuterTruthBaseline(t)
|
||||
count := 0
|
||||
for _, event := range baseline.Events {
|
||||
switch event.Kind {
|
||||
case "P2R", "R2P":
|
||||
count++
|
||||
}
|
||||
}
|
||||
if count == 0 {
|
||||
t.Fatal("missing outer station JPL reference rows")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package basic
|
||||
|
||||
import (
|
||||
"math"
|
||||
|
||||
. "b612.me/astro/tools"
|
||||
)
|
||||
|
||||
// ParallacticAngleByHourAngle 返回视差角(天顶方向角)/ parallactic angle.
|
||||
//
|
||||
// hourAngle 为目标时角,dec 为赤纬,lat 为观测者纬度,单位均为度。
|
||||
// 返回值是 atan2 公式给出的有符号结果,范围通常为 [-180, 180] 度。
|
||||
// hourAngle may be signed or normalized to [0, 360); the trigonometric
|
||||
// formula handles either representation.
|
||||
func ParallacticAngleByHourAngle(hourAngle, dec, lat float64) float64 {
|
||||
return math.Atan2(
|
||||
Sin(hourAngle),
|
||||
Tan(lat)*Cos(dec)-Sin(dec)*Cos(hourAngle),
|
||||
) * 180 / math.Pi
|
||||
}
|
||||
|
||||
// StarParallacticAngle 返回星体在给定观测条件下的视差角(天顶方向角)/ parallactic angle.
|
||||
func StarParallacticAngle(jde, ra, dec, lon, lat, timezone float64) float64 {
|
||||
return ParallacticAngleByHourAngle(StarHourAngle(jde, ra, lon, timezone), dec, lat)
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
package basic
|
||||
|
||||
import (
|
||||
"math"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestParallacticAngleByHourAngleKnownCases(t *testing.T) {
|
||||
cases := []struct {
|
||||
name string
|
||||
hourAngle float64
|
||||
dec float64
|
||||
lat float64
|
||||
want float64
|
||||
}{
|
||||
{name: "meridian", hourAngle: 0, dec: 10, lat: 45, want: 0},
|
||||
{name: "equator west", hourAngle: 30, dec: 0, lat: 0, want: 90},
|
||||
{name: "equator east", hourAngle: -30, dec: 0, lat: 0, want: -90},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
got := ParallacticAngleByHourAngle(tc.hourAngle, tc.dec, tc.lat)
|
||||
if math.Abs(got-tc.want) > 1e-12 {
|
||||
t.Fatalf("%s mismatch: got %.15f want %.15f", tc.name, got, tc.want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestStarParallacticAngleMatchesHourAngleForm(t *testing.T) {
|
||||
date := time.Date(2026, 4, 29, 21, 15, 0, 0, time.FixedZone("CST", 8*3600))
|
||||
jde := Date2JDE(date)
|
||||
_, offsetSeconds := date.Zone()
|
||||
timezone := float64(offsetSeconds) / 3600.0
|
||||
ra := 101.28715533
|
||||
dec := -16.71611586
|
||||
lon := 115.0
|
||||
lat := 40.0
|
||||
|
||||
got := StarParallacticAngle(jde, ra, dec, lon, lat, timezone)
|
||||
want := ParallacticAngleByHourAngle(StarHourAngle(jde, ra, lon, timezone), dec, lat)
|
||||
if math.Abs(got-want) > 1e-12 {
|
||||
t.Fatalf("star parallactic angle mismatch: got %.15f want %.15f", got, want)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
package basic
|
||||
|
||||
import (
|
||||
"math"
|
||||
|
||||
"b612.me/astro/planet"
|
||||
. "b612.me/astro/tools"
|
||||
)
|
||||
|
||||
type planetGeocentricPosition struct {
|
||||
x float64
|
||||
y float64
|
||||
z float64
|
||||
lo float64
|
||||
bo float64
|
||||
}
|
||||
|
||||
func planetHeliocentricXYZN(planetIndex int, jd float64, n int) (float64, float64, float64) {
|
||||
l := planet.WherePlanetN(planetIndex, 0, jd, n)
|
||||
b := planet.WherePlanetN(planetIndex, 1, jd, n)
|
||||
r := planet.WherePlanetN(planetIndex, 2, jd, n)
|
||||
return sphericalToRectangular(l, b, r)
|
||||
}
|
||||
|
||||
func earthHeliocentricXYZN(jd float64, n int) (float64, float64, float64) {
|
||||
l := planet.WherePlanetN(-1, 0, jd, n)
|
||||
b := planet.WherePlanetN(-1, 1, jd, n)
|
||||
r := planet.WherePlanetN(-1, 2, jd, n)
|
||||
return sphericalToRectangular(l, b, r)
|
||||
}
|
||||
|
||||
func sphericalToRectangular(lo, bo, radius float64) (float64, float64, float64) {
|
||||
cosBo := math.Cos(bo * math.Pi / 180)
|
||||
return radius * cosBo * math.Cos(lo*math.Pi/180),
|
||||
radius * cosBo * math.Sin(lo*math.Pi/180),
|
||||
radius * math.Sin(bo*math.Pi/180)
|
||||
}
|
||||
|
||||
func geocentricPositionFromRectangular(x, y, z float64) planetGeocentricPosition {
|
||||
lo := math.Atan2(y, x) * 180 / math.Pi
|
||||
bo := math.Atan2(z, math.Sqrt(x*x+y*y)) * 180 / math.Pi
|
||||
return planetGeocentricPosition{
|
||||
x: x,
|
||||
y: y,
|
||||
z: z,
|
||||
lo: Limit360(lo),
|
||||
bo: bo,
|
||||
}
|
||||
}
|
||||
|
||||
func planetGeocentricPositionN(planetIndex int, planetJD, earthJD float64, n int) planetGeocentricPosition {
|
||||
px, py, pz := planetHeliocentricXYZN(planetIndex, planetJD, n)
|
||||
ex, ey, ez := earthHeliocentricXYZN(earthJD, n)
|
||||
return geocentricPositionFromRectangular(px-ex, py-ey, pz-ez)
|
||||
}
|
||||
|
||||
func planetGeocentricPositionWithEarthN(planetIndex int, planetJD float64, ex, ey, ez float64, n int) planetGeocentricPosition {
|
||||
px, py, pz := planetHeliocentricXYZN(planetIndex, planetJD, n)
|
||||
return geocentricPositionFromRectangular(px-ex, py-ey, pz-ez)
|
||||
}
|
||||
|
||||
func planetApparentGeocentricPositionN(planetIndex int, jd float64, n int) (planetGeocentricPosition, float64) {
|
||||
ex, ey, ez := earthHeliocentricXYZN(jd, n)
|
||||
geoNow := planetGeocentricPositionWithEarthN(planetIndex, jd, ex, ey, ez, n)
|
||||
tau := 0.0057755183 * math.Sqrt(geoNow.x*geoNow.x+geoNow.y*geoNow.y+geoNow.z*geoNow.z)
|
||||
geo := planetGeocentricPositionWithEarthN(planetIndex, jd-tau, ex, ey, ez, n)
|
||||
baseLo := geo.lo
|
||||
baseBo := geo.bo
|
||||
geo.lo = Limit360(baseLo + GXCLo(baseLo, baseBo, jd)/3600.0 + Nutation2000Bi(jd))
|
||||
geo.bo = baseBo + GXCBo(baseLo, baseBo, jd)/3600.0
|
||||
return geo, tau
|
||||
}
|
||||
|
||||
func planetTrueGeocentricPositionN(planetIndex int, jd float64, n int) (planetGeocentricPosition, float64) {
|
||||
ex, ey, ez := earthHeliocentricXYZN(jd, n)
|
||||
geoNow := planetGeocentricPositionWithEarthN(planetIndex, jd, ex, ey, ez, n)
|
||||
tau := 0.0057755183 * math.Sqrt(geoNow.x*geoNow.x+geoNow.y*geoNow.y+geoNow.z*geoNow.z)
|
||||
return planetGeocentricPositionWithEarthN(planetIndex, jd-tau, ex, ey, ez, n), tau
|
||||
}
|
||||
|
||||
func planetEarthAwayExplicitN(planetIndex int, jd float64, n int) float64 {
|
||||
geoNow := planetGeocentricPositionN(planetIndex, jd, jd, n)
|
||||
return math.Sqrt(geoNow.x*geoNow.x + geoNow.y*geoNow.y + geoNow.z*geoNow.z)
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
package basic
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
type planetApparentSample struct {
|
||||
Body string `json:"body"`
|
||||
InputUTC string `json:"input_utc"`
|
||||
RightAscension float64 `json:"right_ascension"`
|
||||
Declination float64 `json:"declination"`
|
||||
EclipticLongitude float64 `json:"ecliptic_longitude"`
|
||||
EclipticLatitude float64 `json:"ecliptic_latitude"`
|
||||
}
|
||||
|
||||
func TestPlanetApparentCoordinatesMatchHorizonsBaseline(t *testing.T) {
|
||||
data, err := os.ReadFile("testdata/planet_apparent_baseline.json")
|
||||
if err != nil {
|
||||
t.Fatalf("read baseline: %v", err)
|
||||
}
|
||||
|
||||
var samples []planetApparentSample
|
||||
if err := json.Unmarshal(data, &samples); err != nil {
|
||||
t.Fatalf("decode baseline: %v", err)
|
||||
}
|
||||
|
||||
type apparentCase struct {
|
||||
lo func(float64) float64
|
||||
bo func(float64) float64
|
||||
ra func(float64) float64
|
||||
dec func(float64) float64
|
||||
}
|
||||
|
||||
cases := map[string]apparentCase{
|
||||
"mercury": {lo: MercuryApparentLo, bo: MercuryApparentBo, ra: MercuryApparentRa, dec: MercuryApparentDec},
|
||||
"venus": {lo: VenusApparentLo, bo: VenusApparentBo, ra: VenusApparentRa, dec: VenusApparentDec},
|
||||
"mars": {lo: MarsApparentLo, bo: MarsApparentBo, ra: MarsApparentRa, dec: MarsApparentDec},
|
||||
"jupiter": {lo: JupiterApparentLo, bo: JupiterApparentBo, ra: JupiterApparentRa, dec: JupiterApparentDec},
|
||||
"saturn": {lo: SaturnApparentLo, bo: SaturnApparentBo, ra: SaturnApparentRa, dec: SaturnApparentDec},
|
||||
"uranus": {lo: UranusApparentLo, bo: UranusApparentBo, ra: UranusApparentRa, dec: UranusApparentDec},
|
||||
"neptune": {lo: NeptuneApparentLo, bo: NeptuneApparentBo, ra: NeptuneApparentRa, dec: NeptuneApparentDec},
|
||||
}
|
||||
|
||||
seen := make(map[string]bool, len(cases))
|
||||
for _, sample := range samples {
|
||||
tc, ok := cases[sample.Body]
|
||||
if !ok {
|
||||
t.Fatalf("unknown body %q", sample.Body)
|
||||
}
|
||||
if seen[sample.Body] {
|
||||
t.Fatalf("duplicate body %q in apparent baseline", sample.Body)
|
||||
}
|
||||
seen[sample.Body] = true
|
||||
|
||||
date, err := time.Parse(time.RFC3339, sample.InputUTC)
|
||||
if err != nil {
|
||||
t.Fatalf("parse sample time %q: %v", sample.InputUTC, err)
|
||||
}
|
||||
jd := TD2UT(Date2JDE(date.UTC()), true)
|
||||
prefix := sample.Body + "." + sample.InputUTC
|
||||
|
||||
assertPlanetApparentAngleClose(t, prefix+".RightAscension", tc.ra(jd), sample.RightAscension, 0.001)
|
||||
assertPlanetPhaseClose(t, prefix+".Declination", tc.dec(jd), sample.Declination, 0.001)
|
||||
assertPlanetApparentAngleClose(t, prefix+".EclipticLongitude", tc.lo(jd), sample.EclipticLongitude, 0.001)
|
||||
assertPlanetPhaseClose(t, prefix+".EclipticLatitude", tc.bo(jd), sample.EclipticLatitude, 0.001)
|
||||
}
|
||||
|
||||
for body := range cases {
|
||||
if !seen[body] {
|
||||
t.Fatalf("missing body %q in apparent baseline", body)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func assertPlanetApparentAngleClose(t *testing.T, name string, got, want, tolerance float64) {
|
||||
t.Helper()
|
||||
if diff := angleDiffAbs(got, want); diff > tolerance {
|
||||
t.Fatalf("%s mismatch: got %.12f want %.12f diff %.12f tolerance %.12f", name, got, want, diff, tolerance)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,200 @@
|
||||
package basic_test
|
||||
|
||||
import (
|
||||
"math"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"b612.me/astro/basic"
|
||||
)
|
||||
|
||||
func TestBasicPlanetObservationNFullMatchesDefault(t *testing.T) {
|
||||
date := time.Date(2026, 4, 26, 9, 30, 45, 123456789, time.FixedZone("CST", 8*3600))
|
||||
ttJD := basic.TD2UT(basic.Date2JDE(date.UTC()), true)
|
||||
jde := basic.Date2JDE(date)
|
||||
lon := 116.391
|
||||
lat := 39.907
|
||||
tz := 8.0
|
||||
height := 45.0
|
||||
|
||||
assertSame := func(name string, got, want float64) {
|
||||
t.Helper()
|
||||
if math.Float64bits(got) != math.Float64bits(want) {
|
||||
t.Fatalf("%s full-n mismatch", name)
|
||||
}
|
||||
}
|
||||
assertSamePair := func(name string, got1, got2, want1, want2 float64) {
|
||||
t.Helper()
|
||||
assertSame(name+".1", got1, want1)
|
||||
assertSame(name+".2", got2, want2)
|
||||
}
|
||||
assertSameErr := func(name string, got, want error) {
|
||||
t.Helper()
|
||||
if got != want {
|
||||
t.Fatalf("%s full-n mismatch", name)
|
||||
}
|
||||
}
|
||||
|
||||
floatChecks := []struct {
|
||||
name string
|
||||
got func() float64
|
||||
want func() float64
|
||||
}{
|
||||
{"MercuryApparentLo", func() float64 { return basic.MercuryApparentLo(ttJD) }, func() float64 { return basic.MercuryApparentLoN(ttJD, -1) }},
|
||||
{"MercuryApparentBo", func() float64 { return basic.MercuryApparentBo(ttJD) }, func() float64 { return basic.MercuryApparentBoN(ttJD, -1) }},
|
||||
{"MercuryApparentRa", func() float64 { return basic.MercuryApparentRa(ttJD) }, func() float64 { return basic.MercuryApparentRaN(ttJD, -1) }},
|
||||
{"MercuryApparentDec", func() float64 { return basic.MercuryApparentDec(ttJD) }, func() float64 { return basic.MercuryApparentDecN(ttJD, -1) }},
|
||||
{"EarthMercuryAway", func() float64 { return basic.EarthMercuryAway(ttJD) }, func() float64 { return basic.EarthMercuryAwayN(ttJD, -1) }},
|
||||
{"MercuryMag", func() float64 { return basic.MercuryMag(ttJD) }, func() float64 { return basic.MercuryMagN(ttJD, -1) }},
|
||||
{"MercuryPhaseAngle", func() float64 { return basic.MercuryPhaseAngle(ttJD) }, func() float64 { return basic.MercuryPhaseAngleN(ttJD, -1) }},
|
||||
{"MercuryIlluminatedFraction", func() float64 { return basic.MercuryIlluminatedFraction(ttJD) }, func() float64 { return basic.MercuryIlluminatedFractionN(ttJD, -1) }},
|
||||
{"MercuryBrightLimbPositionAngle", func() float64 { return basic.MercuryBrightLimbPositionAngle(ttJD) }, func() float64 { return basic.MercuryBrightLimbPositionAngleN(ttJD, -1) }},
|
||||
{"MercuryHeight", func() float64 { return basic.MercuryHeight(jde, lon, lat, tz) }, func() float64 { return basic.MercuryHeightN(jde, lon, lat, tz, -1) }},
|
||||
{"MercuryAzimuth", func() float64 { return basic.MercuryAzimuth(jde, lon, lat, tz) }, func() float64 { return basic.MercuryAzimuthN(jde, lon, lat, tz, -1) }},
|
||||
{"MercuryHourAngle", func() float64 { return basic.MercuryHourAngle(jde, lon, tz) }, func() float64 { return basic.MercuryHourAngleN(jde, lon, tz, -1) }},
|
||||
{"MercuryCulminationTime", func() float64 { return basic.MercuryCulminationTime(jde, lon, tz) }, func() float64 { return basic.MercuryCulminationTimeN(jde, lon, tz, -1) }},
|
||||
{"MercuryRiseTime", func() float64 { value, _ := basic.MercuryRiseTime(jde, lon, lat, tz, 1, height); return value }, func() float64 { value, _ := basic.MercuryRiseTimeN(jde, lon, lat, tz, 1, height, -1); return value }},
|
||||
{"MercurySetTime", func() float64 { value, _ := basic.MercurySetTime(jde, lon, lat, tz, 1, height); return value }, func() float64 { value, _ := basic.MercurySetTimeN(jde, lon, lat, tz, 1, height, -1); return value }},
|
||||
{"VenusApparentLo", func() float64 { return basic.VenusApparentLo(ttJD) }, func() float64 { return basic.VenusApparentLoN(ttJD, -1) }},
|
||||
{"VenusApparentBo", func() float64 { return basic.VenusApparentBo(ttJD) }, func() float64 { return basic.VenusApparentBoN(ttJD, -1) }},
|
||||
{"VenusApparentRa", func() float64 { return basic.VenusApparentRa(ttJD) }, func() float64 { return basic.VenusApparentRaN(ttJD, -1) }},
|
||||
{"VenusApparentDec", func() float64 { return basic.VenusApparentDec(ttJD) }, func() float64 { return basic.VenusApparentDecN(ttJD, -1) }},
|
||||
{"EarthVenusAway", func() float64 { return basic.EarthVenusAway(ttJD) }, func() float64 { return basic.EarthVenusAwayN(ttJD, -1) }},
|
||||
{"VenusMag", func() float64 { return basic.VenusMag(ttJD) }, func() float64 { return basic.VenusMagN(ttJD, -1) }},
|
||||
{"VenusPhaseAngle", func() float64 { return basic.VenusPhaseAngle(ttJD) }, func() float64 { return basic.VenusPhaseAngleN(ttJD, -1) }},
|
||||
{"VenusIlluminatedFraction", func() float64 { return basic.VenusIlluminatedFraction(ttJD) }, func() float64 { return basic.VenusIlluminatedFractionN(ttJD, -1) }},
|
||||
{"VenusBrightLimbPositionAngle", func() float64 { return basic.VenusBrightLimbPositionAngle(ttJD) }, func() float64 { return basic.VenusBrightLimbPositionAngleN(ttJD, -1) }},
|
||||
{"VenusHeight", func() float64 { return basic.VenusHeight(jde, lon, lat, tz) }, func() float64 { return basic.VenusHeightN(jde, lon, lat, tz, -1) }},
|
||||
{"VenusAzimuth", func() float64 { return basic.VenusAzimuth(jde, lon, lat, tz) }, func() float64 { return basic.VenusAzimuthN(jde, lon, lat, tz, -1) }},
|
||||
{"VenusHourAngle", func() float64 { return basic.VenusHourAngle(jde, lon, tz) }, func() float64 { return basic.VenusHourAngleN(jde, lon, tz, -1) }},
|
||||
{"VenusCulminationTime", func() float64 { return basic.VenusCulminationTime(jde, lon, tz) }, func() float64 { return basic.VenusCulminationTimeN(jde, lon, tz, -1) }},
|
||||
{"VenusRiseTime", func() float64 { value, _ := basic.VenusRiseTime(jde, lon, lat, tz, 1, height); return value }, func() float64 { value, _ := basic.VenusRiseTimeN(jde, lon, lat, tz, 1, height, -1); return value }},
|
||||
{"VenusSetTime", func() float64 { value, _ := basic.VenusSetTime(jde, lon, lat, tz, 1, height); return value }, func() float64 { value, _ := basic.VenusSetTimeN(jde, lon, lat, tz, 1, height, -1); return value }},
|
||||
{"MarsApparentLo", func() float64 { return basic.MarsApparentLo(ttJD) }, func() float64 { return basic.MarsApparentLoN(ttJD, -1) }},
|
||||
{"MarsApparentBo", func() float64 { return basic.MarsApparentBo(ttJD) }, func() float64 { return basic.MarsApparentBoN(ttJD, -1) }},
|
||||
{"MarsApparentRa", func() float64 { return basic.MarsApparentRa(ttJD) }, func() float64 { return basic.MarsApparentRaN(ttJD, -1) }},
|
||||
{"MarsApparentDec", func() float64 { return basic.MarsApparentDec(ttJD) }, func() float64 { return basic.MarsApparentDecN(ttJD, -1) }},
|
||||
{"EarthMarsAway", func() float64 { return basic.EarthMarsAway(ttJD) }, func() float64 { return basic.EarthMarsAwayN(ttJD, -1) }},
|
||||
{"MarsMag", func() float64 { return basic.MarsMag(ttJD) }, func() float64 { return basic.MarsMagN(ttJD, -1) }},
|
||||
{"MarsPhaseAngle", func() float64 { return basic.MarsPhaseAngle(ttJD) }, func() float64 { return basic.MarsPhaseAngleN(ttJD, -1) }},
|
||||
{"MarsIlluminatedFraction", func() float64 { return basic.MarsIlluminatedFraction(ttJD) }, func() float64 { return basic.MarsIlluminatedFractionN(ttJD, -1) }},
|
||||
{"MarsBrightLimbPositionAngle", func() float64 { return basic.MarsBrightLimbPositionAngle(ttJD) }, func() float64 { return basic.MarsBrightLimbPositionAngleN(ttJD, -1) }},
|
||||
{"MarsHeight", func() float64 { return basic.MarsHeight(jde, lon, lat, tz) }, func() float64 { return basic.MarsHeightN(jde, lon, lat, tz, -1) }},
|
||||
{"MarsAzimuth", func() float64 { return basic.MarsAzimuth(jde, lon, lat, tz) }, func() float64 { return basic.MarsAzimuthN(jde, lon, lat, tz, -1) }},
|
||||
{"MarsHourAngle", func() float64 { return basic.MarsHourAngle(jde, lon, tz) }, func() float64 { return basic.MarsHourAngleN(jde, lon, tz, -1) }},
|
||||
{"MarsCulminationTime", func() float64 { return basic.MarsCulminationTime(jde, lon, tz) }, func() float64 { return basic.MarsCulminationTimeN(jde, lon, tz, -1) }},
|
||||
{"MarsRiseTime", func() float64 { value, _ := basic.MarsRiseTime(jde, lon, lat, tz, 1, height); return value }, func() float64 { value, _ := basic.MarsRiseTimeN(jde, lon, lat, tz, 1, height, -1); return value }},
|
||||
{"MarsSetTime", func() float64 { value, _ := basic.MarsSetTime(jde, lon, lat, tz, 1, height); return value }, func() float64 { value, _ := basic.MarsSetTimeN(jde, lon, lat, tz, 1, height, -1); return value }},
|
||||
{"JupiterApparentLo", func() float64 { return basic.JupiterApparentLo(ttJD) }, func() float64 { return basic.JupiterApparentLoN(ttJD, -1) }},
|
||||
{"JupiterApparentBo", func() float64 { return basic.JupiterApparentBo(ttJD) }, func() float64 { return basic.JupiterApparentBoN(ttJD, -1) }},
|
||||
{"JupiterApparentRa", func() float64 { return basic.JupiterApparentRa(ttJD) }, func() float64 { return basic.JupiterApparentRaN(ttJD, -1) }},
|
||||
{"JupiterApparentDec", func() float64 { return basic.JupiterApparentDec(ttJD) }, func() float64 { return basic.JupiterApparentDecN(ttJD, -1) }},
|
||||
{"EarthJupiterAway", func() float64 { return basic.EarthJupiterAway(ttJD) }, func() float64 { return basic.EarthJupiterAwayN(ttJD, -1) }},
|
||||
{"JupiterMag", func() float64 { return basic.JupiterMag(ttJD) }, func() float64 { return basic.JupiterMagN(ttJD, -1) }},
|
||||
{"JupiterPhaseAngle", func() float64 { return basic.JupiterPhaseAngle(ttJD) }, func() float64 { return basic.JupiterPhaseAngleN(ttJD, -1) }},
|
||||
{"JupiterIlluminatedFraction", func() float64 { return basic.JupiterIlluminatedFraction(ttJD) }, func() float64 { return basic.JupiterIlluminatedFractionN(ttJD, -1) }},
|
||||
{"JupiterBrightLimbPositionAngle", func() float64 { return basic.JupiterBrightLimbPositionAngle(ttJD) }, func() float64 { return basic.JupiterBrightLimbPositionAngleN(ttJD, -1) }},
|
||||
{"JupiterHeight", func() float64 { return basic.JupiterHeight(jde, lon, lat, tz) }, func() float64 { return basic.JupiterHeightN(jde, lon, lat, tz, -1) }},
|
||||
{"JupiterAzimuth", func() float64 { return basic.JupiterAzimuth(jde, lon, lat, tz) }, func() float64 { return basic.JupiterAzimuthN(jde, lon, lat, tz, -1) }},
|
||||
{"JupiterHourAngle", func() float64 { return basic.JupiterHourAngle(jde, lon, tz) }, func() float64 { return basic.JupiterHourAngleN(jde, lon, tz, -1) }},
|
||||
{"JupiterCulminationTime", func() float64 { return basic.JupiterCulminationTime(jde, lon, tz) }, func() float64 { return basic.JupiterCulminationTimeN(jde, lon, tz, -1) }},
|
||||
{"JupiterRiseTime", func() float64 { value, _ := basic.JupiterRiseTime(jde, lon, lat, tz, 1, height); return value }, func() float64 { value, _ := basic.JupiterRiseTimeN(jde, lon, lat, tz, 1, height, -1); return value }},
|
||||
{"JupiterSetTime", func() float64 { value, _ := basic.JupiterSetTime(jde, lon, lat, tz, 1, height); return value }, func() float64 { value, _ := basic.JupiterSetTimeN(jde, lon, lat, tz, 1, height, -1); return value }},
|
||||
{"SaturnApparentLo", func() float64 { return basic.SaturnApparentLo(ttJD) }, func() float64 { return basic.SaturnApparentLoN(ttJD, -1) }},
|
||||
{"SaturnApparentBo", func() float64 { return basic.SaturnApparentBo(ttJD) }, func() float64 { return basic.SaturnApparentBoN(ttJD, -1) }},
|
||||
{"SaturnApparentRa", func() float64 { return basic.SaturnApparentRa(ttJD) }, func() float64 { return basic.SaturnApparentRaN(ttJD, -1) }},
|
||||
{"SaturnApparentDec", func() float64 { return basic.SaturnApparentDec(ttJD) }, func() float64 { return basic.SaturnApparentDecN(ttJD, -1) }},
|
||||
{"EarthSaturnAway", func() float64 { return basic.EarthSaturnAway(ttJD) }, func() float64 { return basic.EarthSaturnAwayN(ttJD, -1) }},
|
||||
{"SaturnRingB", func() float64 { return basic.SaturnRingB(ttJD) }, func() float64 { return basic.SaturnRingBN(ttJD, -1) }},
|
||||
{"SaturnRingSunB", func() float64 { return basic.SaturnRingSunB(ttJD) }, func() float64 { return basic.SaturnRingSunBN(ttJD, -1) }},
|
||||
{"SaturnRingPositionAngle", func() float64 { return basic.SaturnRingPositionAngle(ttJD) }, func() float64 { return basic.SaturnRingPositionAngleN(ttJD, -1) }},
|
||||
{"SaturnRingDeltaU", func() float64 { return basic.SaturnRingDeltaU(ttJD) }, func() float64 { return basic.SaturnRingDeltaUN(ttJD, -1) }},
|
||||
{"SaturnMag", func() float64 { return basic.SaturnMag(ttJD) }, func() float64 { return basic.SaturnMagN(ttJD, -1) }},
|
||||
{"SaturnPhaseAngle", func() float64 { return basic.SaturnPhaseAngle(ttJD) }, func() float64 { return basic.SaturnPhaseAngleN(ttJD, -1) }},
|
||||
{"SaturnIlluminatedFraction", func() float64 { return basic.SaturnIlluminatedFraction(ttJD) }, func() float64 { return basic.SaturnIlluminatedFractionN(ttJD, -1) }},
|
||||
{"SaturnBrightLimbPositionAngle", func() float64 { return basic.SaturnBrightLimbPositionAngle(ttJD) }, func() float64 { return basic.SaturnBrightLimbPositionAngleN(ttJD, -1) }},
|
||||
{"SaturnHeight", func() float64 { return basic.SaturnHeight(jde, lon, lat, tz) }, func() float64 { return basic.SaturnHeightN(jde, lon, lat, tz, -1) }},
|
||||
{"SaturnAzimuth", func() float64 { return basic.SaturnAzimuth(jde, lon, lat, tz) }, func() float64 { return basic.SaturnAzimuthN(jde, lon, lat, tz, -1) }},
|
||||
{"SaturnHourAngle", func() float64 { return basic.SaturnHourAngle(jde, lon, tz) }, func() float64 { return basic.SaturnHourAngleN(jde, lon, tz, -1) }},
|
||||
{"SaturnCulminationTime", func() float64 { return basic.SaturnCulminationTime(jde, lon, tz) }, func() float64 { return basic.SaturnCulminationTimeN(jde, lon, tz, -1) }},
|
||||
{"SaturnRiseTime", func() float64 { value, _ := basic.SaturnRiseTime(jde, lon, lat, tz, 1, height); return value }, func() float64 { value, _ := basic.SaturnRiseTimeN(jde, lon, lat, tz, 1, height, -1); return value }},
|
||||
{"SaturnSetTime", func() float64 { value, _ := basic.SaturnSetTime(jde, lon, lat, tz, 1, height); return value }, func() float64 { value, _ := basic.SaturnSetTimeN(jde, lon, lat, tz, 1, height, -1); return value }},
|
||||
{"UranusApparentLo", func() float64 { return basic.UranusApparentLo(ttJD) }, func() float64 { return basic.UranusApparentLoN(ttJD, -1) }},
|
||||
{"UranusApparentBo", func() float64 { return basic.UranusApparentBo(ttJD) }, func() float64 { return basic.UranusApparentBoN(ttJD, -1) }},
|
||||
{"UranusApparentRa", func() float64 { return basic.UranusApparentRa(ttJD) }, func() float64 { return basic.UranusApparentRaN(ttJD, -1) }},
|
||||
{"UranusApparentDec", func() float64 { return basic.UranusApparentDec(ttJD) }, func() float64 { return basic.UranusApparentDecN(ttJD, -1) }},
|
||||
{"EarthUranusAway", func() float64 { return basic.EarthUranusAway(ttJD) }, func() float64 { return basic.EarthUranusAwayN(ttJD, -1) }},
|
||||
{"UranusMag", func() float64 { return basic.UranusMag(ttJD) }, func() float64 { return basic.UranusMagN(ttJD, -1) }},
|
||||
{"UranusPhaseAngle", func() float64 { return basic.UranusPhaseAngle(ttJD) }, func() float64 { return basic.UranusPhaseAngleN(ttJD, -1) }},
|
||||
{"UranusIlluminatedFraction", func() float64 { return basic.UranusIlluminatedFraction(ttJD) }, func() float64 { return basic.UranusIlluminatedFractionN(ttJD, -1) }},
|
||||
{"UranusBrightLimbPositionAngle", func() float64 { return basic.UranusBrightLimbPositionAngle(ttJD) }, func() float64 { return basic.UranusBrightLimbPositionAngleN(ttJD, -1) }},
|
||||
{"UranusHeight", func() float64 { return basic.UranusHeight(jde, lon, lat, tz) }, func() float64 { return basic.UranusHeightN(jde, lon, lat, tz, -1) }},
|
||||
{"UranusAzimuth", func() float64 { return basic.UranusAzimuth(jde, lon, lat, tz) }, func() float64 { return basic.UranusAzimuthN(jde, lon, lat, tz, -1) }},
|
||||
{"UranusHourAngle", func() float64 { return basic.UranusHourAngle(jde, lon, tz) }, func() float64 { return basic.UranusHourAngleN(jde, lon, tz, -1) }},
|
||||
{"UranusCulminationTime", func() float64 { return basic.UranusCulminationTime(jde, lon, tz) }, func() float64 { return basic.UranusCulminationTimeN(jde, lon, tz, -1) }},
|
||||
{"UranusRiseTime", func() float64 { value, _ := basic.UranusRiseTime(jde, lon, lat, tz, 1, height); return value }, func() float64 { value, _ := basic.UranusRiseTimeN(jde, lon, lat, tz, 1, height, -1); return value }},
|
||||
{"UranusSetTime", func() float64 { value, _ := basic.UranusSetTime(jde, lon, lat, tz, 1, height); return value }, func() float64 { value, _ := basic.UranusSetTimeN(jde, lon, lat, tz, 1, height, -1); return value }},
|
||||
{"NeptuneApparentLo", func() float64 { return basic.NeptuneApparentLo(ttJD) }, func() float64 { return basic.NeptuneApparentLoN(ttJD, -1) }},
|
||||
{"NeptuneApparentBo", func() float64 { return basic.NeptuneApparentBo(ttJD) }, func() float64 { return basic.NeptuneApparentBoN(ttJD, -1) }},
|
||||
{"NeptuneApparentRa", func() float64 { return basic.NeptuneApparentRa(ttJD) }, func() float64 { return basic.NeptuneApparentRaN(ttJD, -1) }},
|
||||
{"NeptuneApparentDec", func() float64 { return basic.NeptuneApparentDec(ttJD) }, func() float64 { return basic.NeptuneApparentDecN(ttJD, -1) }},
|
||||
{"EarthNeptuneAway", func() float64 { return basic.EarthNeptuneAway(ttJD) }, func() float64 { return basic.EarthNeptuneAwayN(ttJD, -1) }},
|
||||
{"NeptuneMag", func() float64 { return basic.NeptuneMag(ttJD) }, func() float64 { return basic.NeptuneMagN(ttJD, -1) }},
|
||||
{"NeptunePhaseAngle", func() float64 { return basic.NeptunePhaseAngle(ttJD) }, func() float64 { return basic.NeptunePhaseAngleN(ttJD, -1) }},
|
||||
{"NeptuneIlluminatedFraction", func() float64 { return basic.NeptuneIlluminatedFraction(ttJD) }, func() float64 { return basic.NeptuneIlluminatedFractionN(ttJD, -1) }},
|
||||
{"NeptuneBrightLimbPositionAngle", func() float64 { return basic.NeptuneBrightLimbPositionAngle(ttJD) }, func() float64 { return basic.NeptuneBrightLimbPositionAngleN(ttJD, -1) }},
|
||||
{"NeptuneHeight", func() float64 { return basic.NeptuneHeight(jde, lon, lat, tz) }, func() float64 { return basic.NeptuneHeightN(jde, lon, lat, tz, -1) }},
|
||||
{"NeptuneAzimuth", func() float64 { return basic.NeptuneAzimuth(jde, lon, lat, tz) }, func() float64 { return basic.NeptuneAzimuthN(jde, lon, lat, tz, -1) }},
|
||||
{"NeptuneHourAngle", func() float64 { return basic.NeptuneHourAngle(jde, lon, tz) }, func() float64 { return basic.NeptuneHourAngleN(jde, lon, tz, -1) }},
|
||||
{"NeptuneCulminationTime", func() float64 { return basic.NeptuneCulminationTime(jde, lon, tz) }, func() float64 { return basic.NeptuneCulminationTimeN(jde, lon, tz, -1) }},
|
||||
{"NeptuneRiseTime", func() float64 { value, _ := basic.NeptuneRiseTime(jde, lon, lat, tz, 1, height); return value }, func() float64 { value, _ := basic.NeptuneRiseTimeN(jde, lon, lat, tz, 1, height, -1); return value }},
|
||||
{"NeptuneSetTime", func() float64 { value, _ := basic.NeptuneSetTime(jde, lon, lat, tz, 1, height); return value }, func() float64 { value, _ := basic.NeptuneSetTimeN(jde, lon, lat, tz, 1, height, -1); return value }},
|
||||
}
|
||||
for _, tc := range floatChecks {
|
||||
assertSame(tc.name, tc.got(), tc.want())
|
||||
}
|
||||
|
||||
errorChecks := []struct {
|
||||
name string
|
||||
got func() error
|
||||
want func() error
|
||||
}{
|
||||
{"MercuryRiseTime.err", func() error { _, err := basic.MercuryRiseTime(jde, lon, lat, tz, 1, height); return err }, func() error { _, err := basic.MercuryRiseTimeN(jde, lon, lat, tz, 1, height, -1); return err }},
|
||||
{"MercurySetTime.err", func() error { _, err := basic.MercurySetTime(jde, lon, lat, tz, 1, height); return err }, func() error { _, err := basic.MercurySetTimeN(jde, lon, lat, tz, 1, height, -1); return err }},
|
||||
{"VenusRiseTime.err", func() error { _, err := basic.VenusRiseTime(jde, lon, lat, tz, 1, height); return err }, func() error { _, err := basic.VenusRiseTimeN(jde, lon, lat, tz, 1, height, -1); return err }},
|
||||
{"VenusSetTime.err", func() error { _, err := basic.VenusSetTime(jde, lon, lat, tz, 1, height); return err }, func() error { _, err := basic.VenusSetTimeN(jde, lon, lat, tz, 1, height, -1); return err }},
|
||||
{"MarsRiseTime.err", func() error { _, err := basic.MarsRiseTime(jde, lon, lat, tz, 1, height); return err }, func() error { _, err := basic.MarsRiseTimeN(jde, lon, lat, tz, 1, height, -1); return err }},
|
||||
{"MarsSetTime.err", func() error { _, err := basic.MarsSetTime(jde, lon, lat, tz, 1, height); return err }, func() error { _, err := basic.MarsSetTimeN(jde, lon, lat, tz, 1, height, -1); return err }},
|
||||
{"JupiterRiseTime.err", func() error { _, err := basic.JupiterRiseTime(jde, lon, lat, tz, 1, height); return err }, func() error { _, err := basic.JupiterRiseTimeN(jde, lon, lat, tz, 1, height, -1); return err }},
|
||||
{"JupiterSetTime.err", func() error { _, err := basic.JupiterSetTime(jde, lon, lat, tz, 1, height); return err }, func() error { _, err := basic.JupiterSetTimeN(jde, lon, lat, tz, 1, height, -1); return err }},
|
||||
{"SaturnRiseTime.err", func() error { _, err := basic.SaturnRiseTime(jde, lon, lat, tz, 1, height); return err }, func() error { _, err := basic.SaturnRiseTimeN(jde, lon, lat, tz, 1, height, -1); return err }},
|
||||
{"SaturnSetTime.err", func() error { _, err := basic.SaturnSetTime(jde, lon, lat, tz, 1, height); return err }, func() error { _, err := basic.SaturnSetTimeN(jde, lon, lat, tz, 1, height, -1); return err }},
|
||||
{"UranusRiseTime.err", func() error { _, err := basic.UranusRiseTime(jde, lon, lat, tz, 1, height); return err }, func() error { _, err := basic.UranusRiseTimeN(jde, lon, lat, tz, 1, height, -1); return err }},
|
||||
{"UranusSetTime.err", func() error { _, err := basic.UranusSetTime(jde, lon, lat, tz, 1, height); return err }, func() error { _, err := basic.UranusSetTimeN(jde, lon, lat, tz, 1, height, -1); return err }},
|
||||
{"NeptuneRiseTime.err", func() error { _, err := basic.NeptuneRiseTime(jde, lon, lat, tz, 1, height); return err }, func() error { _, err := basic.NeptuneRiseTimeN(jde, lon, lat, tz, 1, height, -1); return err }},
|
||||
{"NeptuneSetTime.err", func() error { _, err := basic.NeptuneSetTime(jde, lon, lat, tz, 1, height); return err }, func() error { _, err := basic.NeptuneSetTimeN(jde, lon, lat, tz, 1, height, -1); return err }},
|
||||
}
|
||||
for _, tc := range errorChecks {
|
||||
assertSameErr(tc.name, tc.got(), tc.want())
|
||||
}
|
||||
|
||||
pairChecks := []struct {
|
||||
name string
|
||||
got func() (float64, float64)
|
||||
want func() (float64, float64)
|
||||
}{
|
||||
{"MercuryApparentRaDec", func() (float64, float64) { return basic.MercuryApparentRaDec(ttJD) }, func() (float64, float64) { return basic.MercuryApparentRaDecN(ttJD, -1) }},
|
||||
{"VenusApparentRaDec", func() (float64, float64) { return basic.VenusApparentRaDec(ttJD) }, func() (float64, float64) { return basic.VenusApparentRaDecN(ttJD, -1) }},
|
||||
{"MarsApparentRaDec", func() (float64, float64) { return basic.MarsApparentRaDec(ttJD) }, func() (float64, float64) { return basic.MarsApparentRaDecN(ttJD, -1) }},
|
||||
{"JupiterApparentRaDec", func() (float64, float64) { return basic.JupiterApparentRaDec(ttJD) }, func() (float64, float64) { return basic.JupiterApparentRaDecN(ttJD, -1) }},
|
||||
{"SaturnApparentRaDec", func() (float64, float64) { return basic.SaturnApparentRaDec(ttJD) }, func() (float64, float64) { return basic.SaturnApparentRaDecN(ttJD, -1) }},
|
||||
{"SaturnRingAxis", func() (float64, float64) { return basic.SaturnRingAxis(ttJD) }, func() (float64, float64) { return basic.SaturnRingAxisN(ttJD, -1) }},
|
||||
{"UranusApparentRaDec", func() (float64, float64) { return basic.UranusApparentRaDec(ttJD) }, func() (float64, float64) { return basic.UranusApparentRaDecN(ttJD, -1) }},
|
||||
{"NeptuneApparentRaDec", func() (float64, float64) { return basic.NeptuneApparentRaDec(ttJD) }, func() (float64, float64) { return basic.NeptuneApparentRaDecN(ttJD, -1) }},
|
||||
}
|
||||
for _, tc := range pairChecks {
|
||||
got1, got2 := tc.got()
|
||||
want1, want2 := tc.want()
|
||||
assertSamePair(tc.name, got1, got2, want1, want2)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,252 @@
|
||||
package basic
|
||||
|
||||
import (
|
||||
"b612.me/astro/planet"
|
||||
. "b612.me/astro/tools"
|
||||
)
|
||||
|
||||
type planetRaDecNFunc func(jd float64, n int) (float64, float64)
|
||||
|
||||
// MercuryPhaseAngle 水星相位角 / phase angle of Mercury.
|
||||
func MercuryPhaseAngle(jd float64) float64 {
|
||||
return MercuryPhaseAngleN(jd, -1)
|
||||
}
|
||||
|
||||
// MercuryPhaseAngleN 水星相位角(截断版) / truncated phase angle of Mercury.
|
||||
func MercuryPhaseAngleN(jd float64, n int) float64 {
|
||||
return planetPhaseAngleN(1, jd, n)
|
||||
}
|
||||
|
||||
// MercuryIlluminatedFraction 水星被照亮比例 / illuminated fraction of Mercury.
|
||||
func MercuryIlluminatedFraction(jd float64) float64 {
|
||||
return MercuryIlluminatedFractionN(jd, -1)
|
||||
}
|
||||
|
||||
// MercuryIlluminatedFractionN 水星被照亮比例(截断版) / truncated illuminated fraction of Mercury.
|
||||
func MercuryIlluminatedFractionN(jd float64, n int) float64 {
|
||||
return planetIlluminatedFractionN(1, jd, n)
|
||||
}
|
||||
|
||||
// MercuryBrightLimbPositionAngle 水星亮面中心位置角 / position angle of Mercury bright limb.
|
||||
func MercuryBrightLimbPositionAngle(jd float64) float64 {
|
||||
return MercuryBrightLimbPositionAngleN(jd, -1)
|
||||
}
|
||||
|
||||
// MercuryBrightLimbPositionAngleN 水星亮面中心位置角(截断版) / truncated position angle of Mercury bright limb.
|
||||
func MercuryBrightLimbPositionAngleN(jd float64, n int) float64 {
|
||||
return planetBrightLimbPositionAngleN(jd, n, MercuryApparentRaDecN)
|
||||
}
|
||||
|
||||
// VenusPhaseAngle 金星相位角 / phase angle of Venus.
|
||||
func VenusPhaseAngle(jd float64) float64 {
|
||||
return VenusPhaseAngleN(jd, -1)
|
||||
}
|
||||
|
||||
// VenusPhaseAngleN 金星相位角(截断版) / truncated phase angle of Venus.
|
||||
func VenusPhaseAngleN(jd float64, n int) float64 {
|
||||
return planetPhaseAngleN(2, jd, n)
|
||||
}
|
||||
|
||||
// VenusIlluminatedFraction 金星被照亮比例 / illuminated fraction of Venus.
|
||||
func VenusIlluminatedFraction(jd float64) float64 {
|
||||
return VenusIlluminatedFractionN(jd, -1)
|
||||
}
|
||||
|
||||
// VenusIlluminatedFractionN 金星被照亮比例(截断版) / truncated illuminated fraction of Venus.
|
||||
func VenusIlluminatedFractionN(jd float64, n int) float64 {
|
||||
return planetIlluminatedFractionN(2, jd, n)
|
||||
}
|
||||
|
||||
// VenusBrightLimbPositionAngle 金星亮面中心位置角 / position angle of Venus bright limb.
|
||||
func VenusBrightLimbPositionAngle(jd float64) float64 {
|
||||
return VenusBrightLimbPositionAngleN(jd, -1)
|
||||
}
|
||||
|
||||
// VenusBrightLimbPositionAngleN 金星亮面中心位置角(截断版) / truncated position angle of Venus bright limb.
|
||||
func VenusBrightLimbPositionAngleN(jd float64, n int) float64 {
|
||||
return planetBrightLimbPositionAngleN(jd, n, VenusApparentRaDecN)
|
||||
}
|
||||
|
||||
// MarsPhaseAngle 火星相位角 / phase angle of Mars.
|
||||
func MarsPhaseAngle(jd float64) float64 {
|
||||
return MarsPhaseAngleN(jd, -1)
|
||||
}
|
||||
|
||||
// MarsPhaseAngleN 火星相位角(截断版) / truncated phase angle of Mars.
|
||||
func MarsPhaseAngleN(jd float64, n int) float64 {
|
||||
return planetPhaseAngleN(3, jd, n)
|
||||
}
|
||||
|
||||
// MarsIlluminatedFraction 火星被照亮比例 / illuminated fraction of Mars.
|
||||
func MarsIlluminatedFraction(jd float64) float64 {
|
||||
return MarsIlluminatedFractionN(jd, -1)
|
||||
}
|
||||
|
||||
// MarsIlluminatedFractionN 火星被照亮比例(截断版) / truncated illuminated fraction of Mars.
|
||||
func MarsIlluminatedFractionN(jd float64, n int) float64 {
|
||||
return planetIlluminatedFractionN(3, jd, n)
|
||||
}
|
||||
|
||||
// MarsBrightLimbPositionAngle 火星亮面中心位置角 / position angle of Mars bright limb.
|
||||
func MarsBrightLimbPositionAngle(jd float64) float64 {
|
||||
return MarsBrightLimbPositionAngleN(jd, -1)
|
||||
}
|
||||
|
||||
// MarsBrightLimbPositionAngleN 火星亮面中心位置角(截断版) / truncated position angle of Mars bright limb.
|
||||
func MarsBrightLimbPositionAngleN(jd float64, n int) float64 {
|
||||
return planetBrightLimbPositionAngleN(jd, n, MarsApparentRaDecN)
|
||||
}
|
||||
|
||||
// JupiterPhaseAngle 木星相位角 / phase angle of Jupiter.
|
||||
func JupiterPhaseAngle(jd float64) float64 {
|
||||
return JupiterPhaseAngleN(jd, -1)
|
||||
}
|
||||
|
||||
// JupiterPhaseAngleN 木星相位角(截断版) / truncated phase angle of Jupiter.
|
||||
func JupiterPhaseAngleN(jd float64, n int) float64 {
|
||||
return planetPhaseAngleN(4, jd, n)
|
||||
}
|
||||
|
||||
// JupiterIlluminatedFraction 木星被照亮比例 / illuminated fraction of Jupiter.
|
||||
func JupiterIlluminatedFraction(jd float64) float64 {
|
||||
return JupiterIlluminatedFractionN(jd, -1)
|
||||
}
|
||||
|
||||
// JupiterIlluminatedFractionN 木星被照亮比例(截断版) / truncated illuminated fraction of Jupiter.
|
||||
func JupiterIlluminatedFractionN(jd float64, n int) float64 {
|
||||
return planetIlluminatedFractionN(4, jd, n)
|
||||
}
|
||||
|
||||
// JupiterBrightLimbPositionAngle 木星亮面中心位置角 / position angle of Jupiter bright limb.
|
||||
func JupiterBrightLimbPositionAngle(jd float64) float64 {
|
||||
return JupiterBrightLimbPositionAngleN(jd, -1)
|
||||
}
|
||||
|
||||
// JupiterBrightLimbPositionAngleN 木星亮面中心位置角(截断版) / truncated position angle of Jupiter bright limb.
|
||||
func JupiterBrightLimbPositionAngleN(jd float64, n int) float64 {
|
||||
return planetBrightLimbPositionAngleN(jd, n, JupiterApparentRaDecN)
|
||||
}
|
||||
|
||||
// SaturnPhaseAngle 土星相位角 / phase angle of Saturn.
|
||||
func SaturnPhaseAngle(jd float64) float64 {
|
||||
return SaturnPhaseAngleN(jd, -1)
|
||||
}
|
||||
|
||||
// SaturnPhaseAngleN 土星相位角(截断版) / truncated phase angle of Saturn.
|
||||
func SaturnPhaseAngleN(jd float64, n int) float64 {
|
||||
return planetPhaseAngleN(5, jd, n)
|
||||
}
|
||||
|
||||
// SaturnIlluminatedFraction 土星被照亮比例 / illuminated fraction of Saturn.
|
||||
func SaturnIlluminatedFraction(jd float64) float64 {
|
||||
return SaturnIlluminatedFractionN(jd, -1)
|
||||
}
|
||||
|
||||
// SaturnIlluminatedFractionN 土星被照亮比例(截断版) / truncated illuminated fraction of Saturn.
|
||||
func SaturnIlluminatedFractionN(jd float64, n int) float64 {
|
||||
return planetIlluminatedFractionN(5, jd, n)
|
||||
}
|
||||
|
||||
// SaturnBrightLimbPositionAngle 土星亮面中心位置角 / position angle of Saturn bright limb.
|
||||
func SaturnBrightLimbPositionAngle(jd float64) float64 {
|
||||
return SaturnBrightLimbPositionAngleN(jd, -1)
|
||||
}
|
||||
|
||||
// SaturnBrightLimbPositionAngleN 土星亮面中心位置角(截断版) / truncated position angle of Saturn bright limb.
|
||||
func SaturnBrightLimbPositionAngleN(jd float64, n int) float64 {
|
||||
return planetBrightLimbPositionAngleN(jd, n, SaturnApparentRaDecN)
|
||||
}
|
||||
|
||||
// UranusPhaseAngle 天王星相位角 / phase angle of Uranus.
|
||||
func UranusPhaseAngle(jd float64) float64 {
|
||||
return UranusPhaseAngleN(jd, -1)
|
||||
}
|
||||
|
||||
// UranusPhaseAngleN 天王星相位角(截断版) / truncated phase angle of Uranus.
|
||||
func UranusPhaseAngleN(jd float64, n int) float64 {
|
||||
return planetPhaseAngleN(6, jd, n)
|
||||
}
|
||||
|
||||
// UranusIlluminatedFraction 天王星被照亮比例 / illuminated fraction of Uranus.
|
||||
func UranusIlluminatedFraction(jd float64) float64 {
|
||||
return UranusIlluminatedFractionN(jd, -1)
|
||||
}
|
||||
|
||||
// UranusIlluminatedFractionN 天王星被照亮比例(截断版) / truncated illuminated fraction of Uranus.
|
||||
func UranusIlluminatedFractionN(jd float64, n int) float64 {
|
||||
return planetIlluminatedFractionN(6, jd, n)
|
||||
}
|
||||
|
||||
// UranusBrightLimbPositionAngle 天王星亮面中心位置角 / position angle of Uranus bright limb.
|
||||
func UranusBrightLimbPositionAngle(jd float64) float64 {
|
||||
return UranusBrightLimbPositionAngleN(jd, -1)
|
||||
}
|
||||
|
||||
// UranusBrightLimbPositionAngleN 天王星亮面中心位置角(截断版) / truncated position angle of Uranus bright limb.
|
||||
func UranusBrightLimbPositionAngleN(jd float64, n int) float64 {
|
||||
return planetBrightLimbPositionAngleN(jd, n, UranusApparentRaDecN)
|
||||
}
|
||||
|
||||
// NeptunePhaseAngle 海王星相位角 / phase angle of Neptune.
|
||||
func NeptunePhaseAngle(jd float64) float64 {
|
||||
return NeptunePhaseAngleN(jd, -1)
|
||||
}
|
||||
|
||||
// NeptunePhaseAngleN 海王星相位角(截断版) / truncated phase angle of Neptune.
|
||||
func NeptunePhaseAngleN(jd float64, n int) float64 {
|
||||
return planetPhaseAngleN(7, jd, n)
|
||||
}
|
||||
|
||||
// NeptuneIlluminatedFraction 海王星被照亮比例 / illuminated fraction of Neptune.
|
||||
func NeptuneIlluminatedFraction(jd float64) float64 {
|
||||
return NeptuneIlluminatedFractionN(jd, -1)
|
||||
}
|
||||
|
||||
// NeptuneIlluminatedFractionN 海王星被照亮比例(截断版) / truncated illuminated fraction of Neptune.
|
||||
func NeptuneIlluminatedFractionN(jd float64, n int) float64 {
|
||||
return planetIlluminatedFractionN(7, jd, n)
|
||||
}
|
||||
|
||||
// NeptuneBrightLimbPositionAngle 海王星亮面中心位置角 / position angle of Neptune bright limb.
|
||||
func NeptuneBrightLimbPositionAngle(jd float64) float64 {
|
||||
return NeptuneBrightLimbPositionAngleN(jd, -1)
|
||||
}
|
||||
|
||||
// NeptuneBrightLimbPositionAngleN 海王星亮面中心位置角(截断版) / truncated position angle of Neptune bright limb.
|
||||
func NeptuneBrightLimbPositionAngleN(jd float64, n int) float64 {
|
||||
return planetBrightLimbPositionAngleN(jd, n, NeptuneApparentRaDecN)
|
||||
}
|
||||
|
||||
func planetPhaseAngleN(planetIndex int, jd float64, n int) float64 {
|
||||
return ArcCos(planetPhaseCosineN(planetIndex, jd, n))
|
||||
}
|
||||
|
||||
func planetIlluminatedFractionN(planetIndex int, jd float64, n int) float64 {
|
||||
return (1 + planetPhaseCosineN(planetIndex, jd, n)) / 2
|
||||
}
|
||||
|
||||
func planetPhaseCosineN(planetIndex int, jd float64, n int) float64 {
|
||||
planetSunDistance := planet.WherePlanetN(planetIndex, 2, jd, n)
|
||||
planetEarthDistance := planetEarthAwayN(planetIndex, jd, n)
|
||||
earthSunDistance := EarthAwayN(jd, n)
|
||||
cosine := (planetSunDistance*planetSunDistance + planetEarthDistance*planetEarthDistance - earthSunDistance*earthSunDistance) / (2 * planetSunDistance * planetEarthDistance)
|
||||
return clampUnit(cosine)
|
||||
}
|
||||
|
||||
func planetBrightLimbPositionAngleN(jd float64, n int, apparentRaDec planetRaDecNFunc) float64 {
|
||||
sunRa, sunDec := HSunApparentRaDecN(jd, n)
|
||||
planetRa, planetDec := apparentRaDec(jd, n)
|
||||
y := Cos(sunDec) * Sin(sunRa-planetRa)
|
||||
x := Sin(sunDec)*Cos(planetDec) - Cos(sunDec)*Sin(planetDec)*Cos(sunRa-planetRa)
|
||||
return ArcTan2(y, x)
|
||||
}
|
||||
|
||||
func clampUnit(value float64) float64 {
|
||||
if value > 1 {
|
||||
return 1
|
||||
}
|
||||
if value < -1 {
|
||||
return -1
|
||||
}
|
||||
return value
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user