#!/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