#!/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 time
for arc in self.arclist:
if (arc.inRange(time)):
inRange.append(arc)
# Find the arc that contains the time
# 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