Files
astro/geojson/solar_shadow_instant.go
T
b612 2bf8478639 feat: 完善日月食与月掩几何链路并扩展历法接口
- 新增日月食中心带、偏食带、阴影足迹、等时线、食分线及升落边界计算,支持极区与混合食拓扑
- 新增日食单时刻阴影求解器、站心状态查询、批量采样和 ΔT 覆盖接口
- 重构恒星与行星月掩路径,补充有限盘面接触、站心修正、掩带宽度、极区投影及升落边界
- 扩展 SVG 与 GeoJSON 输出,支持详细面板、全球/极区/地球投影、边界闭合、时间标记和拓扑签名
- 扩展日月食候选搜索、局地搜索、沙罗序列预计算与范围外推,补充系列锚点和成员一致性校验
- 补齐古历纪年、儒略历独有闰日、多公历候选、历法改革跨日及精确日期运算接口
- 优化 ΔT、章动、恒星时、月球地平线、事件根搜索和本地星历缓存,降低重复计算开销并提升边界稳定
2026-09-17 12:27:40 +08:00

83 lines
3.3 KiB
Go

package geojson
import (
"fmt"
eclipsecore "b612.me/astro/eclipse"
"b612.me/astro/internal/geodata"
)
// MarshalSolarEclipseShadowInstant 单时刻阴影足迹的最小 GeoJSON:只含该时刻的区域与物理边界 / one instant as the smallest useful GeoJSON.
func MarshalSolarEclipseShadowInstant(
instant eclipsecore.SolarEclipseShadowInstant,
) ([]byte, error) {
if instant.Empty() {
return marshalEmptyFeatureCollection()
}
curve, err := solarShadowFootprintCurveFromSegments(instant.Boundaries)
if err != nil {
return nil, fmt.Errorf("geojson: solar shadow instant at %s: %w", formatTime(instant.Time), err)
}
if len(curve) < 2 {
return marshalEmptyFeatureCollection()
}
properties := map[string]interface{}{
"model": string(instant.Model),
"delta_t_seconds": instant.DeltaTSeconds,
"interp_signature": instant.Topology.Signature(),
}
regionRole, boundaryRole := solarCentralShadowFootprintRole, solarCentralShadowBoundaryRole
if instant.Kind == eclipsecore.SolarEclipseShadowPenumbra {
regionRole, boundaryRole = solarPartialFootprintRole, solarPartialFootprintBoundaryRole
}
features := make([]feature, 0, 2)
regionProperties := cloneProperties(properties)
regionProperties["time"] = formatTime(instant.Time)
if instant.Closed {
// 与整包导出同构:自身闭合的区域不带 geometry_role,也不带 closure。
regionProperties["source_boundary_closed"] = true
ring := append([]geodata.GeoPoint(nil), curve...)
if solarShadowRegionDegenerate(curve, ring) {
// 首末接触附近区域收缩到零:整条缺省,不退化成线,也不让近零面积的环触发导出错误。
return marshalEmptyFeatureCollection()
}
value, err := multiPolygonGeometry([][]geodata.GeoPoint{ring})
if err != nil {
return nil, fmt.Errorf("geojson: solar shadow instant at %s: %w", formatTime(instant.Time), err)
}
return marshalFeatureCollection([]feature{
newFeature(solarEclipseEvent, regionRole, value, regionProperties),
})
}
regionProperties["source_boundary_closed"] = false
regionProperties["geometry_role"] = "horizon-closed-region"
regionProperties["closure"] = solarHorizonClosureProperties(
instant.Time, solarHorizonClosureExact(instant.Boundaries, instant.HorizonEnds),
)
ring, boundary := solarShadowFootprintHorizonRing(
instant.Time, instant.Boundaries, instant.HorizonEnds, curve,
)
if solarShadowRegionDegenerate(curve, ring) {
return marshalEmptyFeatureCollection()
}
value, err := multiPolygonGeometry([][]geodata.GeoPoint{ring})
if err != nil {
return nil, fmt.Errorf("geojson: solar shadow instant at %s: %w", formatTime(instant.Time), err)
}
features = append(features, newFeature(
solarEclipseEvent, regionRole, value, regionProperties,
))
boundaryValue, err := geoMultiLineGeometry(boundary, false)
if err != nil {
return nil, fmt.Errorf("geojson: solar shadow instant at %s: %w", formatTime(instant.Time), err)
}
boundaryProperties := cloneProperties(properties)
boundaryProperties["time"] = formatTime(instant.Time)
boundaryProperties["source_boundary_closed"] = false
boundaryProperties["geometry_role"] = "open-boundary"
features = append(features, newFeature(
solarEclipseEvent, boundaryRole, boundaryValue, boundaryProperties,
))
return marshalFeatureCollection(features)
}