feat: 完善日月食与月掩几何链路并扩展历法接口
- 新增日月食中心带、偏食带、阴影足迹、等时线、食分线及升落边界计算,支持极区与混合食拓扑 - 新增日食单时刻阴影求解器、站心状态查询、批量采样和 ΔT 覆盖接口 - 重构恒星与行星月掩路径,补充有限盘面接触、站心修正、掩带宽度、极区投影及升落边界 - 扩展 SVG 与 GeoJSON 输出,支持详细面板、全球/极区/地球投影、边界闭合、时间标记和拓扑签名 - 扩展日月食候选搜索、局地搜索、沙罗序列预计算与范围外推,补充系列锚点和成员一致性校验 - 补齐古历纪年、儒略历独有闰日、多公历候选、历法改革跨日及精确日期运算接口 - 优化 ΔT、章动、恒星时、月球地平线、事件根搜索和本地星历缓存,降低重复计算开销并提升边界稳定
This commit is contained in:
@@ -0,0 +1,83 @@
|
||||
package svg
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"html"
|
||||
"strings"
|
||||
|
||||
"b612.me/astro/moon"
|
||||
)
|
||||
|
||||
// writeOccultationGreatestTimeContours 绘制地方掩甚时刻等值线及其 HH:MM 标注。
|
||||
func writeOccultationGreatestTimeContours(
|
||||
b *strings.Builder,
|
||||
contours []moon.OccultationGreatestTimeContour,
|
||||
layout starOccultationSVGLayout,
|
||||
options StarOccultationSVGOptions,
|
||||
placer *occultationLabelPlacer,
|
||||
) {
|
||||
if len(contours) == 0 {
|
||||
return
|
||||
}
|
||||
type isochroneLabel struct {
|
||||
x, y float64
|
||||
text string
|
||||
}
|
||||
labels := make([]isochroneLabel, 0, len(contours))
|
||||
for _, contour := range contours {
|
||||
for _, segment := range contour.Segments {
|
||||
writeStarOccultationGeoLine(b, segment, layout,
|
||||
occultationLineStyle{className: "greatest-time-isoline", color: "#1f6fb2", strokeWidth: 1.1})
|
||||
}
|
||||
text := contour.Time.In(options.Location).Format("15:04")
|
||||
if x, y, ok := occultationGreatestTimeIsolineLabelPlacement(contour, text, layout, placer); ok {
|
||||
labels = append(labels, isochroneLabel{x: x, y: y, text: text})
|
||||
}
|
||||
}
|
||||
for _, label := range labels {
|
||||
fmt.Fprintf(b, `<text class="greatest-time-isoline-label" x="%.3f" y="%.3f" fill="#1a5c96" stroke="#ffffff" stroke-width="2.6" paint-order="stroke" font-family="Arial, sans-serif" font-size="9" font-weight="700" text-anchor="middle">%s</text>`,
|
||||
label.x, label.y, html.EscapeString(label.text))
|
||||
}
|
||||
}
|
||||
|
||||
// occultationGreatestTimeIsolineLabelPlacement 在最长的支路上取首个未被其它标签占用的标注点。
|
||||
func occultationGreatestTimeIsolineLabelPlacement(
|
||||
contour moon.OccultationGreatestTimeContour,
|
||||
text string,
|
||||
layout starOccultationSVGLayout,
|
||||
placer *occultationLabelPlacer,
|
||||
) (float64, float64, bool) {
|
||||
longest := -1
|
||||
for index, segment := range contour.Segments {
|
||||
if longest < 0 || len(segment) > len(contour.Segments[longest]) {
|
||||
longest = index
|
||||
}
|
||||
}
|
||||
if longest < 0 {
|
||||
return 0, 0, false
|
||||
}
|
||||
segment := contour.Segments[longest]
|
||||
frame := layout.mapFrame()
|
||||
middle := len(segment) / 2
|
||||
for offset := 0; offset <= middle; offset++ {
|
||||
for _, index := range []int{middle + offset, middle - offset} {
|
||||
if index < 0 || index >= len(segment) {
|
||||
continue
|
||||
}
|
||||
point := segment[index]
|
||||
x, y, visible := frame.Project(point.Longitude, point.Latitude)
|
||||
if !visible ||
|
||||
x < frame.X+30 || x > frame.X+frame.Width-30 ||
|
||||
y < frame.Y+18 || y > frame.Y+frame.Height-18 {
|
||||
continue
|
||||
}
|
||||
// 优先压在等时线上方,其次下方;两者都被占用就换下一个候选点。
|
||||
for _, baseline := range []float64{y - 6, y + 13} {
|
||||
if placer.place(x, baseline, "middle", text, 9, 2) {
|
||||
return x, baseline, true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0, 0, false
|
||||
}
|
||||
Reference in New Issue
Block a user