Files
astro/eclipse/svg/transit_partial.go
T
b612 ccf5880269 feat: 完善水星与金星全球凌日搜索及制图导出
- 基于平均下合周期与节点相位预筛候选,减少无效星历计算;跳过无法求解的单个会合周期,避免有限覆盖星历导致搜索提前终止
- 引入影轴 γ、半影半径及地球尺度几何判定,结合站心可见性识别地心无接触、仅部分地区可见的掠凌
- 添加金星 243 年、水星 46 年周期族编号,并明确编号适用范围及未分配时的零值语义
- 为 SVG、GeoJSON 和 KML 增加站心部分凌日可见区,适配接触等时线、几何元数据、图层样式及取景;为地心无接触凌日事件增加正射图,按可见区球面面积加权居中
2026-10-20 22:35:39 +08:00

95 lines
3.4 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package svg
import (
"fmt"
"math"
"strings"
"b612.me/astro/basic"
"b612.me/astro/internal/svgmap"
)
func transitMapOrthographicCenter(transit basic.PlanetTransitResult) svgmap.GeoPoint {
if !transit.HasExternal {
if center, ok := transitMapVisibilityCenter(basic.PlanetTransitVisibilityColumns(transit)); ok {
return center
}
}
return solarEclipseSubsolarPoint(transitMapContactsOf(transit).greatest)
}
func transitMapVisibilityCenter(columns []basic.PlanetTransitVisibilityColumn) (svgmap.GeoPoint, bool) {
var momentX, momentY, momentZ float64
for index, column := range columns {
weight := 0.0
if index > 0 {
weight += (column.Longitude - columns[index-1].Longitude) / 2
}
if index+1 < len(columns) {
weight += (columns[index+1].Longitude - column.Longitude) / 2
}
longitude := column.Longitude * math.Pi / 180
for _, interval := range column.Intervals {
lower, upper := interval[0]*math.Pi/180, interval[1]*math.Pi/180
horizontalMoment := (upper-lower)/2 + (math.Sin(2*upper)-math.Sin(2*lower))/4
verticalMoment := (math.Sin(upper)*math.Sin(upper) - math.Sin(lower)*math.Sin(lower)) / 2
momentX += weight * horizontalMoment * math.Cos(longitude)
momentY += weight * horizontalMoment * math.Sin(longitude)
momentZ += weight * verticalMoment
}
}
if math.Sqrt(momentX*momentX+momentY*momentY+momentZ*momentZ) < 1e-12 {
return svgmap.GeoPoint{}, false
}
return svgmap.GeoPoint{
Longitude: math.Atan2(momentY, momentX) * 180 / math.Pi,
Latitude: math.Atan2(momentZ, math.Hypot(momentX, momentY)) * 180 / math.Pi,
}, true
}
func transitMapProjectionLabel(projection svgmap.Projection, hasExternal bool, language string) string {
if projection != svgmap.ProjectionOrthographic {
return eclipseMapProjectionLabel(projection, language)
}
if language == "en" {
center := "the subsolar point at greatest"
if !hasExternal {
center = "the topocentric visible footprint"
}
return "Orthographic globe projection, centred on " + center + "; one hemisphere only"
}
center := "凌甚太阳直射点"
if !hasExternal {
center = "站心可见区中心"
}
return "正射球面投影,视点取" + center + ",只画朝向视点的半个地球"
}
func writeTransitMapPartialRegions(builder *strings.Builder, frame svgmap.Frame, transit basic.PlanetTransitResult) {
fmt.Fprintf(builder, `<g class="transit-visibility-regions" data-source="%s" clip-path="url(#transit-map-clip)">`, eclipseMapSourceTransitPartialVisibility)
var path strings.Builder
for _, polygon := range basic.PlanetTransitVisibilityPolygons(transit) {
points := make([]svgmap.GeoPoint, len(polygon))
for index, point := range polygon {
points[index] = svgmap.GeoPoint{Longitude: point.Longitude, Latitude: point.Latitude}
}
path.WriteString(lunarEclipseGeoPolygonPath(points, frame))
}
if path.Len() > 0 {
fmt.Fprintf(builder, `<path class="transit-partial-region" d="%s" fill="%s" fill-opacity="%.2f" fill-rule="nonzero"/>`, path.String(), transitMapColorPartial, transitMapPartialFillOpacity)
}
builder.WriteString(`</g>`)
}
func transitMapGeometryNote(transit basic.PlanetTransitResult, language string) string {
text := fmt.Sprintf("γ %.4f", transit.Gamma)
if transit.Series.Number != 0 {
if language == "en" {
text += fmt.Sprintf(" | Series %d (%d yr)", transit.Series.Number, transit.Series.PeriodYears)
} else {
text += fmt.Sprintf(" | 周期族 %d(%d 年)", transit.Series.Number, transit.Series.PeriodYears)
}
}
return text
}