feat(orbitviewer): add satellite orbit viewer with 2D track, 10-day pass prediction, and AR sky view
Build and Push Docker Image / build (push) Successful in 2m31s

This commit is contained in:
2026-09-23 00:06:01 -07:00
parent c4caed6569
commit fa3dc66278
17 changed files with 4381 additions and 95 deletions
+63 -63
View File
@@ -340,70 +340,70 @@ func parseSVGPath(path string, scale, ox, oy float64, flipY bool) []gopdf.Point
var pts []gopdf.Point
matches := reSVG.FindAllStringSubmatch(path, -1)
var lastX, lastY float64
for idx := 0; idx < len(matches); {
item := matches[idx][0]
switch item {
case "M", "L":
if idx+2 < len(matches) {
x, _ := strconv.ParseFloat(matches[idx+1][0], 64)
y, _ := strconv.ParseFloat(matches[idx+2][0], 64)
lastX, lastY = x, y
if flipY {
y = 1024 - y
}
pts = append(pts, gopdf.Point{X: ox + x*scale, Y: oy + y*scale})
idx += 3
} else {
idx++
}
case "C":
if idx+6 < len(matches) {
x1, _ := strconv.ParseFloat(matches[idx+1][0], 64)
y1, _ := strconv.ParseFloat(matches[idx+2][0], 64)
x2, _ := strconv.ParseFloat(matches[idx+3][0], 64)
y2, _ := strconv.ParseFloat(matches[idx+4][0], 64)
x, _ := strconv.ParseFloat(matches[idx+5][0], 64)
y, _ := strconv.ParseFloat(matches[idx+6][0], 64)
for t := 0.2; t <= 1.0; t += 0.2 {
invT := 1 - t
tx := invT*invT*invT*lastX + 3*invT*invT*t*x1 + 3*invT*t*t*x2 + t*t*t*x
ty := invT*invT*invT*lastY + 3*invT*invT*t*y1 + 3*invT*t*t*y2 + t*t*t*y
ty_f := ty
if flipY {
ty_f = 1024 - ty
}
pts = append(pts, gopdf.Point{X: ox + tx*scale, Y: oy + ty_f*scale})
}
lastX, lastY = x, y
idx += 7
} else {
idx++
}
case "Q":
if idx+4 < len(matches) {
x1, _ := strconv.ParseFloat(matches[idx+1][0], 64)
y1, _ := strconv.ParseFloat(matches[idx+2][0], 64)
x, _ := strconv.ParseFloat(matches[idx+3][0], 64)
y, _ := strconv.ParseFloat(matches[idx+4][0], 64)
for t := 0.2; t <= 1.0; t += 0.2 {
invT := 1 - t
tx := invT*invT*lastX + 2*invT*t*x1 + t*t*x
ty := invT*invT*lastY + 2*invT*t*y1 + t*t*y
ty_f := ty
if flipY {
ty_f = 1024 - ty
}
pts = append(pts, gopdf.Point{X: ox + tx*scale, Y: oy + ty_f*scale})
}
lastX, lastY = x, y
idx += 5
} else {
idx++
}
default:
idx++
for idx := 0; idx < len(matches); {
item := matches[idx][0]
switch item {
case "M", "L":
if idx+2 < len(matches) {
x, _ := strconv.ParseFloat(matches[idx+1][0], 64)
y, _ := strconv.ParseFloat(matches[idx+2][0], 64)
lastX, lastY = x, y
if flipY {
y = 1024 - y
}
pts = append(pts, gopdf.Point{X: ox + x*scale, Y: oy + y*scale})
idx += 3
} else {
idx++
}
case "C":
if idx+6 < len(matches) {
x1, _ := strconv.ParseFloat(matches[idx+1][0], 64)
y1, _ := strconv.ParseFloat(matches[idx+2][0], 64)
x2, _ := strconv.ParseFloat(matches[idx+3][0], 64)
y2, _ := strconv.ParseFloat(matches[idx+4][0], 64)
x, _ := strconv.ParseFloat(matches[idx+5][0], 64)
y, _ := strconv.ParseFloat(matches[idx+6][0], 64)
for t := 0.2; t <= 1.0; t += 0.2 {
invT := 1 - t
tx := invT*invT*invT*lastX + 3*invT*invT*t*x1 + 3*invT*t*t*x2 + t*t*t*x
ty := invT*invT*invT*lastY + 3*invT*invT*t*y1 + 3*invT*t*t*y2 + t*t*t*y
ty_f := ty
if flipY {
ty_f = 1024 - ty
}
pts = append(pts, gopdf.Point{X: ox + tx*scale, Y: oy + ty_f*scale})
}
lastX, lastY = x, y
idx += 7
} else {
idx++
}
case "Q":
if idx+4 < len(matches) {
x1, _ := strconv.ParseFloat(matches[idx+1][0], 64)
y1, _ := strconv.ParseFloat(matches[idx+2][0], 64)
x, _ := strconv.ParseFloat(matches[idx+3][0], 64)
y, _ := strconv.ParseFloat(matches[idx+4][0], 64)
for t := 0.2; t <= 1.0; t += 0.2 {
invT := 1 - t
tx := invT*invT*lastX + 2*invT*t*x1 + t*t*x
ty := invT*invT*lastY + 2*invT*t*y1 + t*t*y
ty_f := ty
if flipY {
ty_f = 1024 - ty
}
pts = append(pts, gopdf.Point{X: ox + tx*scale, Y: oy + ty_f*scale})
}
lastX, lastY = x, y
idx += 5
} else {
idx++
}
default:
idx++
}
}
return pts
}