1090 lines
35 KiB
Python
1090 lines
35 KiB
Python
#
|
|
# Author: Walter Szeliga
|
|
# Copyright 2010, by the California Institute of Technology. ALL RIGHTS
|
|
# RESERVED. United States Government Sponsorship acknowledged. Any commercial
|
|
# use must be negotiated with the Office of Technology Transfer at the
|
|
# California Institute of Technology.
|
|
#
|
|
# This software may be subject to U.S. export control laws. By accepting this
|
|
# software, the user agrees to comply with all applicable U.S. export laws and
|
|
# regulations. User has the responsibility to obtain export licenses, or other
|
|
# export authority as may be required before exporting such information to
|
|
# foreign countries or providing access to foreign persons.
|
|
#
|
|
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
import datetime
|
|
import numpy as np
|
|
import logging
|
|
import operator
|
|
from functools import reduce
|
|
#from iscesys.DateTimeUtil.DateTimeUtil import DateTimeUtil as DTU
|
|
from iscesys import DateTimeUtil as DTU
|
|
from iscesys.Traits.Datetime import datetimeType
|
|
from iscesys.Component.Component import Component
|
|
from isceobj.Util.decorators import type_check, pickled, logged
|
|
|
|
# This class stores platform position and velocity information.
|
|
POSITION = Component.Parameter(
|
|
'_position',
|
|
public_name='POSITION',
|
|
default=[],
|
|
container=list,
|
|
type=float,
|
|
mandatory=True,
|
|
doc=''
|
|
)
|
|
|
|
|
|
TIME = Component.Parameter(
|
|
'_time',
|
|
public_name='TIME',
|
|
default=[],
|
|
type=datetimeType,
|
|
mandatory=True,
|
|
doc=''
|
|
)
|
|
|
|
|
|
VELOCITY = Component.Parameter(
|
|
'_velocity',
|
|
public_name='VELOCITY',
|
|
default=[],
|
|
container=list,
|
|
type=float,
|
|
mandatory=True,
|
|
doc=''
|
|
)
|
|
|
|
class StateVector(Component):
|
|
|
|
parameter_list = (POSITION,
|
|
TIME,
|
|
VELOCITY
|
|
)
|
|
family = 'statevector'
|
|
def __init__(self,family = None, name = None, time=None, position=None, velocity=None):
|
|
super().__init__(
|
|
family=family if family else self.__class__.family, name=name)
|
|
super(StateVector, self).__init__()
|
|
self._time = time
|
|
self._position = position or []
|
|
self._velocity = velocity or []
|
|
return None
|
|
def __iter__(self):
|
|
return self
|
|
|
|
@type_check(datetime.datetime)
|
|
def setTime(self, time):
|
|
self._time = time
|
|
pass
|
|
|
|
def getTime(self):
|
|
return self._time
|
|
|
|
def setPosition(self, position):
|
|
self._position = position
|
|
|
|
def getPosition(self):
|
|
return self._position
|
|
|
|
def setVelocity(self, velocity):
|
|
self._velocity = velocity
|
|
|
|
def getVelocity(self):
|
|
return self._velocity
|
|
|
|
def getScalarVelocity(self):
|
|
"""Calculate the scalar velocity M{sqrt(vx^2 + vy^2 + vz^2)}.
|
|
@rtype: float
|
|
@return: the scalar velocity
|
|
"""
|
|
return reduce(operator.add, [item**2 for item in self.velocity])**0.5
|
|
|
|
def calculateHeight(self, ellipsoid):
|
|
"""Calculate the height above the provided ellipsoid.
|
|
@type ellipsoid: Ellipsoid
|
|
@param ellipsoid: an ellipsoid
|
|
@rtype: float
|
|
@return: the height above the ellipsoid
|
|
"""
|
|
print("Orbit.calculateHeight: self.position = ", self.position)
|
|
print("Orbit.calculateHeight: ellipsoid.a, ellipsoid.e2 = ",
|
|
ellipsoid.a, ellipsoid.e2)
|
|
lat, lon, height = ellipsoid.xyz_to_llh(self.position)
|
|
return height
|
|
|
|
def __lt__(self, other):
|
|
return self.time < other.time
|
|
def __gt__(self, other):
|
|
return self.time > other.time
|
|
def __cmp__(self, other):
|
|
return (self.time>other.time) - (self.time<other.time)
|
|
# def __eq__(self, other):
|
|
# return self._time == other._time
|
|
def __eq__(self, other):
|
|
return ((self._time == other._time) and
|
|
(self._position == other._position) and
|
|
(self._velocity == other._velocity))
|
|
|
|
def __str__(self):
|
|
retstr = "Time: %s\n"
|
|
retlst = (self._time,)
|
|
retstr += "Position: %s\n"
|
|
retlst += (self._position,)
|
|
retstr += "Velocity: %s\n"
|
|
retlst += (self._velocity,)
|
|
return retstr % retlst
|
|
|
|
time = property(getTime,setTime)
|
|
position = property(getPosition,setPosition)
|
|
velocity = property(getVelocity,setVelocity)
|
|
pass
|
|
|
|
STATE_VECTOR = Component.Facility('stateVector',
|
|
public_name='STATE_VECTOR',
|
|
module='isceobj.Orbit.Orbit',
|
|
factory='StateVector',
|
|
doc='State Vector properties used by STATE_VECTORS'
|
|
)
|
|
STATE_VECTORS = Component.Facility('_stateVectors',
|
|
public_name='STATE_VECTORS',
|
|
module='iscesys.Component',
|
|
factory='createTraitSeq',
|
|
args=('statevector',),
|
|
mandatory=False,
|
|
doc='Testing Trait Sequence'
|
|
)
|
|
ORBIT_QUALITY = Component.Parameter('_orbitQuality',
|
|
public_name='ORBIT_QUALITY',
|
|
default = '',
|
|
type=str,
|
|
mandatory=False,
|
|
doc="Orbit quality"
|
|
)
|
|
ORBIT_SOURCE = Component.Parameter('_orbitSource',
|
|
public_name='ORBIT_SOURCE',
|
|
default = '',
|
|
type=str,
|
|
mandatory=False,
|
|
doc="Orbit source"
|
|
)
|
|
ORBIT_REFERENCE_FRAME = Component.Parameter('_referenceFrame',
|
|
public_name='ORBIT_REFERENCE_FRAME',
|
|
default = '',
|
|
type=str,
|
|
mandatory=False,
|
|
doc="Orbit reference frame"
|
|
)
|
|
MIN_TIME = Component.Parameter('_minTime',
|
|
public_name = 'MIN_TIME',
|
|
default=datetime.datetime(year=datetime.MAXYEAR,month=12,day=31),
|
|
type=datetimeType,
|
|
mandatory=True,
|
|
doc=''
|
|
)
|
|
MAX_TIME = Component.Parameter('_maxTime',
|
|
public_name = 'MAX_TIME',
|
|
default=datetime.datetime(year=datetime.MINYEAR,month=1,day=1),
|
|
type=datetimeType,
|
|
mandatory=True,
|
|
doc=''
|
|
)
|
|
|
|
|
|
##
|
|
# This class encapsulates orbital information\n
|
|
# The Orbit class consists of a list of \c StateVector objects
|
|
# and provides an iterator over this list.
|
|
@pickled
|
|
class Orbit(Component):
|
|
|
|
|
|
'''
|
|
REMOVE completely once conversion to Configurable is done
|
|
dictionaryOfVariables = {'STATE_VECTORS':
|
|
['_stateVectors',float, 'mandatory'],
|
|
'ORBIT_QUALITY': ['_orbitQuality',str,'optional'],
|
|
'ORBIT_SOURCE': ['_orbitSource',str, 'optional'],
|
|
'ORBIT_REFERENCE_FRAME':
|
|
['_referenceFrame',str,'optional']
|
|
}
|
|
'''
|
|
logging_name = "isce.Orbit"
|
|
|
|
# _minTime = datetime.datetime(year=datetime.MAXYEAR,month=12,day=31)
|
|
# _maxTime = datetime.datetime(year=datetime.MINYEAR,month=1,day=1)
|
|
|
|
family = "orbit"
|
|
parameter_list = (
|
|
ORBIT_QUALITY,
|
|
ORBIT_SOURCE,
|
|
ORBIT_REFERENCE_FRAME,
|
|
MIN_TIME,
|
|
MAX_TIME,
|
|
)
|
|
facility_list = (STATE_VECTORS,)
|
|
|
|
@logged
|
|
def __init__(self,family = None, name = None, source=None, quality=None, stateVectors=None):
|
|
super().__init__(family=family if family else self.__class__.family,
|
|
name=name)
|
|
self.configure()
|
|
self._last = 0
|
|
self._orbitQuality = quality or None
|
|
self._orbitSource = source or None
|
|
self._referenceFrame = None
|
|
self._stateVectors.configure()
|
|
#self._stateVectors = stateVectors or []
|
|
self._cpStateVectors = []
|
|
type(self._stateVectors)
|
|
return None
|
|
|
|
#since the dump works only with primitives, convert the self._stateVectors to a list of lists
|
|
#instead of a list of state vectors
|
|
#def dump(self,filename):
|
|
# self.adaptToRender()
|
|
# super(Orbit,self).dump(filename)
|
|
# #restore in original format
|
|
# self.restoreAfterRendering()
|
|
|
|
#def load(self,filename):
|
|
# import copy
|
|
# import datetime
|
|
# # when loaded up the stateVectors are just list of lists and the starting time is
|
|
# # ins str format
|
|
# super(Orbit,self).load(filename)
|
|
# #make a copy
|
|
# cpStateVectors = copy.deepcopy(self._stateVectors)
|
|
# #convert the str into datetime
|
|
# cpStateVectors[3] = datetime.datetime.strptime(cpStateVectors[3],'%Y-%m-%dT%H:%M:%S.%f')
|
|
# #pack the orbit into stateVectors
|
|
# self._packOrbit(cpStateVectors[0], cpStateVectors[1], cpStateVectors[2], cpStateVectors[3])
|
|
|
|
def adaptToRender(self):
|
|
import copy
|
|
# make a copy of the stateVectors to restore it after dumping
|
|
self._cpStateVectors = copy.deepcopy(self._stateVectors)
|
|
|
|
#self._stateVectors = list(self._unpackOrbit())
|
|
#self._stateVectors[3] = self._stateVectors[3].strftime('%Y-%m-%dT%H:%M:%S.%f')
|
|
|
|
def restoreAfterRendering(self):
|
|
self._stateVectors = self._cpStateVectors
|
|
|
|
def __iter__(self):
|
|
return self
|
|
|
|
def __len__(self):
|
|
return len(self.stateVectors)
|
|
|
|
def __getitem__(self, index):
|
|
return self.stateVectors[index]
|
|
|
|
def __contains__(self, time):
|
|
return self._inRange(time)
|
|
|
|
@property
|
|
def stateVectors(self):
|
|
return self._stateVectors
|
|
|
|
@property
|
|
def maxTime(self):
|
|
return self._maxTime
|
|
|
|
@maxTime.setter
|
|
def maxTime(self, time):
|
|
self._maxTime = time
|
|
|
|
@property
|
|
def minTime(self):
|
|
return self._minTime
|
|
|
|
@minTime.setter
|
|
def minTime(self, time):
|
|
self._minTime = time
|
|
|
|
|
|
def setOrbitQuality(self,qual):
|
|
self._orbitQuality = qual
|
|
|
|
def getOrbitQuality(self):
|
|
return self._orbitQuality
|
|
|
|
def setOrbitSource(self,source):
|
|
self._orbitSource = source
|
|
|
|
def getOrbitSource(self):
|
|
return self._orbitSource
|
|
|
|
def setReferenceFrame(self,ref):
|
|
self._referenceFrame = ref
|
|
|
|
def getReferenceFrame(self):
|
|
return self._referenceFrame
|
|
|
|
@type_check(StateVector)
|
|
def addStateVector(self, vec):
|
|
"""
|
|
Add a state vector to the orbit.
|
|
@type vec: Orbit.StateVector
|
|
@param vec: a state vector
|
|
@raise TypeError: if vec is not of type StateVector
|
|
"""
|
|
pos = vec.getPosition()
|
|
import math
|
|
posmag = math.sqrt(sum([x*x for x in pos]))
|
|
if posmag > 1.e10:
|
|
print("Orbit.addStateVector: vec = ", vec)
|
|
import sys
|
|
sys.exit(0)
|
|
|
|
vtime = vec.getTime()
|
|
if vtime > self.maxTime:
|
|
self._stateVectors.append(vec)
|
|
else:
|
|
for ind, sv in enumerate(self._stateVectors):
|
|
if sv.time > vtime:
|
|
break
|
|
|
|
self._stateVectors.insert(ind, vec)
|
|
|
|
# Reset the minimum and maximum time bounds if necessary
|
|
if vec.time < self.minTime: self.minTime = vec._time
|
|
if vec.time > self.maxTime: self.maxTime = vec._time
|
|
|
|
def __next__(self):
|
|
if self._last < len(self):
|
|
next = self._stateVectors[self._last]
|
|
self._last += 1
|
|
return next
|
|
else:
|
|
self._last = 0 # This is so that we can restart iteration
|
|
raise StopIteration()
|
|
|
|
|
|
def interpolateOrbit(self, time, method='linear'):
|
|
"""Interpolate the state vector of the orbit at a given time.
|
|
@type time: datetime.datetime
|
|
@param time: the time at which interpolation is desired
|
|
@type method: string
|
|
@param method: the interpolation method, valid values are 'linear',
|
|
'legendre' and 'hermite'
|
|
@rtype: Orbit.StateVector
|
|
@return: a state vector at the desired time otherwise None
|
|
@raises ValueError: if the time lies outside of the time spanned by
|
|
the orbit
|
|
@raises NotImplementedError: if the desired interpolation method
|
|
cannot be decoded
|
|
"""
|
|
if time not in self:
|
|
raise ValueError(
|
|
"Time stamp (%s) falls outside of the interpolation interval [%s:%s]" %
|
|
(time,self.minTime,self.maxTime)
|
|
)
|
|
|
|
if method == 'linear':
|
|
newSV = self._linearOrbitInterpolation(time)
|
|
elif method == 'legendre':
|
|
newSV = self._legendreOrbitInterpolation(time)
|
|
elif method == 'hermite':
|
|
newSV = self._hermiteOrbitInterpolation(time)
|
|
else:
|
|
raise NotImplementedError(
|
|
"Orbit interpolation type %s, is not implemented" % method
|
|
)
|
|
return newSV
|
|
|
|
## Isn't orbit redundant? -compute the method based on name
|
|
def interpolate(self, time, method='linear'):
|
|
if time not in self:
|
|
raise ValueError("Time stamp (%s) falls outside of the interpolation interval [%s:%s]"
|
|
% (time,self.minTime,self.maxTime))
|
|
try:
|
|
return getattr(self, '_'+method+'OrbitInterpolation')(time)
|
|
except AttributeError:
|
|
pass
|
|
raise NotImplementedError(
|
|
"Orbit interpolation type %s, is not implemented" % method
|
|
)
|
|
|
|
interpolateOrbit = interpolate
|
|
|
|
def _linearOrbitInterpolation(self,time):
|
|
"""
|
|
Linearly interpolate a state vector. This method returns None if
|
|
there are fewer than 2 state vectors in the orbit.
|
|
@type time: datetime.datetime
|
|
@param time: the time at which to interpolate a state vector
|
|
@rtype: Orbit.StateVector
|
|
@return: the state vector at the desired time
|
|
"""
|
|
if len(self) < 2:
|
|
self.logger.error("Fewer than 2 state vectors present in orbit, cannot interpolate")
|
|
return None
|
|
|
|
position = [0.0 for i in range(3)]
|
|
velocity = [0.0 for i in range(3)]
|
|
newOrbit = self.selectStateVectors(time, 1, 1)
|
|
obsTime, obsPos, obsVel, offset = newOrbit.to_tuple(
|
|
relativeTo=self.minTime
|
|
)
|
|
dtime = float(DTU.timeDeltaToSeconds(time-offset))
|
|
|
|
for i in range(3):
|
|
position[i] = (obsPos[0][i] +
|
|
(obsPos[1][i]-obsPos[0][i])*
|
|
(dtime-obsTime[0])/(obsTime[1]-obsTime[0]))
|
|
velocity[i] = (obsVel[0][i] +
|
|
(obsVel[1][i]-obsVel[0][i])*
|
|
(dtime-obsTime[0])/(obsTime[1]-obsTime[0]))
|
|
|
|
"""
|
|
for sv1 in self.stateVectors:
|
|
tmp=1.0
|
|
for sv2 in self.stateVectors:
|
|
if sv1.time == sv2.time:
|
|
continue
|
|
numerator = float(DTU.timeDeltaToSeconds(sv2.time-time))
|
|
denominator = float(
|
|
DTU.timeDeltaToSeconds(sv2.time - sv1.time)
|
|
)
|
|
tmp = tmp*(numerator)/(denominator)
|
|
for i in range(3):
|
|
position[i] = position[i] + sv1.getPosition()[i]*tmp
|
|
velocity[i] = velocity[i] + sv1.getVelocity()[i]*tmp
|
|
"""
|
|
return StateVector(time=time, position=position, velocity=velocity)
|
|
|
|
def _legendreOrbitInterpolation(self,time):
|
|
"""Interpolate a state vector using an 8th order Legendre polynomial.
|
|
This method returns None if there are fewer than 9 state vectors in
|
|
the orbit.
|
|
@type time: datetime.datetime
|
|
@param time: the time at which to interpolate a state vector
|
|
@rtype: Orbit.StateVector
|
|
@return: the state vector at the desired time
|
|
"""
|
|
if len(self) < 9:
|
|
self.logger.error(
|
|
"Fewer than 9 state vectors present in orbit, cannot interpolate"
|
|
)
|
|
return None
|
|
|
|
seq = [4,5,3,6,2,7,1,8]
|
|
found = False
|
|
|
|
for ind in seq:
|
|
rind = 9 - ind
|
|
try:
|
|
newOrbit = self.selectStateVectors(time, 4, 5)
|
|
found = True
|
|
except LookupError as e:
|
|
pass
|
|
|
|
if found:
|
|
break
|
|
|
|
if not found:
|
|
raise Exception('Could not find state vectors before/after for interpolation')
|
|
|
|
|
|
obsTime, obsPos, obsVel, offset = newOrbit.to_tuple(
|
|
relativeTo=self.minTime
|
|
)
|
|
t = DTU.timeDeltaToSeconds(time-self.minTime)
|
|
t0 = DTU.timeDeltaToSeconds(newOrbit.minTime-self.minTime)
|
|
tn = DTU.timeDeltaToSeconds(newOrbit.maxTime-self.minTime)
|
|
ansPos = self._legendre8(t0, tn, t, obsPos)
|
|
ansVel = self._legendre8(t0, tn, t, obsVel)
|
|
|
|
return StateVector(time=time, position=ansPos, velocity=ansVel)
|
|
|
|
|
|
|
|
def _legendre8(self,t0,tn,t,v):
|
|
"""Interpolate an orbit using an 8th order Legendre polynomial
|
|
@type t0: float
|
|
@param t0: starting time
|
|
@type tn: float
|
|
@param tn: ending time
|
|
@type t: float
|
|
@param t: time at which vt must be interpolated
|
|
@type v: list
|
|
@param v: 9 consecutive points
|
|
@rtype: float
|
|
@return: interpolated point at time t
|
|
"""
|
|
trel = (t-t0)/(tn-t0)*(len(v)-1)+1
|
|
itrel=max(1,min(int(trel)-4,len(v)-9))+1
|
|
t = trel-itrel
|
|
vt = [0 for i in range(3)]
|
|
kx = 0
|
|
x=t+1
|
|
noemer = [40320,-5040,1440,-720,576,-720,1440,-5040,40320]
|
|
|
|
teller=(x)*(x-1)*(x-2)*(x-3)*(x-4)*(x-5)*(x-6)*(x-7)*(x-8)
|
|
if (teller == 0):
|
|
kx = int(x)
|
|
for i in range(3):
|
|
vt[i] = v[kx][i]
|
|
else:
|
|
for kx in range(9):
|
|
coeff=teller/noemer[kx]/(x-kx)
|
|
for i in range(3):
|
|
vt[i] = vt[i] + coeff*v[kx][i]
|
|
|
|
return vt
|
|
|
|
|
|
def _hermiteOrbitInterpolation(self,time):
|
|
"""
|
|
Interpolate a state vector using Hermite interpolation.
|
|
This method returns None if there are fewer than 4 state
|
|
vectors in the orbit.
|
|
@type time: datetime.datetime
|
|
@param time: the time at which to interpolate a state vector
|
|
@rtype: Orbit.StateVector
|
|
@return: the state vector at the desired time
|
|
"""
|
|
|
|
import os
|
|
from ctypes import c_double, cdll,byref
|
|
orbitHermite = (
|
|
cdll.LoadLibrary(os.path.dirname(__file__)+'/orbitHermite.so')
|
|
)
|
|
|
|
if len(self) < 4:
|
|
self.logger.error(
|
|
"Fewer than 4 state vectors present in orbit, cannot interpolate"
|
|
)
|
|
return None
|
|
|
|
# The Fortran routine assumes that it is getting an array of four
|
|
# state vectors
|
|
try:
|
|
newOrbit = self.selectStateVectors(time, 2, 2)
|
|
except LookupError:
|
|
try:
|
|
newOrbit = self.selectStateVectors(time,1,3)
|
|
except LookupError:
|
|
try:
|
|
newOrbit = self.selectStateVectors(time,3,1)
|
|
except LookupError:
|
|
self.logger.error("Unable to select 2 state vectors before and after "+
|
|
"chosen time %s" % (time))
|
|
return None
|
|
|
|
# For now, assume that time is an array containing the times at which
|
|
# we want to interpolate
|
|
obsTime, obsPos, obsVel,offset = newOrbit.to_tuple(
|
|
relativeTo=self.minTime
|
|
)
|
|
|
|
td = time - self.minTime
|
|
ansTime = DTU.timeDeltaToSeconds(td)
|
|
flatObsPos = [item for sublist in obsPos for item in sublist]
|
|
flatObsVel = [item for sublist in obsVel for item in sublist]
|
|
flatAnsPos= [0.,0.,0.]# list([0.0 for i in range(3)])
|
|
flatAnsVel= [0.,0.,0.]#list([0.0 for i in range(3)])
|
|
obsTime_C = (c_double*len(obsTime))(*obsTime)
|
|
obsPos_C = (c_double*len(flatObsPos))(*flatObsPos)
|
|
obsVel_C = (c_double*len(flatObsVel))(*flatObsVel)
|
|
ansTime_C = c_double(ansTime)
|
|
ansPos_C = (c_double*3)(*flatAnsPos)
|
|
ansVel_C = (c_double*3)(*flatAnsVel)
|
|
|
|
# Use the C wrapper to the fortran Hermite interpolator
|
|
orbitHermite.orbitHermite_C(obsPos_C,
|
|
obsVel_C,
|
|
obsTime_C,
|
|
byref(ansTime_C),
|
|
ansPos_C,
|
|
ansVel_C)
|
|
|
|
return StateVector(time=time, position=ansPos_C[:], velocity=ansVel_C[:])
|
|
|
|
## This need to be public -very confusing since there is an __iter__
|
|
def to_tuple(self, relativeTo=None):
|
|
return self._unpackOrbit(relativeTo=relativeTo)
|
|
|
|
def _unpackOrbit(self, relativeTo=None):
|
|
"""Convert and orbit object into tuple of lists containing time,
|
|
position and velocity.
|
|
@type relativeTo: datetime.datetime
|
|
@param relativeTo: the time with which to reference the unpacked orbit
|
|
@return: a tuple containing a list of time, position, velocity and
|
|
relative time offset
|
|
"""
|
|
time = []
|
|
position = []
|
|
velocity = []
|
|
if relativeTo is None:
|
|
relativeTo = self.minTime
|
|
|
|
for sv in self.stateVectors:
|
|
td = sv.time - relativeTo
|
|
currentTime = ((
|
|
td.microseconds +
|
|
(td.seconds + td.days * 24 * 3600.0) * 10**6) / 10**6
|
|
)
|
|
currentPosition = sv.getPosition()
|
|
currentVelocity = sv.getVelocity()
|
|
time.append(currentTime)
|
|
position.append(currentPosition)
|
|
velocity.append(currentVelocity)
|
|
|
|
return time, position, velocity, relativeTo
|
|
|
|
#def _packOrbit(self,time,position,velocity,relativeTo):
|
|
# self._minTime = relativeTo
|
|
# self._stateVectors = [];
|
|
# for t,p,v in zip(time,position,velocity):
|
|
# sv = StateVector(time=relativeTo + datetime.timedelta(seconds=t),position=p,velocity=v)
|
|
# self.addStateVector(sv)
|
|
|
|
## Why does this version fail ERS and not ALOS?
|
|
def selectStateVectorsBroken(self, time, before, after):
|
|
"""Given a time and a number of before and after state vectors,
|
|
return an Orbit with (before+after) state vectors with reference to
|
|
time.
|
|
@type time: datetime.datetime
|
|
@param time: the reference time for subselection
|
|
@type before: integer
|
|
@param before: the number of state vectors before the chosen time to
|
|
select
|
|
@type after: integer
|
|
@param after: the number of state vectors after the chosen time to
|
|
select
|
|
@rtype: Orbit.Orbit
|
|
@return: an orbit containing (before+after) state vectors relative to
|
|
time
|
|
@raises LookupError: if there are insufficient state vectors in the
|
|
orbit
|
|
"""
|
|
# First, find the index closest to the requested time
|
|
i=0
|
|
while self.stateVectors[i].time <= time:
|
|
i += 1
|
|
beforeIndex = i
|
|
|
|
# Check that we can grab enough data
|
|
if (beforeIndex-before) < 0:
|
|
raise LookupError("Requested index %s is out of bounds" %
|
|
(beforeIndex-before))
|
|
elif (beforeIndex+after) > len(self):
|
|
raise LookupError("Requested index %s is out of bounds" %
|
|
(beforeIndex+after))
|
|
|
|
# Create a new orbit object - filled with goodies.
|
|
return Orbit(source=self.orbitSource,
|
|
quality=self.orbitQuality,
|
|
stateVectors=[
|
|
self[i] for i in range(
|
|
(beforeIndex-before),(beforeIndex+after)
|
|
)])
|
|
|
|
|
|
|
|
def selectStateVectors(self,time,before,after):
|
|
"""
|
|
Given a time and a number of before and after state vectors,
|
|
return an Orbit with (before+after) state vectors with reference to
|
|
time.
|
|
"""
|
|
# First, find the index closest to the requested time
|
|
i=0
|
|
while(self._stateVectors[i].getTime() <= time):
|
|
i += 1
|
|
beforeIndex = i
|
|
|
|
# Check that we can grab enough data
|
|
if ((beforeIndex-before) < 0):
|
|
raise LookupError(
|
|
"Requested index %s is out of bounds" % (beforeIndex-before)
|
|
)
|
|
elif ((beforeIndex+after) > len(self._stateVectors)):
|
|
raise LookupError(
|
|
"Requested index %s is out of bounds" % (beforeIndex+after)
|
|
)
|
|
|
|
# Create a new orbit object
|
|
newOrbit = Orbit(name='neworbit')
|
|
newOrbit.configure()
|
|
# inject dependencies
|
|
newOrbit.setOrbitSource(self.orbitSource)
|
|
newOrbit.setOrbitQuality(self.orbitQuality)
|
|
for i in range((beforeIndex-before),(beforeIndex+after)):
|
|
newOrbit.addStateVector(self[i])
|
|
|
|
return newOrbit
|
|
|
|
|
|
|
|
def trimOrbit(self, startTime, stopTime):
|
|
"""Trim the list of state vectors to encompass the time span
|
|
[startTime:stopTime]
|
|
@type startTime: datetime.datetime
|
|
@param startTime: the desired starting time for the output orbit
|
|
@type stopTime: datetime.datetime
|
|
@param stopTime: the desired stopping time for the output orbit
|
|
@rtype: Orbit.Orbit
|
|
@return: an orbit containing all of the state vectors within the time
|
|
span [startTime:stopTime]
|
|
"""
|
|
|
|
newOrbit = Orbit()
|
|
newOrbit.configure()
|
|
newOrbit.setOrbitSource(self._orbitSource)
|
|
newOrbit.setReferenceFrame(self._referenceFrame)
|
|
for sv in self._stateVectors:
|
|
if startTime < sv.time < stopTime:
|
|
newOrbit.addStateVector(sv)
|
|
|
|
return newOrbit
|
|
|
|
def _inRange(self,time):
|
|
"""Check whether a given time is within the range of values for an
|
|
orbit.
|
|
@type time: datetime.datetime
|
|
@param time: a time
|
|
@rtype: boolean
|
|
@return: True if the time falls within the time span of the orbit,
|
|
otherwise False
|
|
"""
|
|
return self.minTime <= time <= self.maxTime
|
|
|
|
def __str__(self):
|
|
retstr = "Orbit Source: %s\n"
|
|
retlst = (self._orbitSource,)
|
|
retstr += "Orbit Quality: %s\n"
|
|
retlst += (self._orbitQuality,)
|
|
retstr += "Orbit Reference Frame: %s\n"
|
|
retlst += (self._referenceFrame,)
|
|
return retstr % retlst
|
|
|
|
stateVector = property()
|
|
orbitQuality = property(getOrbitQuality, setOrbitQuality)
|
|
orbitSource = property(getOrbitSource, setOrbitSource)
|
|
|
|
pass
|
|
|
|
|
|
def getHeading(self, time=None, spacing=0.5, planet=None):
|
|
'''
|
|
Compute heading around given azimuth time.
|
|
If time is not provided, mid point of orbit is used.
|
|
'''
|
|
|
|
from isceobj.Planet.Planet import Planet
|
|
|
|
if planet is None:
|
|
planet = Planet(pname='Earth')
|
|
|
|
refElp = planet.ellipsoid
|
|
if time is None:
|
|
delta = self.maxTime - self.minTime
|
|
aztime = self.minTime + datetime.timedelta(seconds = 0.5 * delta.total_seconds())
|
|
else:
|
|
aztime = time
|
|
|
|
t1 = aztime - datetime.timedelta(seconds=spacing)
|
|
t2 = aztime + datetime.timedelta(seconds=spacing)
|
|
|
|
vec1 = self.interpolateOrbit(t1, method='hermite')
|
|
vec2 = self.interpolateOrbit(t2, method='hermite')
|
|
|
|
llh1 = refElp.xyz_to_llh(vec1.getPosition())
|
|
llh2 = refElp.xyz_to_llh(vec2.getPosition())
|
|
|
|
#Heading
|
|
hdg = refElp.geo_hdg(llh1, llh2)
|
|
|
|
return np.degrees(hdg)
|
|
|
|
def getENUHeading(self, time=None, planet=None):
|
|
'''
|
|
Compute heading at given azimuth time using single state vector.
|
|
If time is not provided, mid point of orbit is used.
|
|
'''
|
|
|
|
from isceobj.Planet.Planet import Planet
|
|
|
|
if planet is None:
|
|
planet = Planet(pname='Earth')
|
|
|
|
refElp = planet.ellipsoid
|
|
if time is None:
|
|
delta = self.maxTime - self.minTime
|
|
aztime = self.minTime + datetime.timedelta(seconds = 0.5 * delta.total_seconds())
|
|
else:
|
|
aztime = time
|
|
|
|
vec1 = self.interpolateOrbit(aztime, method='hermite')
|
|
llh1 = refElp.xyz_to_llh(vec1.getPosition())
|
|
|
|
enumat = refElp.enubasis(llh1)
|
|
venu = np.dot(enumat.xyz_to_enu, vec1.getVelocity())
|
|
|
|
#Heading
|
|
hdg = np.arctan2(venu[0,0], venu[0,1])
|
|
|
|
return np.degrees(hdg)
|
|
|
|
|
|
def rdr2geo(self, aztime, rng, height=0.,
|
|
doppler = None, wvl = None,
|
|
planet=None, side=-1):
|
|
'''
|
|
Returns point on ground at given height and doppler frequency.
|
|
Never to be used for heavy duty computing.
|
|
'''
|
|
|
|
from isceobj.Planet.Planet import Planet
|
|
|
|
####Setup doppler for the equations
|
|
dopfact = 0.0
|
|
|
|
hdg = self.getENUHeading(time=aztime)
|
|
|
|
sv = self.interpolateOrbit(aztime, method='hermite')
|
|
pos = sv.getPosition()
|
|
vel = sv.getVelocity()
|
|
vmag = np.linalg.norm(vel)
|
|
|
|
if doppler is not None:
|
|
dopfact = doppler(DTU.seconds_since_midnight(aztime), rng) * 0.5 * wvl * rng/vmag
|
|
|
|
if planet is None:
|
|
refElp = Planet(pname='Earth').ellipsoid
|
|
else:
|
|
refElp = planet.ellipsoid
|
|
|
|
###Convert position and velocity to local tangent plane
|
|
satLLH = refElp.xyz_to_llh(pos)
|
|
|
|
refElp.setSCH(satLLH[0], satLLH[1], hdg)
|
|
radius = refElp.pegRadCur
|
|
|
|
#####Setup ortho normal system right below satellite
|
|
satVec = np.array(pos)
|
|
velVec = np.array(vel)
|
|
|
|
###Setup TCN basis
|
|
clat = np.cos(np.radians(satLLH[0]))
|
|
slat = np.sin(np.radians(satLLH[0]))
|
|
clon = np.cos(np.radians(satLLH[1]))
|
|
slon = np.sin(np.radians(satLLH[1]))
|
|
nhat = np.array([-clat*clon, -clat*slon, -slat])
|
|
temp = np.cross(nhat, velVec)
|
|
chat = temp / np.linalg.norm(temp)
|
|
temp = np.cross(chat, nhat)
|
|
that = temp / np.linalg.norm(temp)
|
|
vhat = velVec / np.linalg.norm(velVec)
|
|
|
|
####Solve the range doppler eqns iteratively
|
|
####Initial guess
|
|
zsch = height
|
|
|
|
for ii in range(10):
|
|
|
|
###Near nadir tests
|
|
if (satLLH[2]-zsch) >= rng:
|
|
return None
|
|
|
|
a = (satLLH[2] + radius)
|
|
b = (radius + zsch)
|
|
|
|
costheta = 0.5*(a/rng + rng/a - (b/a)*(b/rng))
|
|
sintheta = np.sqrt(1-costheta*costheta)
|
|
|
|
gamma = rng*costheta
|
|
alpha = dopfact - gamma*np.dot(nhat,vhat)/np.dot(vhat,that)
|
|
beta = -side*np.sqrt(rng*rng*sintheta*sintheta - alpha*alpha)
|
|
|
|
delta = alpha * that + beta * chat + gamma * nhat
|
|
|
|
targVec = satVec + delta
|
|
|
|
targLLH = refElp.xyz_to_llh(list(targVec))
|
|
targXYZ = refElp.llh_to_xyz([targLLH[0], targLLH[1], height])
|
|
targSCH = refElp.xyz_to_sch(targXYZ)
|
|
|
|
zsch = targSCH[2]
|
|
|
|
rdiff = rng - np.linalg.norm(np.array(satVec) - np.array(targXYZ))
|
|
|
|
return targLLH
|
|
|
|
|
|
def rdr2geoNew(self, aztime, rng, height=0.,
|
|
doppler = None, wvl = None,
|
|
planet=None, side=-1):
|
|
'''
|
|
Returns point on ground at given height and doppler frequency.
|
|
Never to be used for heavy duty computing.
|
|
'''
|
|
|
|
from isceobj.Planet.Planet import Planet
|
|
|
|
####Setup doppler for the equations
|
|
dopfact = 0.
|
|
|
|
sv = self.interpolateOrbit(aztime, method='hermite')
|
|
pos = np.array(sv.getPosition())
|
|
vel =np.array( sv.getVelocity())
|
|
vmag = np.linalg.norm(vel)
|
|
|
|
if doppler is not None:
|
|
dopfact = doppler(DTU.seconds_since_midnight(aztime), rng) * 0.5 * wvl * rng/vmag
|
|
|
|
if planet is None:
|
|
refElp = Planet(pname='Earth').ellipsoid
|
|
else:
|
|
refElp = planet.ellipsoid
|
|
|
|
###Convert position and velocity to local tangent plane
|
|
major = refElp.a
|
|
minor = major * np.sqrt(1 - refElp.e2)
|
|
|
|
#####Setup ortho normal system right below satellite
|
|
satDist = np.linalg.norm(pos)
|
|
alpha = 1 / np.linalg.norm(pos/ np.array([major, major, minor]))
|
|
radius = alpha * satDist
|
|
hgt = (1.0 - alpha) * satDist
|
|
|
|
###Setup TCN basis - Geocentric
|
|
nhat = -pos/satDist
|
|
temp = np.cross(nhat, vel)
|
|
chat = temp / np.linalg.norm(temp)
|
|
temp = np.cross(chat, nhat)
|
|
that = temp / np.linalg.norm(temp)
|
|
vhat = vel / vmag
|
|
|
|
####Solve the range doppler eqns iteratively
|
|
####Initial guess
|
|
zsch = height
|
|
|
|
for ii in range(10):
|
|
|
|
###Near nadir tests
|
|
if (hgt-zsch) >= rng:
|
|
return None
|
|
|
|
a = satDist
|
|
b = (radius + zsch)
|
|
|
|
costheta = 0.5*(a/rng + rng/a - (b/a)*(b/rng))
|
|
sintheta = np.sqrt(1-costheta*costheta)
|
|
|
|
gamma = rng*costheta
|
|
alpha = dopfact - gamma*np.dot(nhat,vhat)/np.dot(vhat,that)
|
|
beta = -side*np.sqrt(rng*rng*sintheta*sintheta - alpha*alpha)
|
|
|
|
delta = alpha * that + beta * chat + gamma * nhat
|
|
|
|
targVec = pos + delta
|
|
|
|
targLLH = refElp.xyz_to_llh(list(targVec))
|
|
targXYZ = np.array(refElp.llh_to_xyz([targLLH[0], targLLH[1], height]))
|
|
|
|
zsch = np.linalg.norm(targXYZ) - radius
|
|
|
|
rdiff = rng - np.linalg.norm(pos - targXYZ)
|
|
|
|
return targLLH
|
|
|
|
|
|
####Make rdr2geo same as pointOnGround
|
|
pointOnGround = rdr2geo
|
|
|
|
def geo2rdr(self, llh, side=-1, planet=None,
|
|
doppler=None, wvl=None):
|
|
'''
|
|
Takes a lat, lon, height triplet and returns azimuth time and range.
|
|
Assumes zero doppler for now.
|
|
'''
|
|
|
|
from isceobj.Planet.Planet import Planet
|
|
from isceobj.Util.Poly2D import Poly2D
|
|
if doppler is None:
|
|
doppler = Poly2D()
|
|
doppler.initPoly(azimuthOrder=0, rangeOrder=0, coeffs=[[0.]])
|
|
wvl = 0.0
|
|
|
|
if planet is None:
|
|
refElp = Planet(pname='Earth'). ellipsoid
|
|
else:
|
|
refElp = planet.ellipsoid
|
|
|
|
xyz = refElp.llh_to_xyz(llh)
|
|
|
|
delta = (self.maxTime - self.minTime).total_seconds() * 0.5
|
|
tguess = self.minTime + datetime.timedelta(seconds = delta)
|
|
outOfBounds = False
|
|
for ii in range(51):
|
|
try:
|
|
sv = self.interpolateOrbit(tguess, method='hermite')
|
|
except:
|
|
outOfBounds = True
|
|
break
|
|
|
|
pos = np.array(sv.getPosition())
|
|
vel = np.array(sv.getVelocity())
|
|
|
|
dr = xyz-pos
|
|
rng = np.linalg.norm(dr)
|
|
|
|
dopfact = np.dot(dr,vel)
|
|
fdop = doppler(DTU.seconds_since_midnight(tguess),rng) * wvl * 0.5
|
|
fdopder = (0.5*wvl*doppler(DTU.seconds_since_midnight(tguess),rng+10.0) - fdop) / 10.0
|
|
|
|
fn = dopfact - fdop * rng
|
|
c1 = -np.dot(vel, vel)
|
|
c2 = (fdop/rng + fdopder)
|
|
|
|
fnprime = c1 + c2 * dopfact
|
|
|
|
tguess = tguess - datetime.timedelta(seconds = fn/fnprime)
|
|
|
|
if outOfBounds:
|
|
raise Exception('Interpolation time out of bounds')
|
|
|
|
|
|
return tguess, rng
|
|
|
|
|
|
def exportToC(self):
|
|
from isceobj.Util import combinedlibmodule
|
|
orb = []
|
|
|
|
for sv in self._stateVectors:
|
|
tim = DTU.seconds_since_midnight(sv.getTime())
|
|
pos = sv.getPosition()
|
|
vel = sv.getVelocity()
|
|
|
|
row = [tim] + pos + vel
|
|
orb.append(row)
|
|
|
|
cOrbit = combinedlibmodule.exportOrbitToC(1,orb)
|
|
return cOrbit
|
|
|
|
def importFromC(self, ptr, dateobj):
|
|
from isceobj.Util import combinedlibmodule
|
|
from datetime import timedelta
|
|
|
|
print('Importing from C')
|
|
basis, data = combinedlibmodule.importOrbitFromC(ptr)
|
|
|
|
for row in data:
|
|
sv = StateVector()
|
|
sv.setTime( dateobj + timedelta(seconds = row[0]))
|
|
sv.setPosition(row[1:4])
|
|
sv.setVelocity(row[4:7])
|
|
self.addStateVector(sv)
|
|
|
|
return
|
|
|
|
|
|
def createOrbit():
|
|
return Orbit()
|