feat: 完善水星与金星全球凌日搜索及制图导出
- 基于平均下合周期与节点相位预筛候选,减少无效星历计算;跳过无法求解的单个会合周期,避免有限覆盖星历导致搜索提前终止 - 引入影轴 γ、半影半径及地球尺度几何判定,结合站心可见性识别地心无接触、仅部分地区可见的掠凌 - 添加金星 243 年、水星 46 年周期族编号,并明确编号适用范围及未分配时的零值语义 - 为 SVG、GeoJSON 和 KML 增加站心部分凌日可见区,适配接触等时线、几何元数据、图层样式及取景;为地心无接触凌日事件增加正射图,按可见区球面面积加权居中
This commit is contained in:
41 files changed
+1554
-307
No files matched your search
@@ -0,0 +1,94 @@
|
||||
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
|
||||
}
|
||||
Reference in new issue
Block a user