coordinate frames transformation

This commit is contained in:
nuknal
2024-08-15 14:01:05 +08:00
parent 7c1240f2db
commit c2480a828b
12 changed files with 19640 additions and 16 deletions

12
cmd/eopp_5_line.txt Normal file
View File

@@ -0,0 +1,12 @@
60116.00 .141781 .000000 .069287 .102814 .017651 .001945365.25
435.00 .355810 .000000 -.014438 .004464 .082289 .077284365.25435.00
60309.00 .023848 .000093 .000000 .000000 -.022000 .006000
.000000 .000000 .012000 -.007000 500.0000 500.0000 365.2500 182.6250
37 4226 60535 60534 00000 .092937
60535 .18632757 .46806991 .03671475
60536 .18814225 .46718764 .03789417
60537 .18992531 .46631192 .03920366
60538 .19169447 .46542788 .04057785
60539 .19347468 .46450799 .04191240
60540 .19529435 .46351585 .04307934
60541 .19717570 .46241930 .04396107

19270
cmd/finals2000A.all Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1,5 +1,15 @@
package main
import (
_ "embed"
)
//go:embed finals2000A.all
var eopData []byte
//go:embed eopp_5_line.txt
var eopp5Line []byte
func main() {
rootCmd.Execute()
}

View File

@@ -9,6 +9,7 @@ import (
"github.com/airbusgeo/godal"
"github.com/sirupsen/logrus"
"github.com/spf13/cobra"
"starwiz.cn/sjy01/image-proc/pkg/calculator"
"starwiz.cn/sjy01/image-proc/pkg/config"
producer "starwiz.cn/sjy01/image-proc/pkg/producer"
)
@@ -34,6 +35,9 @@ var procCmd = &cobra.Command{
logrus.SetLevel(config.GCONFIG.Log.LogLevel)
calculator.EOP = calculator.NewEOPTable()
calculator.EOP.Load(eopData, eopp5Line)
reg := producer.NewRegistrator(producer.DownSampled)
reg.Params = initParams()

1
go.mod
View File

@@ -30,6 +30,7 @@ require (
github.com/golang/protobuf v1.5.4 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/hashicorp/hcl v1.0.0 // indirect
github.com/hebl/gofa v1.19.1 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/jonboulle/clockwork v0.4.0 // indirect
github.com/lestrrat-go/strftime v1.0.6 // indirect

2
go.sum
View File

@@ -1063,6 +1063,8 @@ github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO
github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ=
github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I=
github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
github.com/hebl/gofa v1.19.1 h1:/aVMeT0MXDrq5oMdTAKn1qyfIFlsDKvzx8DDLnsEx9s=
github.com/hebl/gofa v1.19.1/go.mod h1:UmDlahQBVbigXqA+ghdTtaXADoyQSWeiW+7nEB86HXU=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/iancoleman/strcase v0.2.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho=
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=

View File

@@ -38,13 +38,13 @@ func CameraDirectionVec(u, v float64) []float64 {
// fmt.Printf("Diagonal (calculated from FOV): %.6f mm\n", dCalcOfFOV)
directionVec := []float64{0, 0, 0}
directionVec[0] = 0 // x方向, mm线性CCD每次单行成像
directionVec[1] = (v - PANPixels/2) * PANCellSize / 1000.0 // y方向, mm
directionVec[2] = -FocalLength // z方向, mm
directionVec[0] = 0 // x方向, mm线性CCD每次单行成像
directionVec[1] = (v - PANPixels/2) * PANCellSize / 1000.0 / 1000.0 // y方向, mm
directionVec[2] = -FocalLength / 1000.0 // z方向, mm
// 归一化
// fmt.Printf("Direction Vector: (%.6f, %.6f, %.6f) \n", directionVec[0], directionVec[1], directionVec[2])
directionVec = normalizeVec(directionVec)
// directionVec = normalizeVec(directionVec)
// fmt.Printf("Direction Vector (normalized): (%.6f, %.6f, %.6f) \n", directionVec[0], directionVec[1], directionVec[2])
return directionVec

266
pkg/calculator/eop.go Normal file
View File

@@ -0,0 +1,266 @@
package calculator
import (
"bufio"
"bytes"
_ "embed"
"fmt"
"io"
"math"
"os"
"strconv"
"strings"
"time"
"github.com/hebl/gofa"
)
const (
ARC_SECONDS_PER_RADIAN = 206264.80624709636
)
// https://www.iers.org/IERS/EN/DataProducts/EarthOrientationData/eop.html
// https://maia.usno.navy.mil/ser7/finals2000A.all
type EOPEntry struct {
Year int
Month int
Day int
MJD float64
Xp float64 // 弧度 rad
Yp float64 // 弧度 rad
Dut1 float64
}
// https://earth-info.nga.mil/#eopp
type EOPP5Line struct {
Ta float64
A, B float64
C, D, P [2]float64
E, F float64
G, H, Q [2]float64
Tb float64
I, J float64
K, L, R [4]float64
}
type EOPTable struct {
URL string
Local2000A string
EOPs map[float64]*EOPEntry
EOPP5Line EOPP5Line
UseEOPP5Line bool
}
var EOP *EOPTable
func NewEOPTable() *EOPTable {
eop := &EOPTable{
URL: "https://maia.usno.navy.mil/ser7/finals2000A.all",
Local2000A: "data/EOP/finals2000A.all",
EOPs: make(map[float64]*EOPEntry),
UseEOPP5Line: false, // 当天使用 EOPP5Line 预测结果无法通过测试
}
return eop
}
func (eop *EOPTable) Load(eopData []byte, eopp5Line []byte) error {
f, err := os.Open(eop.Local2000A)
if err != nil {
return eop.loadEmbed(eopData, eopp5Line)
}
defer f.Close()
data, err := io.ReadAll(f)
if err != nil {
return eop.loadEmbed(eopData, eopp5Line)
}
return eop.loadEmbed(data, eopp5Line)
}
func (eop *EOPTable) loadEmbed(eopData []byte, eopp5Line []byte) error {
reader := bufio.NewReader(bytes.NewReader(eopData))
for {
line, _, err := reader.ReadLine()
if err == io.EOF {
break
}
if err != nil {
return err
}
var e EOPEntry
if err := e.line2EOPEntry(line); err != nil {
break
}
eop.EOPs[e.MJD] = &e
}
eop.EOPP5Line.parse(eopp5Line)
return nil
}
func (eop EOPTable) Get(mjd float64) (*EOPEntry, bool) {
e, ok := eop.EOPs[mjd]
if !ok && eop.UseEOPP5Line {
xp, yp, dut1 := eop.EOPP5Line.Predict(mjd)
e = &EOPEntry{
MJD: mjd,
Xp: xp / ARC_SECONDS_PER_RADIAN,
Yp: yp / ARC_SECONDS_PER_RADIAN,
Dut1: dut1,
}
ok = true
}
return e, ok
}
func (eop EOPTable) GetByUTCTime(utctime time.Time) (*EOPEntry, bool) {
var djm0, mjd float64
gofa.Cal2jd(int(utctime.Year()), int(utctime.Month()), int(utctime.Day()), &djm0, &mjd)
return eop.Get(mjd)
}
// https://maia.usno.navy.mil/ser7/readme.finals2000A
// The format of the finals2000A.data, finals2000A.daily, and finals2000A.all files is:
// Col.# Format Quantity
// ------- ------ -------------------------------------------------------------
// 1-2 I2 year (to get true calendar year, add 1900 for MJD<=51543 or add 2000 for MJD>=51544)
// 3-4 I2 month number
// 5-6 I2 day of month
// 7 X [blank]
// 8-15 F8.2 fractional Modified Julian Date (MJD UTC)
// 16 X [blank]
// 17 A1 IERS (I) or Prediction (P) flag for Bull. A polar motion values
// 18 X [blank]
// 19-27 F9.6 Bull. A PM-x (sec. of arc)
// 28-36 F9.6 error in PM-x (sec. of arc)
// 37 X [blank]
// 38-46 F9.6 Bull. A PM-y (sec. of arc)
// 47-55 F9.6 error in PM-y (sec. of arc)
// 56-57 2X [blanks]
// 58 A1 IERS (I) or Prediction (P) flag for Bull. A UT1-UTC values
// 59-68 F10.7 Bull. A UT1-UTC (sec. of time)
// 69-78 F10.7 error in UT1-UTC (sec. of time)
// 79 X [blank]
// 80-86 F7.4 Bull. A LOD (msec. of time) -- NOT ALWAYS FILLED
// 87-93 F7.4 error in LOD (msec. of time) -- NOT ALWAYS FILLED
// 94-95 2X [blanks]
// 96 A1 IERS (I) or Prediction (P) flag for Bull. A nutation values
// 97 X [blank]
// 98-106 F9.3 Bull. A dX wrt IAU2000A Nutation (msec. of arc), Free Core Nutation NOT Removed
// 107-115 F9.3 error in dX (msec. of arc)
// 116 X [blank]
// 117-125 F9.3 Bull. A dY wrt IAU2000A Nutation (msec. of arc), Free Core Nutation NOT Removed
// 126-134 F9.3 error in dY (msec. of arc)
// 135-144 F10.6 Bull. B PM-x (sec. of arc)
// 145-154 F10.6 Bull. B PM-y (sec. of arc)
// 155-165 F11.7 Bull. B UT1-UTC (sec. of time)
// 166-175 F10.3 Bull. B dX wrt IAU2000A Nutation (msec. of arc)
// 176-185 F10.3 Bull. B dY wrt IAU2000A Nutation (msec. of arc)
func (e *EOPEntry) line2EOPEntry(line []byte) error {
if len(line) < 68 {
return fmt.Errorf("invalid eop line")
}
e.Year, _ = strconv.Atoi(string(line[0:2]))
e.Month, _ = strconv.Atoi(strings.TrimSpace(string(line[2:4])))
e.Day, _ = strconv.Atoi(strings.TrimSpace(string(line[4:6])))
e.MJD, _ = strconv.ParseFloat(string(line[7:15]), 64)
e.Xp, _ = strconv.ParseFloat(strings.TrimSpace(string(line[18:27])), 64)
e.Yp, _ = strconv.ParseFloat(strings.TrimSpace(string(line[37:46])), 64)
e.Xp = e.Xp / ARC_SECONDS_PER_RADIAN
e.Yp = e.Yp / ARC_SECONDS_PER_RADIAN
e.Dut1, _ = strconv.ParseFloat(strings.TrimSpace(string(line[58:68])), 64)
return nil
}
func (e *EOPP5Line) parse(eopp5Line []byte) error {
e.P[0] = 1.0
e.P[1] = 1.0
for i := 0; i < 4; i++ {
e.R[i] = 1.0
}
reader := bufio.NewReader(bytes.NewReader(eopp5Line))
var lines [][]byte
for {
line, _, err := reader.ReadLine()
if err == io.EOF {
break
}
if err != nil {
return err
}
lines = append(lines, line)
}
if len(lines) < 5 {
return fmt.Errorf("invalid eopp5 lines")
}
e.Ta, _ = strconv.ParseFloat(strings.TrimSpace(string(lines[0][0:10])), 64)
e.A, _ = strconv.ParseFloat(strings.TrimSpace(string(lines[0][10:20])), 64)
e.B, _ = strconv.ParseFloat(strings.TrimSpace(string(lines[0][20:30])), 64)
e.C[0], _ = strconv.ParseFloat(strings.TrimSpace(string(lines[0][30:40])), 64)
e.C[1], _ = strconv.ParseFloat(strings.TrimSpace(string(lines[0][40:50])), 64)
e.D[0], _ = strconv.ParseFloat(strings.TrimSpace(string(lines[0][50:60])), 64)
e.D[1], _ = strconv.ParseFloat(strings.TrimSpace(string(lines[0][60:70])), 64)
e.P[0], _ = strconv.ParseFloat(strings.TrimSpace(string(lines[0][70:76])), 64)
e.P[1], _ = strconv.ParseFloat(strings.TrimSpace(string(lines[1][0:6])), 64)
e.E, _ = strconv.ParseFloat(strings.TrimSpace(string(lines[1][6:16])), 64)
e.F, _ = strconv.ParseFloat(strings.TrimSpace(string(lines[1][16:26])), 64)
e.G[0], _ = strconv.ParseFloat(strings.TrimSpace(string(lines[1][26:36])), 64)
e.G[1], _ = strconv.ParseFloat(strings.TrimSpace(string(lines[1][36:46])), 64)
e.H[0], _ = strconv.ParseFloat(strings.TrimSpace(string(lines[1][46:56])), 64)
e.H[1], _ = strconv.ParseFloat(strings.TrimSpace(string(lines[1][56:66])), 64)
e.Q[0], _ = strconv.ParseFloat(strings.TrimSpace(string(lines[1][66:72])), 64)
e.Q[1], _ = strconv.ParseFloat(strings.TrimSpace(string(lines[1][72:78])), 64)
e.Tb, _ = strconv.ParseFloat(strings.TrimSpace(string(lines[2][0:10])), 64)
e.I, _ = strconv.ParseFloat(strings.TrimSpace(string(lines[2][10:20])), 64)
e.J, _ = strconv.ParseFloat(strings.TrimSpace(string(lines[2][20:30])), 64)
e.K[0], _ = strconv.ParseFloat(strings.TrimSpace(string(lines[2][30:40])), 64)
e.K[1], _ = strconv.ParseFloat(strings.TrimSpace(string(lines[2][40:50])), 64)
e.K[2], _ = strconv.ParseFloat(strings.TrimSpace(string(lines[2][50:60])), 64)
e.K[3], _ = strconv.ParseFloat(strings.TrimSpace(string(lines[2][60:70])), 64)
e.L[0], _ = strconv.ParseFloat(strings.TrimSpace(string(lines[3][0:10])), 64)
e.L[1], _ = strconv.ParseFloat(strings.TrimSpace(string(lines[3][10:20])), 64)
e.L[2], _ = strconv.ParseFloat(strings.TrimSpace(string(lines[3][20:30])), 64)
e.L[3], _ = strconv.ParseFloat(strings.TrimSpace(string(lines[3][30:40])), 64)
e.R[0], _ = strconv.ParseFloat(strings.TrimSpace(string(lines[3][40:49])), 64)
e.R[1], _ = strconv.ParseFloat(strings.TrimSpace(string(lines[3][49:58])), 64)
e.R[2], _ = strconv.ParseFloat(strings.TrimSpace(string(lines[3][58:67])), 64)
e.R[3], _ = strconv.ParseFloat(strings.TrimSpace(string(lines[3][67:76])), 64)
return nil
}
// https://earth-info.nga.mil/php/download.php?file=gnss-eopp
func (e *EOPP5Line) Predict(mjd float64) (xp, yp, dut1 float64) {
xp = e.A + e.B*(mjd-e.Ta) +
e.C[0]*math.Sin(2*math.Pi*(mjd-e.Ta)/e.P[0]) + e.D[0]*math.Cos(2*math.Pi*(mjd-e.Ta)/e.P[0]) +
e.C[1]*math.Sin(2*math.Pi*(mjd-e.Ta)/e.P[1]) + e.D[1]*math.Cos(2*math.Pi*(mjd-e.Ta)/e.P[1])
yp = e.E + e.F*(mjd-e.Ta) +
e.G[0]*math.Sin(2*math.Pi*(mjd-e.Ta)/e.Q[0]) + e.H[0]*math.Cos(2*math.Pi*(mjd-e.Ta)/e.Q[0]) +
e.G[1]*math.Sin(2*math.Pi*(mjd-e.Ta)/e.Q[1]) + e.H[1]*math.Cos(2*math.Pi*(mjd-e.Ta)/e.Q[1])
dut1 = e.I + e.J*(mjd-e.Tb) +
e.K[0]*math.Sin(2*math.Pi*(mjd-e.Tb)/e.R[0]) + e.L[0]*math.Cos(2*math.Pi*(mjd-e.Tb)/e.R[0]) +
e.K[1]*math.Sin(2*math.Pi*(mjd-e.Tb)/e.R[1]) + e.L[1]*math.Cos(2*math.Pi*(mjd-e.Tb)/e.R[1]) +
e.K[2]*math.Sin(2*math.Pi*(mjd-e.Tb)/e.R[2]) + e.L[2]*math.Cos(2*math.Pi*(mjd-e.Tb)/e.R[2]) +
e.K[3]*math.Sin(2*math.Pi*(mjd-e.Tb)/e.R[3]) + e.L[3]*math.Cos(2*math.Pi*(mjd-e.Tb)/e.R[3])
return
}

View File

@@ -15,7 +15,7 @@ type IntersectionPoint struct {
H float64
}
func IntersectionAttitude(q Quaternion, satPos84 []float64, satTime time.Time, ucam int) (IntersectionPoint, error) {
func IntersectionAttitude(q Quaternion, satPosECI, satPos84 []float64, satTime time.Time, ucam int) (IntersectionPoint, error) {
// alpha := FOV * math.Pi / 180.0
// alpha = -alpha/2.0 + float64(ucam)*(alpha/float64(PANPixels))
// direction := []float64{0, math.Tan(alpha), -1.3}
@@ -33,16 +33,19 @@ func IntersectionAttitude(q Quaternion, satPos84 []float64, satTime time.Time, u
dECI := result.RawVector().Data
// -------- 转到ECEF坐标系 --------
x, y, z := ECItoECEF(dECI[0], dECI[1], dECI[2], satTime)
dECEF := []float64{x, y, z}
// x, y, z := ECItoECEF(dECI[0], dECI[1], dECI[2], satTime)
// dECEF := []float64{x, y, z}
// -------- 计算与地球表面的交点 --------
intersection, err := intersectWithEllipsoid(satPos84, dECEF)
intersection, err := intersectWithEllipsoid(satPosECI, dECI)
if err != nil {
return IntersectionPoint{}, err
}
lat, lon, h := ECEFToGeodetic(intersection[0], intersection[1], intersection[2])
x, y, z := ECItoECEF(intersection[0], intersection[1], intersection[2], satTime)
intersection = []float64{x, y, z}
lat, lon, h := ECEFGeocentricToGeodetic(intersection[0], intersection[1], intersection[2])
return IntersectionPoint{Lat: lat, Lon: lon, H: h}, nil
}
@@ -78,7 +81,7 @@ func IntersectionECI(Qsat2orbit, Qorbit2eci Quaternion, satPos84 []float64, satT
return IntersectionPoint{}, err
}
lat, lon, h := ECEFToGeodetic(intersection[0], intersection[1], intersection[2])
lat, lon, h := ECEFGeocentricToGeodetic(intersection[0], intersection[1], intersection[2])
return IntersectionPoint{Lat: lat, Lon: lon, H: h}, nil
}

View File

@@ -9,11 +9,11 @@ import (
func J2000ToWGS84(j2000X, j2000Y, j2000Z float64, utc time.Time) (float64, float64, float64) {
itrsX, itrsY, itrsZ := ECItoECEF(j2000X, j2000Y, j2000Z, utc)
// Convert ITRS to geodetic coordinates (WGS84)
latitude, longitude, height := ECEFToGeodetic(itrsX, itrsY, itrsZ)
latitude, longitude, height := ECEFGeocentricToGeodetic(itrsX, itrsY, itrsZ)
return latitude, longitude, height
}
func ECItoECEF(j2000X, j2000Y, j2000Z float64, utc time.Time) (float64, float64, float64) {
func _ECItoECEF(j2000X, j2000Y, j2000Z float64, utc time.Time) (float64, float64, float64) {
julianDate := UTCToJulianDate(utc)
gast := CalculateGAST(julianDate, utc)

54
pkg/calculator/sofa.go Normal file
View File

@@ -0,0 +1,54 @@
package calculator
import (
"math"
"time"
"github.com/hebl/gofa"
)
func ECItoECEF(j2000X, j2000Y, j2000Z float64, utc time.Time) (float64, float64, float64) {
j2000Position := [3]float64{j2000X, j2000Y, j2000Z}
var ecef [3]float64
// 当前的 TT 时间 (Terrestrial Time)
var tt1, tt2 float64
gofa.Dtf2d("UTC",
utc.Year(),
int(utc.Month()),
utc.Day(),
utc.Hour(),
utc.Minute(),
float64(utc.Second()),
&tt1, &tt2)
var djm0, mjd float64
gofa.Cal2jd(int(utc.Year()), int(utc.Month()), int(utc.Day()), &djm0, &mjd)
var dut1 float64 = 0.0562
xp := 0.0
yp := 0.0
if e, ok := EOP.Get(mjd); ok {
xp = e.Xp
yp = e.Yp
dut1 = e.Dut1
}
// 计算 UT1 时间 世界时UT1与协调世界时UTC
var ut11, ut12 float64
gofa.Utcut1(tt1, tt2, dut1, &ut11, &ut12)
// 计算 C2T 矩阵 (ICRS -> CIRS -> ECEF)
var rc2t [3][3]float64
gofa.C2t06a(tt1, tt2, ut11, ut12, xp, yp, &rc2t)
// 计算 ECEF 坐标
gofa.Rxp(rc2t, j2000Position, &ecef)
return ecef[0], ecef[1], ecef[2]
}
func ECEFGeocentricToGeodetic(x, y, z float64) (float64, float64, float64) {
var lat, lon, alt float64
gofa.Gc2gd(gofa.WGS84, [3]float64{x, y, z}, &lon, &lat, &alt)
return lat * 180 / math.Pi, lon * 180 / math.Pi, alt
}

View File

@@ -114,7 +114,9 @@ func (r *Registrator) SetSceneBoundary(scene *Scene) (topLeft, bottomRight orb.P
endTime := time.Unix(int64(auxilary.ReferenceTime2000)+int64(ae.UTCTimeSec), int64(ae.Microsecond)*1000).UTC()
startPos84 := []float64{as.W84PosX, as.W84PosY, as.W84PosZ}
startPosECI := []float64{as.J2000PosX, as.J2000PosY, as.J2000PosZ}
endPos84 := []float64{ae.W84PosX, ae.W84PosY, ae.W84PosZ}
endPosECI := []float64{ae.J2000PosX, ae.J2000PosY, ae.J2000PosZ}
// FIXME: GPS 拟合效果不佳
// x0 := float64(r.auxHeads[startPosInAux].TimeSec) + float64(r.auxHeads[startPosInAux].TimeSecFrac)/10e6
@@ -135,12 +137,12 @@ func (r *Registrator) SetSceneBoundary(scene *Scene) (topLeft, bottomRight orb.P
// ------------------ 使用定姿态四元数计算图像边界 ------------------
log.Info("using attitude quaternion to calculate image boundary...")
Qsat2eci := calculator.Quaternion{W: as.QuatAttstarQ0, X: as.QuatAttstarQ1, Y: as.QuatAttstarQ2, Z: as.QuatAttstarQ3}
line0Start, _ := calculator.IntersectionAttitude(Qsat2eci, startPos84, startTime, 0)
line0End, _ := calculator.IntersectionAttitude(Qsat2eci, startPos84, startTime, payload.PAN_PIXEL_WIDTH)
line0Start, _ := calculator.IntersectionAttitude(Qsat2eci, startPosECI, startPos84, startTime, 0)
line0End, _ := calculator.IntersectionAttitude(Qsat2eci, startPosECI, startPos84, startTime, payload.PAN_PIXEL_WIDTH)
Qsat2eci = calculator.Quaternion{W: ae.QuatAttstarQ0, X: ae.QuatAttstarQ1, Y: ae.QuatAttstarQ2, Z: ae.QuatAttstarQ3}
lineNStart, _ := calculator.IntersectionAttitude(Qsat2eci, endPos84, endTime, 0)
lineNEnd, _ := calculator.IntersectionAttitude(Qsat2eci, endPos84, endTime, payload.PAN_PIXEL_WIDTH)
lineNStart, _ := calculator.IntersectionAttitude(Qsat2eci, endPosECI, endPos84, endTime, 0)
lineNEnd, _ := calculator.IntersectionAttitude(Qsat2eci, endPosECI, endPos84, endTime, payload.PAN_PIXEL_WIDTH)
// ------------------ 使用本体和轨道四元数计算图像边界 ECI------------------
// log.Info("using orbit and body quaternion to calculate image boundary...")