271 lines
8.9 KiB
Python
271 lines
8.9 KiB
Python
#!/usr/bin/env python3
|
|
import os
|
|
import re
|
|
import struct
|
|
import datetime
|
|
import logging
|
|
from isceobj.Orbit.Orbit import Orbit
|
|
from isceobj.Orbit.Orbit import StateVector
|
|
from iscesys.DateTimeUtil.DateTimeUtil import DateTimeUtil as DTU
|
|
import isceobj.Planet.AstronomicalHandbook as AstronomicalHandbook
|
|
import isceobj.Planet.Ellipsoid as Ellipsoid
|
|
from isceobj.Util.decorators import type_check, pickled, logged
|
|
|
|
|
|
@pickled
|
|
class ODR(object):
|
|
"""A class to parse Orbital Data Records (ODR) generated by Delft"""
|
|
|
|
logging_name = 'isceobj.Orbit.ODR'
|
|
|
|
@logged
|
|
def __init__(self, file=None):
|
|
self._file = file
|
|
self._format = None
|
|
self._satellite = None
|
|
self._arcNumber = None
|
|
self._cycleLength = None
|
|
self._numberOfRecords = None
|
|
self._version = None
|
|
self._ephemeris = Orbit()
|
|
self._ephemeris.configure()
|
|
self._ephemeris.setOrbitSource('ODR')
|
|
self._ephemeris.setReferenceFrame('ECR')
|
|
self.grs80 = Ellipsoid.Ellipsoid(
|
|
a=AstronomicalHandbook.PlanetsData.ellipsoid['Earth']['GRS-80'].a,
|
|
e2=AstronomicalHandbook.PlanetsData.ellipsoid['Earth']['GRS-80'].e2
|
|
)
|
|
|
|
return None
|
|
|
|
#jng added the start and stop time. The computation of the velocities seems pretty time comsuming, so limit the orbit data extraction only to startTime nad stopTime
|
|
def parseHeader(self, startTime=None, stopTime=None):
|
|
fp = None
|
|
try:
|
|
fp = open(self._file,'rb')
|
|
except IOError as strerr:
|
|
self.logger.error(strerr)
|
|
buffer = fp.read(16)
|
|
# Header 1
|
|
(format,satellite,dataStartTimeSeconds) = struct.unpack('>4s8si',buffer)
|
|
buffer = fp.read(16)
|
|
# Header 2
|
|
(cycleLength,number,numberOfRecords,version) = struct.unpack('>4i',buffer)
|
|
|
|
self._format = format.decode('utf-8')
|
|
self._satellite = satellite.decode('utf-8')
|
|
self._arcNumber = number
|
|
self._cycleLength = cycleLength*1e3 # In cycle length in days
|
|
self._numberOfRecords = numberOfRecords
|
|
self._version = version
|
|
|
|
positions = []
|
|
for i in range(numberOfRecords):
|
|
buffer = fp.read(16)
|
|
if not startTime == None:
|
|
position = self.parseDataRecords(buffer)
|
|
if position['time'] < startTime:
|
|
continue
|
|
|
|
if not stopTime == None:
|
|
position = self.parseDataRecords(buffer)
|
|
if position['time'] > stopTime:
|
|
continue
|
|
|
|
positions.append(self.parseDataRecords(buffer))
|
|
|
|
self.createStateVectors(positions)
|
|
fp.close()
|
|
|
|
def parseDataRecords(self,buffer):
|
|
"""Parse the individual data records for this ODR file"""
|
|
(timeSeconds,latitude,longitude,height) = struct.unpack('>4i',buffer)
|
|
time = self._utcSecondsToDatetime(timeSeconds)
|
|
if (self._format == '@ODR'):
|
|
latitude = latitude*1e-6
|
|
longitude = longitude*1e-6
|
|
elif (self._format == 'xODR'):
|
|
latitude = latitude*1e-7
|
|
longitude = longitude*1e-7
|
|
height = height*1e-3
|
|
|
|
xyz = self._convertToXYZ(latitude,longitude,height)
|
|
return ({'time': time,
|
|
'x':xyz[0],
|
|
'y':xyz[1],
|
|
'z':xyz[2]})
|
|
|
|
|
|
def createStateVectors(self,positions):
|
|
"""Calculate the satellite velocity from the position data and create StateVector objects"""
|
|
|
|
for i in range(len(positions)):
|
|
t0 = positions[i]['time']
|
|
x0 = positions[i]['x']
|
|
y0 = positions[i]['y']
|
|
z0 = positions[i]['z']
|
|
|
|
sv = StateVector()
|
|
sv.configure()
|
|
sv.setTime(t0)
|
|
sv.setPosition([x0,y0,z0])
|
|
sv.setVelocity([0.0,0.0,0.0])
|
|
self._ephemeris.addStateVector(sv)
|
|
self._calculateVelocities()
|
|
|
|
def _calculateVelocities(self):
|
|
##PSA: Need enough state vectors before and after to make sure interpolation is reasonable
|
|
##Call to trimOrbit is always needed to get rid of padding state vectors
|
|
for sv in self._ephemeris[5:-5]:
|
|
t0 = sv.getTime()
|
|
t1 = t0 + datetime.timedelta(seconds=-0.5)
|
|
t2 = t0 + datetime.timedelta(seconds=0.5)
|
|
|
|
try:
|
|
sv1 = self._ephemeris.interpolateOrbit(t1,method='legendre')
|
|
sv2 = self._ephemeris.interpolateOrbit(t2,method='legendre')
|
|
except ValueError:
|
|
continue
|
|
if (not sv1) or (not sv2):
|
|
continue
|
|
v1 = sv1.getPosition()
|
|
v2 = sv2.getPosition()
|
|
vx = (v2[0]-v1[0])
|
|
vy = (v2[1]-v1[1])
|
|
vz = (v2[2]-v1[2])
|
|
sv.setVelocity([vx,vy,vz])
|
|
|
|
def trimOrbit(self,startTime,stopTime):
|
|
"""Trim the list of state vectors to encompass the time span [startTime:stopTime]"""
|
|
|
|
newOrbit = Orbit()
|
|
newOrbit.configure()
|
|
newOrbit.setOrbitSource('ODR')
|
|
newOrbit.setReferenceFrame('ECR')
|
|
for sv in self._ephemeris:
|
|
if ((sv.getTime() > startTime) and (sv.getTime() < stopTime)):
|
|
newOrbit.addStateVector(sv)
|
|
|
|
return newOrbit
|
|
|
|
def getEphemeris(self):
|
|
return self._ephemeris
|
|
|
|
def _convertToXYZ(self,latitude,longitude,height):
|
|
# The latitude, longitude and height are referenced to the center of mass of the satellite above the GRS80 ellipsoid
|
|
xyz = self.grs80.llh_to_xyz([latitude,longitude,height])
|
|
return xyz
|
|
|
|
def _utcSecondsToDatetime(self,seconds):
|
|
"""All of the ODR records are in UTC seconds from 1 Jan. 1985"""
|
|
dataTime = datetime.datetime(year=1985,month=1,day=1)
|
|
dataTime = dataTime + datetime.timedelta(seconds=seconds)
|
|
return dataTime
|
|
|
|
class Arclist(object):
|
|
"""A class for parsing the ODR arclist file"""
|
|
|
|
def __init__(self,file=None):
|
|
self.file = file
|
|
self.arclist = []
|
|
|
|
def parse(self):
|
|
begin = False
|
|
fp = open(self.file)
|
|
for line in fp.readlines():
|
|
if (begin):
|
|
arc = self.parseLine(line)
|
|
self.arclist.append(arc)
|
|
# I should pre-compile this regex to speed-up the execution
|
|
if (re.search('^Arc#',line)):
|
|
begin=True
|
|
|
|
fp.close()
|
|
|
|
def parseLine(self,line):
|
|
arc = Arc()
|
|
arc.number = line[0:3] # Arc number
|
|
arc.start = datetime.datetime.strptime(line[5:17],'%y%m%d %H:%M') # Starting time for the arc
|
|
arc.stop = datetime.datetime.strptime(line[20:32],'%y%m%d %H:%M') # End time for the arc
|
|
arc.slrResidual = line[34:38] # Satellite laser ranging residual in cm
|
|
arc.crossOver = line[39:43]
|
|
arc.altimeter = line[45:49]
|
|
arc.repeat = line[51:57] # Repeat cycle in days
|
|
arc.version = line[58:61] # Version number
|
|
arc.precise = datetime.datetime.strptime(line[63:78],'%y%m%d %H:%M:%S') # Starting time of the precise segment of the arc
|
|
|
|
return arc
|
|
|
|
def getArc(self,time):
|
|
"""Given a datetime object, determine the first arc number that contains precise ephemeris"""
|
|
inRange = []
|
|
# Make a list containing all of the
|
|
# arcs that span <code>time</code>
|
|
for arc in self.arclist:
|
|
if (arc.inRange(time)):
|
|
inRange.append(arc)
|
|
|
|
# Find the arc that contains the <code>time</code>
|
|
# in the "precise" region of the arc
|
|
for arc in inRange:
|
|
if (time >= arc.precise):
|
|
return arc.number
|
|
|
|
return None
|
|
|
|
def getOrbitFile(self,time):
|
|
number = self.getArc(time)
|
|
return "ODR." + number
|
|
|
|
|
|
class Arc(object):
|
|
"""A class representing an orbital arc segment"""
|
|
|
|
def __init__(self):
|
|
self.number = None
|
|
self._start = None
|
|
self._stop = None
|
|
self.slrResidual = None
|
|
self.xover = None
|
|
self.altim = None
|
|
self.repeatCycle = None
|
|
self.version = None
|
|
self._precise = None
|
|
|
|
def getStart(self):
|
|
return self._start
|
|
|
|
@type_check(datetime.datetime)
|
|
def setStart(self,start):
|
|
self._start = start
|
|
pass
|
|
|
|
def getStop(self):
|
|
return self._stop
|
|
|
|
@type_check(datetime.datetime)
|
|
def setStop(self,stop):
|
|
self._stop = stop
|
|
pass
|
|
|
|
def getPrecise(self):
|
|
return self._precise
|
|
|
|
@type_check(datetime.datetime)
|
|
def setPrecise(self, precise):
|
|
self._precise = precise
|
|
pass
|
|
|
|
def inRange(self,time):
|
|
"""Determine whether a time stamp lies within the start and stop times"""
|
|
return self._start <= time <= self._stop
|
|
|
|
start = property(fget=getStart,fset=setStart)
|
|
stop = property(fget=getStop,fset=setStop)
|
|
precise = property(fget=getPrecise,fset=setPrecise)
|
|
pass
|
|
|
|
|
|
|
|
|