Source code for skplatform.platform.platform
from typing import List, Union, Tuple
import numpy as np
from .optical_geometry import OpticalGeometry
from .rotationmatrix import RotationMatrix
from .platform_pointing import PlatformPointing
from .positionandorientationarray import PositionAndOrientationArray
from .platformlocator import PlatformLocation
from ..orientation_techniques import OrientationTechniques
# -----------------------------------------------------------------------------
# class Platform
# -----------------------------------------------------------------------------
[docs]
class Platform():
"""
The purpose of the Platform class is to capture the details of the physical platform carrying an instrument. Common
examples of platforms used in atmospheric research are spacecraft, aircraft, balloons and ground-based sites. There
are a myriad of details surrounding real platforms but the Platform class's only concern is to generate a list
of instrument *time*, *location* and rotation matrices that properly orient :ref:`icf` for each simulated measurement.
This list of platform state information is internally generated and then passed onto the next stage of analysis or retrieval.
The general form of usage is to perform the following steps,
#. Optional. Specify how an instrument is mounted on the platform. This defines the :ref:`icf`.
#. Specify the universal times for a set of measurements
#. Specify the position of the platform for a set of measurements using a variety of positioning techniques, see :ref:`positioning_technique` and :meth:`~.add_measurement_set`
#. Specify the orientation of the platform for a set of measurements using a variety of orientation techniques, see :ref:`pointing_technique` and :meth:`~.add_measurement_set`.
#. The orientations define the :ref:`pcf` and the positions define the :ref:`gcf`.
#. Create the internal :class:`~.PositionAndOrientationArray` for the measurement set, see :meth:`~.make_observation_policy`.
#. Convert the :class:`~.PositionAndOrientationArray` to arrays of position and instrument look vectors suitable for retrieval code.
"""
# -----------------------------------------------------------------------------
# __init__
# -----------------------------------------------------------------------------
[docs]
def __init__(self, observation_policy: PositionAndOrientationArray = None, platform_locator: PlatformLocation = None):
"""
Parameters:
platform_locator: PlatformLocation
Default None. This optional parameter can be used to add a platform location object such as a satellite,
aircraft or ground site.
observation_policy: PositionAndOrientationArray
Default None. This optional parameter can use an existing :ref:`observationpolicy_class` object rather than
create a new empty instance.
"""
# super().__init__()
self._platform_pointing: PlatformPointing = PlatformPointing()
self._platform_location: PlatformLocation = platform_locator
self._orientationtechniques: OrientationTechniques = OrientationTechniques()
self._position_and_orientation_array: PositionAndOrientationArray = observation_policy if observation_policy is not None else PositionAndOrientationArray()
# ------------------------------------------------------------------------------
# platform_pointing
# ------------------------------------------------------------------------------
@property
def platform_pointing(self) -> PlatformPointing:
"""
Gets the internal :class:`~.PlatformPointing` object. This object manages all the rotation matrices used to
transform between various frames.
"""
return self._platform_pointing
# ------------------------------------------------------------------------------
# platform_locator
# ------------------------------------------------------------------------------
@property
def platform_locator(self) -> PlatformLocation:
"""
Sets and gets the internal :class:`~.PlatformLocation` object. This field is set to None by default but can
be set to a valid class if the user wishes to use a specialized class to get the platform position or orientation
as a function of time.
"""
return self._platform_location
@platform_locator.setter
def platform_locator(self, value):
self._platform_location = value
# ------------------------------------------------------------------------------
# observation_policy
# ------------------------------------------------------------------------------
@property
def observation_policy(self) -> PositionAndOrientationArray:
"""
Returns the current internal :ref:`observationpolicy_class` object.
Returns:
PositionAndOrientationArray
Returns the current internal :ref:`observationpolicy_class` object.
"""
if (self._position_and_orientation_array is None) or (self._orientationtechniques._isdirty):
self.make_position_and_orientation_array()
return self._position_and_orientation_array
# ------------------------------------------------------------------------------
# platform_ecef_positions
# ------------------------------------------------------------------------------
@property
def platform_ecef_positions(self) -> np.ndarray:
"""
Returns the position of the platform for each RT calculation
Returns:
np.ndarray(3,N)
The X,Y,Z location of the platform for each RT calculation. The coordinates are in meters form the center of the Earth,
"""
return self._position_and_orientation_array.ecef_positions()
# ------------------------------------------------------------------------------
# icf_to_ecef_rotation_matrices
# ------------------------------------------------------------------------------
@property
def icf_to_ecef_rotation_matrices(self) -> List[RotationMatrix]:
"""
Returns the platform rotation matrices, one matrix for each RT calculation
Returns:
np.ndarray(3,N)
The X,Y,Z icf_to_ecef_rotation_matrices of the platform.
"""
return self._position_and_orientation_array.icf_to_ecef_rotation_matrix()
# ------------------------------------------------------------------------------
# num_exposures
# ------------------------------------------------------------------------------
@property
def numsamples(self) -> int:
"""
Returns the number of samples/exposures in the current observation set
Returns:
int
The number of samples in the current observation set
"""
return self.observation_policy.numsamples_in_observationset()
# ------------------------------------------------------------------------------
# add_measurement_set
# ------------------------------------------------------------------------------
[docs]
def add_measurement_set(self, utc, platform_position, platform_orientation, icf_orientation=None):
"""
Adds a set of *N* measurements definitions to the internal list of measurement sets. An overview of position and
orientation techniques is given in :ref:`platforms_model`.
Parameters:
utc:Array, sequence or scalar of string, float, datetime or datetime64
The set of universal times for the set of measurements. The array should be an array or sequence of any type that can be
coerced by package sktimeutils into a numpy array (N,) of datetime64. This includes strings, datetime, datetime64. Floating
point values are also converted and are assumed to represent modified julian date. The number of universal time values
defines the number of measurements in this set unless it is a scalar. In this case the time value is duplicated into an array with
the same number of measurements as parameter *observer_positions*. All ali_elements of the array should be of the same
type and should represent universal time. Be wary of using datetime objects with explicit time-zones.
observer_positions: Tuple[str, sequence]
A two element tuple. The first element specifies the :ref:`positioning_technique` to be used to position the
platform. The second element of the tuple is an array of arrays of float. The array of arrays can be any Python, list of lists, tuples or arrays
that can be coerced into a two dimensional array of shape **(N, numparams)** where *N* is the number of
measurements and must match the size of the *utc* parameter parameters and *numparams* is the number of parameters
required by the chosen :ref:`positioning_technique`. The second element of the tuple can be dropped if the *positioning technique*
is *from_platform*.
platform_orientation:
A three element tuple. The first element of the tuple is a string that specifies the :ref:`pointing_technique`. The second element
of the tuple specifies :ref:`rollcontrol` and the third element is an array of arrays of float. The array of arrays can be any Python, list of lists, tuples or arrays
that can be coerced into a two dimensional array of shape **(N, numparams)** where *N* is the number of
measurements and must match the size of the *utc* parameter and *numparams* is the number of parameters
required by the chosen :ref:`pointing_technique`
instrument_internal_rotation:
Optional. An array that specifies the internal rotation of the instrument within the :ref:`icf`. This is intended to
provide support for tilting mirrors and turntables attached to the instrument that redirect the instrument boresight independently
of the platform. The array specifies the azimuth, elevation and roll of the instrument boresight in the :ref:`icf`.
The array is a sequence or array that can be sensibly coerced into an array of size (N,2) or (N,3) where N is the number of measurements.
N can be 1 in which case the array size is broadcast to match the number of measurements inferred from the other parameters. Elements [:,0] is the azimuth in degrees of the
instrument boresight in the instrument control frame, left handed rotation around :math:`\\hat{z}_{ICF}`. Elements [:,1] are the elevation in
degrees of the instrument boresight in the instrument control frame, left handed rotation around the rotated :math:`\\hat{y}_{ICF}` axis.
Elements[:,2], which are are optional, are the roll of the instrument boresight in degrees, right handed rotation around the rotated :math:`\\hat{x}_{ICF}` axis.
The roll defaults to 0.0 if not supplied.
"""
self._orientationtechniques.add_measurement_set(utc, platform_position, platform_orientation, icf_orientation=icf_orientation)
# ------------------------------------------------------------------------------
# make_observation_policy
# ------------------------------------------------------------------------------
[docs]
def make_position_and_orientation_array(self) -> PositionAndOrientationArray:
"""
Takes all the measurements from previous calls to :meth:`~.Platform.add_measurement_set` and converts them into
a list of measurements stored inside the internal instance of :class:`~.PositionAndOrientationArray` object. This list of
measurements can be converted to other formats, such as **OpticalGeometry** required by other parts of the
``skretrieval`` package. This method clears all the measurements that have been previously added.
"""
self._position_and_orientation_array.clear()
self._orientationtechniques.make_observation_set(self)
return self._position_and_orientation_array
# ------------------------------------------------------------------------------
# make_optical_geometry
# ------------------------------------------------------------------------------
[docs]
def make_optical_geometry(self) -> List[OpticalGeometry]:
"""
Takes all the measurements from previous calls to :meth:`~.Platform.add_measurement_set` and returns them as
a list of OpticalGeometry values which can be used by various retrievals. The internal :class:`~.PositionAndOrientationArray` is
also created at this time using method :meth:`~Platform.make_observation_policy`.
"""
position_and_orientation_array = self.make_position_and_orientation_array()
return position_and_orientation_array.to_optical_geometry()
# -----------------------------------------------------------------------------
# make_ecef_position_and_lookvector
# -----------------------------------------------------------------------------
def make_ecef_position_and_lookvector(self, icf_lookvectors=None, one_to_one: bool=False) -> Tuple[np.ndarray, np.ndarray, np.ndarray]:
position_and_orientation_array = self.make_position_and_orientation_array()
return position_and_orientation_array.to_ecef_position_and_look(icf_lookvectors=icf_lookvectors, one_to_one=one_to_one)
# -----------------------------------------------------------------------------
# def add_current_state(self )->int:
# -----------------------------------------------------------------------------
[docs]
def add_current_platform_state(self) -> int:
"""
Fetches the current platform position and orientation and uses this as a new sample in the observation
policy.
Returns:
int
The number of samples in the current observation policy
"""
return self._position_and_orientation_array.add_current_state(self._platform_pointing)
# -----------------------------------------------------------------------------
# clear_states(self):
# -----------------------------------------------------------------------------
[docs]
def clear_states(self):
"""
Clears all of the internally cached measurement states. This should be called when
re-using a platform object to create a new measurement set.
"""
self._orientationtechniques.clear()
self._position_and_orientation_array.clear()
# ------------------------------------------------------------------------------
# icf_to_ecef
# ------------------------------------------------------------------------------
[docs]
def icf_to_ecef(self, los_icf: np.ndarray) -> np.ndarray:
"""
Returns the lines of sight in geographic geocentric ECEF coordinates of the lines of sight specified
in the instrument control frame.
Parameters:
los_icf : np.ndarray( 3,Nlos)
A 2-D array of N unit vectors expressed in the instrument control frame
Returns:
np.ndarray (3,Nlos, Ntime)
A numpy array is returned with the 3 element geographic, geocentric, line of sight unit vector for each
input lines of sight and each time in the current observation set.
"""
Nlos = los_icf.shape[1]
obs = self.observation_policy # Get the observation set used for this back_end_radiance calculation # Get the number of instantaneous lines of sight from the second dimension of the array. LOS are specified in the instrument control frame
Nt = obs.numsamples_in_observationset() # Get the number of exposures/samples in the observation set
rot = obs.icf_to_ecef_rotation_matrix() # Copy the rotation matrices for each sample in the observation set.
all_los = np.full((3, Nlos, Nt), np.nan) # Create an array to hold the lines of sight for all instantaneous directions and all times
for i in range(Nt): # for each time
R = rot[i] # Get the platform rotation matrix that converts ICF vectors to ECEF vectors
instantaneouslos = R.R @ los_icf # Convert all the instrument control frame instantaneous lines of sight for this sample to geocentric geographic lines of sight
all_los[:, :, i] = instantaneouslos # and save the geo unit vectors in the array.
return all_los