ccf5880269
- 基于平均下合周期与节点相位预筛候选,减少无效星历计算;跳过无法求解的单个会合周期,避免有限覆盖星历导致搜索提前终止 - 引入影轴 γ、半影半径及地球尺度几何判定,结合站心可见性识别地心无接触、仅部分地区可见的掠凌 - 添加金星 243 年、水星 46 年周期族编号,并明确编号适用范围及未分配时的零值语义 - 为 SVG、GeoJSON 和 KML 增加站心部分凌日可见区,适配接触等时线、几何元数据、图层样式及取景;为地心无接触凌日事件增加正射图,按可见区球面面积加权居中
95 lines
3.4 KiB
Go
95 lines
3.4 KiB
Go
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
|
||
}
|