2bf8478639
- 新增日月食中心带、偏食带、阴影足迹、等时线、食分线及升落边界计算,支持极区与混合食拓扑 - 新增日食单时刻阴影求解器、站心状态查询、批量采样和 ΔT 覆盖接口 - 重构恒星与行星月掩路径,补充有限盘面接触、站心修正、掩带宽度、极区投影及升落边界 - 扩展 SVG 与 GeoJSON 输出,支持详细面板、全球/极区/地球投影、边界闭合、时间标记和拓扑签名 - 扩展日月食候选搜索、局地搜索、沙罗序列预计算与范围外推,补充系列锚点和成员一致性校验 - 补齐古历纪年、儒略历独有闰日、多公历候选、历法改革跨日及精确日期运算接口 - 优化 ΔT、章动、恒星时、月球地平线、事件根搜索和本地星历缓存,降低重复计算开销并提升边界稳定
702 lines
39 KiB
Go
702 lines
39 KiB
Go
package basic
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"math"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
// ErrInvalidOccultationInput 表示月掩输入契约无效。
|
|
// ErrInvalidOccultationInput reports invalid lunar-occultation contract input.
|
|
var ErrInvalidOccultationInput = errors.New("invalid lunar occultation input")
|
|
|
|
// ErrOccultationPathSamplingLimit 表示请求的时间步长、中心线间距或有限盘面采样超过确定性的工作量或输出预算。
|
|
// ErrOccultationPathSamplingLimit reports that the requested time step, center-line spacing, or aggregate finite-disk sampling would exceed the implementation's deterministic work or output budget.
|
|
var ErrOccultationPathSamplingLimit = errors.New("lunar occultation path sampling limit exceeded")
|
|
|
|
const (
|
|
occultationSearchMinimumStep = 250 * time.Millisecond
|
|
occultationPathMinimumStep = time.Second
|
|
occultationPathMinimumTargetSpacingKM = 1.0
|
|
occultationEventSelectionTolerance = 10 * time.Millisecond
|
|
occultationEventSelectionToleranceDays = float64(occultationEventSelectionTolerance) / float64(24*time.Hour)
|
|
)
|
|
|
|
// CoordinateFrame 标识恒星输入坐标使用的赤道坐标系。
|
|
// CoordinateFrame identifies the equatorial coordinate frame used by an input stellar coordinate.
|
|
type CoordinateFrame string
|
|
|
|
const (
|
|
// CoordinateFrameICRS 表示 ICRS 星表坐标系。
|
|
// CoordinateFrameICRS is the ICRS catalog frame.
|
|
CoordinateFrameICRS CoordinateFrame = "icrs"
|
|
// CoordinateFrameJ2000 表示 J2000 平均赤道坐标系。
|
|
// CoordinateFrameJ2000 is the mean equatorial J2000 frame.
|
|
CoordinateFrameJ2000 CoordinateFrame = "j2000"
|
|
// CoordinateFrameApparentOfDate 表示历元时刻的视赤道坐标系。
|
|
// CoordinateFrameApparentOfDate is the apparent equatorial frame of date.
|
|
CoordinateFrameApparentOfDate CoordinateFrame = "apparent_of_date"
|
|
)
|
|
|
|
// OccultationType 标识月掩结果的几何类型。
|
|
// OccultationType identifies the result geometry.
|
|
type OccultationType string
|
|
|
|
const (
|
|
// OccultationTotal 表示掩甚时目标盘面被完全覆盖。
|
|
// OccultationTotal means the target disk is fully covered at greatest occultation.
|
|
OccultationTotal OccultationType = "total"
|
|
// OccultationPartial 表示掩甚时有限目标盘面只有部分被覆盖。
|
|
// OccultationPartial means only part of a finite target disk is covered at greatest occultation.
|
|
OccultationPartial OccultationType = "partial"
|
|
// OccultationGrazing 表示两边缘相切,且没有正持续时间的重叠。
|
|
// OccultationGrazing means the limbs are tangent without a positive-duration overlap.
|
|
OccultationGrazing OccultationType = "grazing"
|
|
)
|
|
|
|
// OccultationPlanet 标识有限盘面的行星目标。
|
|
// OccultationPlanet identifies a finite-disk planetary target.
|
|
type OccultationPlanet string
|
|
|
|
const (
|
|
// OccultationMercury 表示水星有限盘面目标。
|
|
// OccultationMercury identifies Mercury as the finite-disk target.
|
|
OccultationMercury OccultationPlanet = "mercury"
|
|
// OccultationVenus 表示金星有限盘面目标。
|
|
// OccultationVenus identifies Venus as the finite-disk target.
|
|
OccultationVenus OccultationPlanet = "venus"
|
|
// OccultationMars 表示火星有限盘面目标。
|
|
// OccultationMars identifies Mars as the finite-disk target.
|
|
OccultationMars OccultationPlanet = "mars"
|
|
// OccultationJupiter 表示木星有限盘面目标。
|
|
// OccultationJupiter identifies Jupiter as the finite-disk target.
|
|
OccultationJupiter OccultationPlanet = "jupiter"
|
|
// OccultationSaturn 表示土星有限盘面目标。
|
|
// OccultationSaturn identifies Saturn as the finite-disk target.
|
|
OccultationSaturn OccultationPlanet = "saturn"
|
|
// OccultationUranus 表示天王星有限盘面目标。
|
|
// OccultationUranus identifies Uranus as the finite-disk target.
|
|
OccultationUranus OccultationPlanet = "uranus"
|
|
// OccultationNeptune 表示海王星有限盘面目标。
|
|
// OccultationNeptune identifies Neptune as the finite-disk target.
|
|
OccultationNeptune OccultationPlanet = "neptune"
|
|
)
|
|
|
|
// String 返回结果标识中使用的英文目标名称。
|
|
// String returns the English target name used in result identifiers.
|
|
func (p OccultationPlanet) String() string {
|
|
switch p {
|
|
case OccultationMercury:
|
|
return "Mercury"
|
|
case OccultationVenus:
|
|
return "Venus"
|
|
case OccultationMars:
|
|
return "Mars"
|
|
case OccultationJupiter:
|
|
return "Jupiter"
|
|
case OccultationSaturn:
|
|
return "Saturn"
|
|
case OccultationUranus:
|
|
return "Uranus"
|
|
case OccultationNeptune:
|
|
return "Neptune"
|
|
default:
|
|
return ""
|
|
}
|
|
}
|
|
|
|
// Validate 检查行星目标是否受支持。
|
|
// Validate checks whether the planetary target is supported.
|
|
func (p OccultationPlanet) Validate() error {
|
|
if p.String() == "" {
|
|
return fmt.Errorf("%w: unsupported occultation planet %q", ErrInvalidOccultationInput, p)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func validateOccultationTimeRange(start, end time.Time) error {
|
|
if start.IsZero() || end.IsZero() {
|
|
return fmt.Errorf("%w: start and end are required", ErrInvalidOccultationInput)
|
|
}
|
|
if !end.After(start) {
|
|
return fmt.Errorf("%w: end must be after start", ErrInvalidOccultationInput)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func occultationTimeInSelectionWindow(value, start, end time.Time) bool {
|
|
return value.Sub(start) >= -occultationEventSelectionTolerance &&
|
|
value.Sub(end) <= occultationEventSelectionTolerance
|
|
}
|
|
|
|
// Observer 描述站心观测地点。
|
|
// Observer describes the topocentric observing site.
|
|
type Observer struct {
|
|
// Longitude 是经度,东经为正,单位为度。
|
|
// Longitude is east-positive, in degrees.
|
|
Longitude float64
|
|
// Latitude 是纬度,北纬为正,单位为度。
|
|
// Latitude is north-positive, in degrees.
|
|
Latitude float64
|
|
// Height 是观测者相对平均海平面的高度,单位为米。
|
|
// Height is the observer elevation above mean sea level, in meters.
|
|
Height float64
|
|
}
|
|
|
|
// moonTopocentricSemidiameterN 返回指定地点看到的月球角半径。
|
|
// 通用 MoonSemidiameterN 使用地心距离;月掩接触使用同一站心视差修正后的观测者到月球距离。
|
|
// moonTopocentricSemidiameterN returns the lunar angular radius as seen from the supplied site.
|
|
// The usual MoonSemidiameterN uses geocentric distance; occultation contacts use the observer-to-Moon distance after the same topocentric parallax correction as the direction.
|
|
func moonTopocentricSemidiameterN(tt float64, observer Observer, n int) float64 {
|
|
moonRA, moonDec := HMoonGeocentricApparentRaDecN(tt, n)
|
|
moonDistanceKM := HMoonAwayN(tt, n)
|
|
if !finite(moonRA) || !finite(moonDec) || !finite(moonDistanceKM) || moonDistanceKM <= 0 {
|
|
return math.NaN()
|
|
}
|
|
distanceKM := topocentricDistanceKM(moonRA, moonDec, moonDistanceKM, observer, TD2UT(tt, false))
|
|
if !finite(distanceKM) || distanceKM <= 0 {
|
|
return math.NaN()
|
|
}
|
|
return angularSemidiameterArcsec(moonEquatorialRadiusKM, distanceKM)
|
|
}
|
|
|
|
// topocentricDistanceKM 使用与 TopocentricRaDec 相同的 WGS-84 风格站点因子计算观测者到目标的距离。
|
|
// 目标采用视赤道坐标,恒星时与 TopocentricRaDec 一样基于 UTC/UT。
|
|
// topocentricDistanceKM uses the same WGS-84-style site factors as TopocentricRaDec.
|
|
// The target uses apparent equatorial coordinates, and the sidereal angle is based on UTC/UT.
|
|
func topocentricDistanceKM(ra, dec, distanceKM float64, observer Observer, ut float64) float64 {
|
|
return topocentricDistanceKMWithSidereal(ra, dec, distanceKM, observer, ApparentSiderealTime(ut)*15)
|
|
}
|
|
|
|
func topocentricDistanceKMWithSidereal(
|
|
ra, dec, distanceKM float64,
|
|
observer Observer,
|
|
siderealDegrees float64,
|
|
) float64 {
|
|
const earthEquatorialRadius = 6378.14
|
|
const astronomicalUnitKM = angularDiameterAstronomicalUnitKM
|
|
|
|
distanceAU := distanceKM / astronomicalUnitKM
|
|
if distanceAU <= 0 {
|
|
return math.NaN()
|
|
}
|
|
raRad := ra * math.Pi / 180
|
|
decRad := dec * math.Pi / 180
|
|
moon := [3]float64{
|
|
distanceAU * math.Cos(decRad) * math.Cos(raRad),
|
|
distanceAU * math.Cos(decRad) * math.Sin(raRad),
|
|
distanceAU * math.Sin(decRad),
|
|
}
|
|
theta := (siderealDegrees + observer.Longitude) * math.Pi / 180
|
|
observerAU := earthEquatorialRadius / astronomicalUnitKM
|
|
observerVector := [3]float64{
|
|
observerAU * pcosi(observer.Latitude, observer.Height) * math.Cos(theta),
|
|
observerAU * pcosi(observer.Latitude, observer.Height) * math.Sin(theta),
|
|
observerAU * psini(observer.Latitude, observer.Height),
|
|
}
|
|
dx := moon[0] - observerVector[0]
|
|
dy := moon[1] - observerVector[1]
|
|
dz := moon[2] - observerVector[2]
|
|
return math.Sqrt(dx*dx+dy*dy+dz*dz) * astronomicalUnitKM
|
|
}
|
|
|
|
// Validate 检查站心计算所需的地理范围。
|
|
// Validate checks the geographic bounds needed by topocentric calculations.
|
|
func (o Observer) Validate() error {
|
|
if !finite(o.Longitude) || !finite(o.Latitude) || !finite(o.Height) {
|
|
return fmt.Errorf("%w: observer values must be finite", ErrInvalidOccultationInput)
|
|
}
|
|
if o.Longitude < -180 || o.Longitude > 180 {
|
|
return fmt.Errorf("%w: observer longitude must be in [-180, 180]", ErrInvalidOccultationInput)
|
|
}
|
|
if o.Latitude < -90 || o.Latitude > 90 {
|
|
return fmt.Errorf("%w: observer latitude must be in [-90, 90]", ErrInvalidOccultationInput)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// StarCoordinate 是调用者为恒星提供的星表坐标或视位置坐标。
|
|
// RA 和 Dec 的单位为度;ProperMotionRACosDecMasPerYear 使用星表常见的 dRA*cos(Dec) 约定,单位为毫角秒/年。
|
|
// StarCoordinate is a catalog or apparent coordinate supplied for a star.
|
|
// RA and Dec are degrees; ProperMotionRACosDecMasPerYear uses the usual catalog convention of dRA*cos(Dec), in milliarcseconds per year.
|
|
type StarCoordinate struct {
|
|
ID string
|
|
|
|
RA float64
|
|
Dec float64
|
|
Epoch time.Time
|
|
Frame CoordinateFrame
|
|
|
|
ProperMotionRACosDecMasPerYear float64
|
|
ProperMotionDecMasPerYear float64
|
|
ParallaxMas float64
|
|
}
|
|
|
|
// Validate 在构造目标前检查恒星坐标契约。
|
|
// Validate checks the coordinate contract before a target is constructed.
|
|
func (s StarCoordinate) Validate() error {
|
|
if !finite(s.RA) || s.RA < 0 || s.RA >= 360 {
|
|
return fmt.Errorf("%w: star RA must be in [0, 360)", ErrInvalidOccultationInput)
|
|
}
|
|
if !finite(s.Dec) || s.Dec < -90 || s.Dec > 90 {
|
|
return fmt.Errorf("%w: star Dec must be in [-90, 90]", ErrInvalidOccultationInput)
|
|
}
|
|
if s.Epoch.IsZero() {
|
|
return fmt.Errorf("%w: star epoch is required", ErrInvalidOccultationInput)
|
|
}
|
|
if !validCoordinateFrame(s.Frame) {
|
|
return fmt.Errorf("%w: unsupported star coordinate frame %q", ErrInvalidOccultationInput, s.Frame)
|
|
}
|
|
if !finite(s.ProperMotionRACosDecMasPerYear) || !finite(s.ProperMotionDecMasPerYear) {
|
|
return fmt.Errorf("%w: star proper motion must be finite", ErrInvalidOccultationInput)
|
|
}
|
|
if !finite(s.ParallaxMas) || s.ParallaxMas < 0 {
|
|
return fmt.Errorf("%w: star parallax must be finite and non-negative", ErrInvalidOccultationInput)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// StarCoordinateFromStarData 将一条内嵌星表记录转换为月掩搜索使用的 J2000 坐标契约。
|
|
// 星表自行从角秒/年转换为毫角秒/年;正的秒差距距离转换为毫角秒年视差。本函数只转换传入值,不会加载星表。
|
|
// StarCoordinateFromStarData converts one embedded-catalog entry into the J2000 coordinate contract used by lunar-occultation searches.
|
|
// The catalog's proper motions are converted from arcseconds/year to milliarcseconds/year; a positive parsec distance is converted to annual parallax in milliarcseconds.
|
|
// This function only converts the supplied value and never loads the catalog.
|
|
func StarCoordinateFromStarData(star StarData) (StarCoordinate, error) {
|
|
if star.HR == 0 {
|
|
return StarCoordinate{}, fmt.Errorf("%w: star catalog HR number is required", ErrInvalidOccultationInput)
|
|
}
|
|
if !finite(star.Pc) || star.Pc < 0 {
|
|
return StarCoordinate{}, fmt.Errorf("%w: star distance must be finite and non-negative", ErrInvalidOccultationInput)
|
|
}
|
|
|
|
parallaxMas := 0.0
|
|
if star.Pc > 0 {
|
|
parallaxMas = 1000 / star.Pc
|
|
}
|
|
coordinate := StarCoordinate{
|
|
ID: starCoordinateIDFromStarData(star),
|
|
RA: star.Ra,
|
|
Dec: star.Dec,
|
|
Epoch: time.Date(2000, time.January, 1, 12, 0, 0, 0, time.UTC),
|
|
Frame: CoordinateFrameJ2000,
|
|
ProperMotionRACosDecMasPerYear: star.PmRA * 1000,
|
|
ProperMotionDecMasPerYear: star.PmDec * 1000,
|
|
ParallaxMas: parallaxMas,
|
|
}
|
|
if err := coordinate.Validate(); err != nil {
|
|
return StarCoordinate{}, fmt.Errorf("convert star catalog coordinate: %w", err)
|
|
}
|
|
return coordinate, nil
|
|
}
|
|
|
|
func starCoordinateIDFromStarData(star StarData) string {
|
|
for _, name := range []string{star.ChineseName, star.ChineseAlias, star.CommonName, star.Name} {
|
|
if name = strings.TrimSpace(name); name != "" {
|
|
return name
|
|
}
|
|
}
|
|
if star.HR > 0 {
|
|
return fmt.Sprintf("HR %d", star.HR)
|
|
}
|
|
return ""
|
|
}
|
|
|
|
// OccultationSearchOptions 控制固定目标和行星月掩搜索。
|
|
// 零值使用实现默认值;MaxEvents == 0 表示不限制数量。
|
|
// OccultationSearchOptions controls fixed-target and planetary occultation searches.
|
|
// Zero values select implementation defaults; MaxEvents == 0 means unlimited.
|
|
type OccultationSearchOptions struct {
|
|
// MaxStep 是粗略搜索的最大步长;小于 250ms 的正值会被拒绝,因为在支持的时间范围内无法可靠地用儒略日浮点数推进。
|
|
// MaxStep is the maximum coarse-search step. Positive values below 250 ms are rejected because they cannot be advanced reliably in Julian-day floating-point arithmetic over the supported time span.
|
|
MaxStep time.Duration
|
|
// SafetyMarginArcsec 是加入粗略候选和黄纬预筛的安全余量,单位为角秒。
|
|
// SafetyMarginArcsec is added to coarse candidate and latitude prefilters.
|
|
SafetyMarginArcsec float64
|
|
// MaxEvents 为正时限制返回事件数量。
|
|
// MaxEvents limits the number of returned events when positive.
|
|
MaxEvents int
|
|
}
|
|
|
|
// OccultationPathAlgorithm 选择全球月掩路径的星历求解分支。
|
|
// OccultationPathAlgorithm selects the ephemeris branch for global occultation paths.
|
|
type OccultationPathAlgorithm string
|
|
|
|
const (
|
|
// OccultationPathAlgorithmOptimized 使用经抽检的密集星历插值,保留站心方程与连续包络。
|
|
// OccultationPathAlgorithmOptimized uses checked dense ephemeris interpolation with the same station equations and continuous envelopes.
|
|
OccultationPathAlgorithmOptimized OccultationPathAlgorithm = "optimized"
|
|
// OccultationPathAlgorithmExact 保留原分支:插值预测候选,最终求解使用全项星历。
|
|
// OccultationPathAlgorithmExact retains the original branch: interpolated candidates and full-term ephemerides for final solving.
|
|
OccultationPathAlgorithmExact OccultationPathAlgorithm = "exact"
|
|
)
|
|
|
|
// OccultationPathOptions 控制全球月掩路径采样。
|
|
//
|
|
// Step 为路径采样的基础时间步长,正值至少为 1 秒。TargetSpacingKM 要求相邻中心线点超过目标地面距离时进行自适应加密。
|
|
// 正的 TargetSpacingKM 至少为 1 km;超过中心线或有限盘面路径工作量预算时返回 ErrOccultationPathSamplingLimit,不会静默降低请求分辨率。恒星和行星瞬时足迹使用 1 分钟目标步长和独立样本上限。
|
|
// OccultationPathOptions controls global occultation-path sampling.
|
|
// Step is the base time step used for path samples; positive values must be at least one second. TargetSpacingKM requests adaptive refinement when adjacent center-line points exceed the requested ground distance.
|
|
// Positive TargetSpacingKM values must be at least 1 km. Requests that exceed the center-line or aggregate finite-disk work budgets return ErrOccultationPathSamplingLimit instead of silently reducing resolution. Stellar and planetary instantaneous footprints use a one-minute target step and a separate sample cap.
|
|
type OccultationPathOptions struct {
|
|
// Algorithm 的零值等同 optimized;exact 可选择原有精确分支。此选项不影响独立的单时刻月影与事件查询接口。
|
|
// Algorithm defaults to optimized; exact selects the original branch. Independent instant-footprint and event-search APIs are unaffected.
|
|
Algorithm OccultationPathAlgorithm
|
|
Step time.Duration
|
|
TargetSpacingKM float64
|
|
// RiseSetStep 独立控制六类升落阶段线的采样步长;零值使用 5 分钟。
|
|
// RiseSetStep controls horizon-curve sampling independently from the path step; zero uses five minutes.
|
|
RiseSetStep time.Duration
|
|
// DisableRiseSet 在不需要时跳过六类初掩、掩甚、终掩月升/月落线。
|
|
// DisableRiseSet skips the six local phase/horizon curves when they are not needed.
|
|
DisableRiseSet bool
|
|
// DisableFootprints 跳过密集的独立瞬时可见区,改用稀疏支撑样本构造紧凑掩带;中心线、边界和升落阶段线仍保留。
|
|
// DisableFootprints skips dense standalone instantaneous visible regions and uses sparse support samples to construct compact bands; the center line, limits, and rise/set curves remain available.
|
|
DisableFootprints bool
|
|
// IncludeFootprintTimeline 在保留紧凑静态掩带的同时,增加独立采样的瞬时足迹时间线。
|
|
// IncludeFootprintTimeline adds independently sampled instantaneous footprints while retaining the compact static band.
|
|
IncludeFootprintTimeline bool
|
|
// FootprintTimelineStep 控制瞬时足迹时间线的采样间隔;启用时零值使用 5 分钟。
|
|
// FootprintTimelineStep controls the instantaneous timeline interval; zero uses five minutes when the timeline is enabled.
|
|
FootprintTimelineStep time.Duration
|
|
// GreatestTimeValues 是要计算的地方掩甚时刻等值线的时刻取值(力学时儒略日,最多 64 条,超出按时间截断);空值时改用 GreatestTimeStep。
|
|
// 只有确实存在该时刻掩甚轨迹的取值才会出现在结果里,所以返回条数可能少于请求条数。
|
|
// 每条等时线用固定时刻的残差零集延拓,成本正比于曲线长度而不是可见域面积。
|
|
// GreatestTimeValues requests local greatest-occultation time isolines as TT Julian ephemeris days;
|
|
// when empty, GreatestTimeStep is used instead. Each isochrone is continued along the zero set of a
|
|
// fixed-instant residual so the cost scales with curve length rather than with the visible area.
|
|
GreatestTimeValues []float64
|
|
// GreatestTimeStep 是等时线间隔;仅在 GreatestTimeValues 为空时生效,非正值不计算等时线。
|
|
// GreatestTimeStep is the isochrone interval; it applies only when GreatestTimeValues is empty,
|
|
// and non-positive values disable the isolines.
|
|
GreatestTimeStep time.Duration
|
|
}
|
|
|
|
// Validate 检查全球路径采样选项。
|
|
// Validate checks global path sampling options.
|
|
func (o OccultationPathOptions) Validate() error {
|
|
switch o.Algorithm {
|
|
case "", OccultationPathAlgorithmOptimized, OccultationPathAlgorithmExact:
|
|
default:
|
|
return fmt.Errorf("%w: unsupported path algorithm %q", ErrInvalidOccultationInput, o.Algorithm)
|
|
}
|
|
if o.Step < 0 {
|
|
return fmt.Errorf("%w: path step cannot be negative", ErrInvalidOccultationInput)
|
|
}
|
|
if o.Step > 0 && o.Step < occultationPathMinimumStep {
|
|
return fmt.Errorf("%w: path step must be zero or at least %s", ErrInvalidOccultationInput, occultationPathMinimumStep)
|
|
}
|
|
if !finite(o.TargetSpacingKM) || o.TargetSpacingKM < 0 {
|
|
return fmt.Errorf("%w: path target spacing must be finite and non-negative", ErrInvalidOccultationInput)
|
|
}
|
|
if o.TargetSpacingKM > 0 && o.TargetSpacingKM < occultationPathMinimumTargetSpacingKM {
|
|
return fmt.Errorf("%w: path target spacing must be zero or at least %.0f km", ErrInvalidOccultationInput, occultationPathMinimumTargetSpacingKM)
|
|
}
|
|
if o.RiseSetStep < 0 {
|
|
return fmt.Errorf("%w: rise/set step cannot be negative", ErrInvalidOccultationInput)
|
|
}
|
|
if o.RiseSetStep > 0 && o.RiseSetStep < occultationPathMinimumStep {
|
|
return fmt.Errorf("%w: rise/set step must be zero or at least %s", ErrInvalidOccultationInput, occultationPathMinimumStep)
|
|
}
|
|
if o.FootprintTimelineStep < 0 {
|
|
return fmt.Errorf("%w: footprint step cannot be negative", ErrInvalidOccultationInput)
|
|
}
|
|
if o.FootprintTimelineStep > 0 && o.FootprintTimelineStep < occultationPathMinimumStep {
|
|
return fmt.Errorf("%w: footprint step must be zero or at least %s", ErrInvalidOccultationInput, occultationPathMinimumStep)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Validate 检查选项值,但不选择算法专用默认值。
|
|
// Validate checks option values without selecting algorithm-specific defaults.
|
|
func (o OccultationSearchOptions) Validate() error {
|
|
if o.MaxStep < 0 {
|
|
return fmt.Errorf("%w: search max step cannot be negative", ErrInvalidOccultationInput)
|
|
}
|
|
if o.MaxStep > 0 && o.MaxStep < occultationSearchMinimumStep {
|
|
return fmt.Errorf("%w: search max step must be zero or at least %s", ErrInvalidOccultationInput, occultationSearchMinimumStep)
|
|
}
|
|
if !finite(o.SafetyMarginArcsec) || o.SafetyMarginArcsec < 0 {
|
|
return fmt.Errorf("%w: search safety margin must be finite and non-negative", ErrInvalidOccultationInput)
|
|
}
|
|
if o.MaxEvents < 0 {
|
|
return fmt.Errorf("%w: search max events cannot be negative", ErrInvalidOccultationInput)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// StarOccultationInfo 描述点光源恒星月掩;掩始和掩终是月缘交点。
|
|
// StarOccultationInfo describes a point-source stellar occultation. The immersion and emersion times are the Moon-limb crossings.
|
|
type StarOccultationInfo struct {
|
|
TargetID string
|
|
Observer Observer
|
|
Type OccultationType
|
|
|
|
Immersion time.Time
|
|
Greatest time.Time
|
|
Emersion time.Time
|
|
// ContactsComplete 表示两个月缘接触时刻均已求解。
|
|
// ContactsComplete is true when both lunar-limb contacts were solved.
|
|
ContactsComplete bool
|
|
|
|
MinimumSeparationArcsec float64
|
|
PositionAngleDeg float64
|
|
MoonSemidiameterArcsec float64
|
|
|
|
MoonAltitudeAtGreatest float64
|
|
MoonAzimuthAtGreatest float64
|
|
VisibleAtGreatest bool
|
|
}
|
|
|
|
// PlanetOccultationInfo 描述有限盘面行星月掩。
|
|
// ExternalImmersion 和 ExternalEmersion 分别是 C1 和 C4;全掩事件的 InternalImmersion 和 InternalEmersion 分别是 C2 和 C3,偏掩和掠掩时为零。
|
|
// ContactsComplete 表示报告几何适用的所有接触均已求解;目标按赤道半径建模为圆盘,环、大气延伸和扁率不在模型内。
|
|
// PlanetOccultationInfo describes a finite-disk planetary occultation.
|
|
// ExternalImmersion and ExternalEmersion are C1 and C4. For a total event, InternalImmersion and InternalEmersion are C2 and C3; they are zero for partial and grazing events.
|
|
// ContactsComplete means every contact applicable to the reported geometry was solved. The target is modeled as a circular disk using its equatorial body radius; rings, atmospheric extensions, and oblateness are outside this contact model.
|
|
type PlanetOccultationInfo struct {
|
|
Planet OccultationPlanet
|
|
TargetID string
|
|
Observer Observer
|
|
Type OccultationType
|
|
|
|
ExternalImmersion time.Time
|
|
InternalImmersion time.Time
|
|
Greatest time.Time
|
|
InternalEmersion time.Time
|
|
ExternalEmersion time.Time
|
|
|
|
HasInternalContacts bool
|
|
ContactsComplete bool
|
|
|
|
MinimumSeparationArcsec float64
|
|
PositionAngleDeg float64
|
|
MoonSemidiameterArcsec float64
|
|
PlanetSemidiameterArcsec float64
|
|
|
|
MoonAltitudeAtGreatest float64
|
|
MoonAzimuthAtGreatest float64
|
|
VisibleAtGreatest bool
|
|
}
|
|
|
|
// OccultationPathPoint 是全球月掩路径上的一个地理采样点。
|
|
// Start 和 End 描述月缘外接触掩带。
|
|
// WidthKM 是中心线采样处的地面横向宽度:接触锥可见弧上地面横向偏移的极差;投影折叠的退化事件改用同刻两条横切母线与椭球交点间的弦长。
|
|
// LimitSeparationKM 只对南北限采样点(含全掩限)非零,是同一时刻对侧限线的地面间距;它与 WidthKM 构造不同,不要互相换算。
|
|
// 基础采样直接求解,自适应插入点使用宽度插值并进行五米采样误差检查。
|
|
// OccultationPathPoint is a geographic sample of a global lunar-occultation path.
|
|
// Start and End describe the outer lunar-limb footprint.
|
|
// WidthKM is the ground cross-track width at a center-line sample: the spread of ground cross-track offsets over the visible contact-cone arc, falling back to the same-instant chord between the two cross-track generators when the projection folds.
|
|
// LimitSeparationKM is non-zero only on northern/southern limit samples (total limits included) and is the same-instant ground distance to the opposite limit; it is constructed differently from WidthKM and must not be converted into it.
|
|
// Base samples are solved directly; adaptive samples use width interpolation and five-meter error checks.
|
|
type OccultationPathPoint struct {
|
|
Time time.Time
|
|
Longitude float64
|
|
Latitude float64
|
|
MoonAltitude float64
|
|
WidthKM float64
|
|
LimitSeparationKM float64
|
|
}
|
|
|
|
// OccultationGreatestTimeContour 是一个固定地方掩甚时刻的等值线支路集合。
|
|
// OccultationGreatestTimeContour contains the continuous branches of one fixed local greatest-occultation time.
|
|
type OccultationGreatestTimeContour struct {
|
|
// JDE 是该等值线表示的力学时儒略日,也就是各支路上地方掩甚发生的时刻。
|
|
// JDE is the TT Julian ephemeris day represented by this contour, the local greatest-occultation instant along every branch.
|
|
JDE float64
|
|
// Time 是 JDE 对应的时刻;按步长请求时它是原始对齐时刻,避免 JDE 往返把整分取值截断成前一分钟。
|
|
// Time is the instant matching JDE; for step-derived levels it is the original aligned instant,
|
|
// so a JDE round trip cannot truncate a whole-minute level into the previous minute.
|
|
Time time.Time
|
|
// Segments 是该时刻的连续等时线支路;一条支路两端止于月平线(几何地平,无蒙气差修正)或掩可见域边界,
|
|
// 纬度 ±88° 以上不再延拓,同一时刻可能有多条不相连的支路。
|
|
// Segments are continuous isochrone branches; each branch ends at the lunar horizon or the occultation-visibility boundary.
|
|
Segments [][]OccultationPathPoint
|
|
}
|
|
|
|
// StarOccultationPath 包含点光源恒星月掩的全球掩带。
|
|
// 中心线是月心与恒星对齐的轨迹;NorthernLimit 和 SouthernLimit 是月缘外接触锥的切点轨迹(掩星在该线上恰好退化为擦边),不是中心线的等距横向平移。
|
|
// StarOccultationPath contains the global footprint of a point-source stellar occultation.
|
|
// The center line is the locus where the lunar center aligns with the star; NorthernLimit and SouthernLimit are the tangency tracks of the outer-contact cone, where the occultation degenerates to a graze, so they are not a constant offset of the center line.
|
|
type StarOccultationPath struct {
|
|
TargetID string
|
|
|
|
Start OccultationPathPoint
|
|
Greatest OccultationPathPoint
|
|
End OccultationPathPoint
|
|
// Complete 表示 Start 和 End 是全球月缘外接触点,而不是查询窗口裁剪点。
|
|
// Complete is true when Start and End are the global outer-limb contacts rather than query-window clipping points.
|
|
Complete bool
|
|
|
|
CenterLine []OccultationPathPoint
|
|
NorthernLimit []OccultationPathPoint
|
|
SouthernLimit []OccultationPathPoint
|
|
// GreatestLimitSeparationKM 是掩甚处南北限的地面间距(取与掩甚时刻最近的限线采样对),与 Greatest.WidthKM 口径不同,不要互相换算。
|
|
// GreatestLimitSeparationKM is the ground separation of the two limits at greatest, taken from the limit sample pair nearest to the greatest instant; it uses a different construction from Greatest.WidthKM and must not be converted into it.
|
|
GreatestLimitSeparationKM float64
|
|
// BandContours 是构造静态可见掩带的连续接触包络;点光源恒星等价于月缘外接触边界。
|
|
// BandContours are the continuous contact envelopes used to construct the static visible band; for point-source stars they are the lunar-limb outer-contact boundaries.
|
|
BandContours [][]OccultationPathPoint
|
|
// VisibilityContours 是掩星有效期间月球可见性的连续时间外包络。
|
|
// VisibilityContours are the continuous temporal envelopes of lunar visibility while the occultation is active.
|
|
VisibilityContours [][]OccultationPathPoint
|
|
// Footprints 是时刻采样的可见点源掩区,其扫掠构成全球掩带;采样口径与行星外接触足迹一致。
|
|
// Footprints are sampled visible point-source regions whose sweep forms the global band, using the same sampling contract as planetary outer-contact footprints.
|
|
Footprints []OccultationFootprint
|
|
// BandFootprints 是关闭密集瞬时足迹时用于构造紧凑掩带的稀疏可见区样本;展示层应合并它们,不应逐个输出。
|
|
// BandFootprints are sparse visible-region samples used to construct a compact band when dense instantaneous footprints are disabled. Renderers should merge rather than emit them individually.
|
|
BandFootprints []OccultationFootprint
|
|
// RiseSetCurves 是初掩、掩甚和终掩分别发生在月升或月落时的六类边界。
|
|
// RiseSetCurves are the six boundaries where local start, greatest, or end occurs at moonrise or moonset.
|
|
RiseSetCurves []OccultationRiseSetCurve
|
|
// GreatestTimeContours 是按掩甚时刻采样的等时线。
|
|
// GreatestTimeContours are sampled local greatest-occultation time isolines.
|
|
GreatestTimeContours []OccultationGreatestTimeContour
|
|
|
|
Step time.Duration
|
|
TargetSpacingKM float64
|
|
}
|
|
|
|
// OccultationFootprint 是一个时刻的可见接触足迹。
|
|
// Polygons 包含供独立时刻绘制的闭合可见区;Boundaries 保留未经地平线封口的接触锥弧,供稀疏静态样本连续扫掠。
|
|
// Closed 表示 Boundaries 本身是否为闭合交线。
|
|
// OccultationFootprint is one instantaneous visible contact footprint.
|
|
// Polygons contain closed visible regions for rendering one instant. Boundaries retain contact-cone arcs before horizon closure for continuously sweeping sparse static samples.
|
|
// Closed reports whether Boundaries themselves form a closed intersection.
|
|
type OccultationFootprint struct {
|
|
// Time 是该瞬时足迹对应的事件时刻。
|
|
// Time is the event time represented by this instantaneous footprint.
|
|
Time time.Time
|
|
// Polygons 是供独立时刻绘制的闭合可见区域。
|
|
// Polygons are closed visible regions for rendering one instant.
|
|
Polygons [][]OccultationPathPoint
|
|
// InteriorPolygons 是为单时刻绘制保留的闭合可见修复面。静态掩带构建器会将其与连续扫掠的接触边界弧分开,避免数值中心修复扩大长时间地理掩带。
|
|
// InteriorPolygons are closed visible repair faces retained for one-instant
|
|
// rendering. Static band builders keep them separate from the continuously
|
|
// swept contact-boundary arcs so a numerical center repair cannot enlarge a
|
|
// long-lived geographic band.
|
|
InteriorPolygons [][]OccultationPathPoint
|
|
// Boundaries 是尚未经过地平线封口的接触边界弧。
|
|
// Boundaries are contact-boundary arcs before horizon closure.
|
|
Boundaries [][]OccultationPathPoint
|
|
// Closed 表示 Boundaries 是否构成闭合交线。
|
|
// Closed reports whether Boundaries form a closed intersection.
|
|
Closed bool
|
|
}
|
|
|
|
// OccultationRiseSetCurve 是一种局部月掩阶段与月升/月落同时发生的边界。
|
|
// OccultationRiseSetCurve is one boundary where a local occultation phase coincides with moonrise or moonset.
|
|
type OccultationRiseSetCurve struct {
|
|
// Phase 是与月升或月落同时发生的局部月掩阶段。
|
|
// Phase is the local occultation phase coinciding with moonrise or moonset.
|
|
Phase RiseSetPhase
|
|
// Direction 标识月球正在升起还是落下。
|
|
// Direction identifies whether the Moon is rising or setting.
|
|
Direction RiseSetDirection
|
|
// Segments 是反经线和支路跳变安全分段后的边界采样。
|
|
// Segments are boundary samples split safely at the antimeridian and branch changes.
|
|
Segments [][]OccultationPathPoint
|
|
}
|
|
|
|
// PlanetOccultationFootprint 保留有限盘面行星月掩足迹的兼容名称。
|
|
// PlanetOccultationFootprint retains the compatibility name for finite-disk planetary footprints.
|
|
type PlanetOccultationFootprint = OccultationFootprint
|
|
|
|
// PlanetOccultationPath 包含有限盘面行星月掩的全球掩带。
|
|
// NorthernLimit 和 SouthernLimit 是行星盘面任意部分被覆盖的外接触边界。
|
|
// HasTotalBand 为 true 时,NorthernTotalLimit 和 SouthernTotalLimit 是行星圆盘完全被月球覆盖的内接触边界;
|
|
// 环、大气延伸和扁率不在两种接触模型内。
|
|
// PlanetOccultationPath contains the global footprint of a finite-disk planetary occultation.
|
|
// NorthernLimit and SouthernLimit are the outer-contact boundaries where any part of the planet disk is covered.
|
|
// When HasTotalBand is true, NorthernTotalLimit and SouthernTotalLimit are the inner-contact boundaries where the complete circular planet disk is covered by the Moon. Rings, atmospheric extensions, and oblateness are outside both contact models.
|
|
type PlanetOccultationPath struct {
|
|
Planet OccultationPlanet
|
|
TargetID string
|
|
|
|
Start OccultationPathPoint
|
|
Greatest OccultationPathPoint
|
|
End OccultationPathPoint
|
|
// Complete 表示 Start 和 End 是全球外接触点。
|
|
// Complete is true when Start and End are the global outer contacts.
|
|
Complete bool
|
|
|
|
// CenterLine 是影轴与椭球交点的轨迹;非中心事件退化为最接近影轴的椭球点。
|
|
// CenterLine is the track of the shadow-axis/ellipsoid intersection, degenerating to the ellipsoid point nearest the axis for non-central events.
|
|
CenterLine []OccultationPathPoint
|
|
// NorthernLimit 和 SouthernLimit 是外接触锥的切点轨迹:掩星在该线上恰好退化为擦边,不是中心线的等距横向平移。
|
|
// NorthernLimit and SouthernLimit are the tangency tracks of the outer-contact cone, where the occultation degenerates to a graze, so they are not a constant-width offset of the center line.
|
|
NorthernLimit []OccultationPathPoint
|
|
SouthernLimit []OccultationPathPoint
|
|
// GreatestLimitSeparationKM 是掩甚处南北限的地面间距(取与掩甚时刻最近的限线采样对),与 Greatest.WidthKM 口径不同,不要互相换算。
|
|
// GreatestLimitSeparationKM is the ground separation of the two limits at greatest, taken from the limit sample pair nearest to the greatest instant; it uses a different construction from Greatest.WidthKM and must not be converted into it.
|
|
GreatestLimitSeparationKM float64
|
|
// PartialBandContours 是偏掩静态带的连续外接触包络;任意目标盘面重叠时取零。
|
|
// PartialBandContours are the continuous outer-contact envelopes for the static partial band where any target-disk overlap begins.
|
|
PartialBandContours [][]OccultationPathPoint
|
|
// PartialVisibilityContours 是月球可见性在偏掩阶段内的连续时间外包络。
|
|
// PartialVisibilityContours are the continuous temporal envelopes of lunar visibility while partial occultation is active.
|
|
PartialVisibilityContours [][]OccultationPathPoint
|
|
// PartialFootprints 是时刻采样的可见外接触区域,其扫掠构成全球偏掩区域;足迹以 1 分钟为目标步长,超出独立样本上限时会变粗,
|
|
// 每个足迹携带实际采样时刻。
|
|
// PartialFootprints are instantaneous visible outer-contact regions whose sweep forms the global partial-occultation area. Footprints target one-minute intervals and become coarser when their independent sample cap is reached; each footprint carries its actual sample time.
|
|
PartialFootprints []PlanetOccultationFootprint
|
|
// PartialBandFootprints 是关闭密集瞬时足迹时用于构造紧凑偏掩带的稀疏可见区样本。
|
|
// PartialBandFootprints are sparse visible-region samples used to construct the compact partial band when dense instantaneous footprints are disabled.
|
|
PartialBandFootprints []PlanetOccultationFootprint
|
|
// RiseSetCurves 是初掩、掩甚和终掩分别发生在月升或月落时的六类边界。
|
|
// RiseSetCurves are the six boundaries where local start, greatest, or end occurs at moonrise or moonset.
|
|
RiseSetCurves []OccultationRiseSetCurve
|
|
|
|
HasTotalBand bool
|
|
// TotalStart 和 TotalEnd 是全球内接触的起止点。
|
|
// TotalStart and TotalEnd are the first and last global inner contacts.
|
|
TotalStart OccultationPathPoint
|
|
TotalEnd OccultationPathPoint
|
|
// TotalComplete 表示 TotalStart 和 TotalEnd 未被内部搜索范围截断。
|
|
// TotalComplete is true when TotalStart and TotalEnd are not clipped by the internal search span.
|
|
TotalComplete bool
|
|
|
|
// NorthernTotalLimit 和 SouthernTotalLimit 是内接触(全掩)锥的切点轨迹,同刻间距同样记录在 LimitSeparationKM 上。
|
|
// NorthernTotalLimit and SouthernTotalLimit are the tangency tracks of the inner-contact cone; their same-instant separation is recorded in LimitSeparationKM as well.
|
|
NorthernTotalLimit []OccultationPathPoint
|
|
SouthernTotalLimit []OccultationPathPoint
|
|
// TotalBandContours 是全掩静态带的连续内接触包络;仅在目标圆盘完全被月球覆盖时取零。
|
|
// TotalBandContours are the continuous inner-contact envelopes for the static total band where the complete target disk is covered by the Moon.
|
|
TotalBandContours [][]OccultationPathPoint
|
|
// TotalVisibilityContours 是全掩阶段内月球可见性的连续时间外包络。
|
|
// TotalVisibilityContours are the temporal envelopes of lunar visibility while the total-occultation contact condition is active.
|
|
TotalVisibilityContours [][]OccultationPathPoint
|
|
// TotalFootprints 是时刻采样的可见内接触区域,其扫掠构成全球全掩区域;采样使用与 PartialFootprints 相同的 1 分钟目标步长和样本上限。
|
|
// TotalFootprints are instantaneous visible inner-contact regions whose sweep forms the global full-coverage area. Their sampling uses the same one-minute target step and sample cap as PartialFootprints.
|
|
TotalFootprints []PlanetOccultationFootprint
|
|
// TotalBandFootprints 是关闭密集瞬时足迹时用于构造紧凑全掩带的稀疏可见区样本。
|
|
// TotalBandFootprints are sparse visible-region samples used to construct the compact total band when dense instantaneous footprints are disabled.
|
|
TotalBandFootprints []PlanetOccultationFootprint
|
|
// TotalRiseSetCurves 是全掩带使用的内接触月升/月落边界;其相位与 RiseSetCurves 相同,但 contact metric 采用内接触而非外接触。
|
|
// TotalRiseSetCurves are the inner-contact moonrise/moonset boundaries used by the total band; they share the same phase labels as RiseSetCurves but evaluate the inner-contact metric instead of the outer-contact metric.
|
|
TotalRiseSetCurves []OccultationRiseSetCurve
|
|
// GreatestTotalWidthKM 是全球掩甚时的全掩带宽度,与 Greatest.WidthKM 同口径而作用于内接触锥,且恒小于它。
|
|
// GreatestTotalWidthKM is the full-coverage band width at global greatest, using the same construction as Greatest.WidthKM on the inner-contact cone and always below it.
|
|
GreatestTotalWidthKM float64
|
|
// GreatestTimeContours 是按掩甚时刻采样的等时线,判据用外接触锥,与偏掩可见域一致。
|
|
// GreatestTimeContours are sampled local greatest-occultation time isolines, gated by the outer-contact cone so they match the partial-occultation visibility area.
|
|
GreatestTimeContours []OccultationGreatestTimeContour
|
|
|
|
Step time.Duration
|
|
TargetSpacingKM float64
|
|
}
|
|
|
|
func finite(value float64) bool {
|
|
return !math.IsNaN(value) && !math.IsInf(value, 0)
|
|
}
|
|
|
|
func validCoordinateFrame(frame CoordinateFrame) bool {
|
|
switch frame {
|
|
case CoordinateFrameICRS, CoordinateFrameJ2000, CoordinateFrameApparentOfDate:
|
|
return true
|
|
default:
|
|
return false
|
|
}
|
|
}
|