Source code for etrsitrs.datumtransformation
r'''
The *datumtransformation* module contains the actual transform math,
implemented in the *forward_transform()* and *reverse_transform()*
functions, as well as the *DatumTransformation* class, which manages
rates of change of parameters and wraps the forward- and reverse-
transforms.
'''
from numpy import dot
try:
from etrsitrs.parameterset import ParameterSet
except ImportError:
from parameterset import ParameterSet
[docs]def forward_transform(xyz_m, translate_m, rotation_matrix):
r''' Transform *xyz_m* given a translation vector and a rotation
matrix. Only use *translate_m* and *matrix* from the
*ParameterSet* returned by *propagate_parameters()*. Implements
.. math::
\left(\begin{array}{c} x_B \\ y_B \\ z_B\end{array}\right) =
\left(\begin{array}{c} x_A \\ y_A \\ z_A\end{array}\right) +
\left(\begin{array}{c} T1 \\ T2 \\ T3\end{array}\right) +
\left(\begin{array}{ccc}
D & -R3 & R2 \\
R3 & D & -R1 \\
-R2 & R1 & D
\end{array}\right)
\left(\begin{array}{c} x_A \\ y_A \\ z_A\end{array}\right)
**Parameters**
xyz_m : numpy.array of length 3
The coordinates to transform in meters.
translate_m : numpy.array of length 3
Propagated (T1, T2, T3).
rotation_matrix : numpy.array of shape (3, 3)
The rotation matrix obtained by calling the
*ParameterSet.matrix()* method on the result of
*propagate_parameters()*.
**Returns**
A numpy.array of length 3 with the transformed coordinates.
**Examples**
At epoch 2000.0:
>>> from numpy import array
>>> parameters = ParameterSet(translate_m = array([ 0.0521, 0.0493, -0.0585]),
... term_d = 1.3400e-09,
... rotate_rad = array([ 4.31968990e-09, 2.61314574e-08, -4.22369679e-08]))
>>> onsala_itrf2008 = array([3370658.542, 711877.138, 5349786.952])
>>> onsala_etrf2000 = forward_transform(onsala_itrf2008,
... parameters.translate_m,
... parameters.matrix())
>>> print('%.3f, %.3f, %.3f' % tuple(onsala_etrf2000))
3370658.768, 711877.023, 5349786.816
'''
return xyz_m + translate_m + dot(rotation_matrix, xyz_m)
[docs]def reverse_transform(xyz_m, translate_m, rotation_matrix):
r'''
The opposite of *forward_transform()*. Transform xyz given a
translation vector and a rotation matrix. Only use *translate_m*
and *matrix* from the *ParameterSet* returned by
*propagate_parameters()*. Implements
.. math::
\left(\begin{array}{c} x_A \\ y_A \\ z_A\end{array}\right) =
\left(\begin{array}{c} x_B \\ y_B \\ z_B\end{array}\right) -
\left(\begin{array}{c} T1 \\ T2 \\ T3\end{array}\right) -
\left(\begin{array}{ccc}
D & -R3 & R2 \\
R3 & D & -R1 \\
-R2 & R1 & D
\end{array}\right)
\left[\left(\begin{array}{c} x_B \\ y_B \\ z_B\end{array}\right) -
\left(\begin{array}{c} T1 \\ T2 \\ T3\end{array}\right)\right].
**Parameters**
xyz_m : numpy.array of length 3
The coordinates to transform in meters.
translate_m : numpy.array of length 3
Propagated (T1, T2, T3).
rotation_matrix : numpy.array of shape (3, 3)
The rotation matrix obtained by calling the
*ParameterSet.matrix()* method on the result of
*propagate_parameters()*.
**Returns**
A numpy.array of length 3 with the transformed coordinates.
**Examples**
**Examples**
At epoch 2000.0:
>>> from numpy import array
>>> parameters = ParameterSet(translate_m = array([ 0.0521, 0.0493, -0.0585]),
... term_d = 1.3400e-09,
... rotate_rad = array([ 4.31968990e-09, 2.61314574e-08, -4.22369679e-08]))
>>> onsala_etrf2000 = array([3370658.768, 711877.023, 5349786.816])
>>> onsala_itrf2008 = reverse_transform(onsala_etrf2000,
... parameters.translate_m,
... parameters.matrix())
>>> print('%.3f, %.3f, %.3f' % tuple(onsala_itrf2008))
3370658.542, 711877.138, 5349786.952
'''
return xyz_m - translate_m - dot(rotation_matrix, xyz_m - translate_m)
[docs]class DatumTransformation(object):
r'''
A datum transformation is used to transform coordinates from
reference frame A to reference frame B. It is defined by a frame
*from* which to transform, a frame *to* which to transform, the
transformation parameters at the reference epoch, and their rates
of change.
**Parameters**
from_frame : string
The reference frame *from* which the parameters transform, for
example 'ITRF2008'
to_frame : string
The reference frame *to* which the parameters transform, for
example 'ETRF2000'
parameters : ParameterSet
The values of the transform parameters :math:`Tn`, :math:`D`,
and :math:`Rn`.
rates : ParameterSet
The annual rates of change for the parameters :math:`Tn`,
:math:`D`, and :math:`Rn`.
ref_epoch : float
The year to which the parameters are referenced. The
parameters at ``epoch`` are ``parameters`` + ``rates`` *
(epoch - ref_epoch)
**Examples**
>>> from numpy import array, pi
>>> mm = 0.001
>>> mas = pi/(180.0*3600.0*1000.0)
>>> transform = DatumTransformation(
... from_frame = 'ITRF2008', to_frame = 'ETRF2000',
... parameters = ParameterSet(array([52.1, 49.3, -58.5])*mm,
... 1.34e-9,
... array([0.891, 5.390, -8.712])*mas),
... rates = ParameterSet(array([0.1, 0.1, -1.8])*mm,
... 0.08e-9,
... array([0.081, 0.490, -0.792])*mas),
... ref_epoch = 2000.0)
>>> transform
DatumTransformation(from_frame = 'ITRF2008', to_frame = 'ETRF2000',
parameters = ParameterSet(translate_m = array([ 0.0521, 0.0493, -0.0585]), term_d = 1.3400e-09, rotate_rad = array([ 4.31968990e-09, 2.61314574e-08, -4.22369679e-08])),
rates = ParameterSet(translate_m = array([ 0.0001, 0.0001, -0.0018]), term_d = 8.0000e-11, rotate_rad = array([ 3.92699082e-10, 2.37558704e-09, -3.83972435e-09])),
ref_epoch = 2000.0)
The parameters are only valid for the reference epoch. If one
needs to convert coordinates at any other epoch, the parameters
must first be propagated to that epoch with the help of the rates
of change:
>>> epoch = 2005.0
>>> par_2005 = transform.propagate_parameters(epoch)
>>> par_2005
ParameterSet(translate_m = array([ 0.0526, 0.0498, -0.0675]), term_d = 1.7400e-09, rotate_rad = array([ 6.28318531e-09, 3.80093926e-08, -6.14355897e-08]))
These propagated parameters can now be used to actually transform
coordinates from the ITRF2008 frame to ETRF2000, at the epoch
2005.0. Here is an example for the Onsala Space Observatory, a
EUREF class A station. According to the EUREF web site for this
station,
http://www.epncb.oma.be/_productsservices/coordinates/crd4station.php?station=ONSA,
Onsala has the following coordinates:
========= ======= ============================= ============================ =============================
Frame Epoch X Y Z
(y) (m) (m) (m)
========= ======= ============================= ============================ =============================
ETRF2000 2005.0 :math:`3370658.847 \pm 0.001` :math:`711876.949 \pm 0.001` :math:`5349786.771 \pm 0.001`
ITRF2008 2005.0 :math:`3370658.542 \pm 0.001` :math:`711877.138 \pm 0.001` :math:`5349786.952 \pm 0.001`
========= ======= ============================= ============================ =============================
Let's see how this works out:
>>> onsala_itrf2008 = array([3370658.542, 711877.138, 5349786.952])
>>> itrf_to_etrf = transform.convert_fn('ITRF2008', 'ETRF2000', epoch = 2005.0)
>>> onsala_etrf2000 = itrf_to_etrf(onsala_itrf2008)
>>> print('%.3f, %.3f, %.3f' % tuple(onsala_etrf2000))
3370658.848, 711876.948, 5349786.770
Not bad at all. We also have the reverse transform, from ETRF2000 to ITRF2008:
>>> onsala_etrf2000 = array([3370658.848, 711876.948, 5349786.770])
>>> etrf_to_itrf = transform.convert_fn('ETRF2000', 'ITRF2008', epoch = 2005.0)
>>> onsala_itrf2008 = etrf_to_itrf(onsala_etrf2000)
>>> print('%.3f, %.3f, %.3f' % tuple(onsala_itrf2008))
3370658.542, 711877.138, 5349786.952
For single use, one can call the *convert* method, which under the
hood first creates a conversion function. Note that this is fairly
wasteful in terms of cpu cycles:
>>> print('%.3f, %.3f, %.3f' %
... tuple(transform.convert(onsala_itrf2008, 'ITRF2008', 'ETRF2000', 2005.0)))
3370658.848, 711876.948, 5349786.770
>>> print('%.3f, %.3f, %.3f' %
... tuple(transform.convert(onsala_etrf2000, from_frame = 'ETRF2000', to_frame = 'ITRF2008', epoch = 2005.0)))
3370658.542, 711877.138, 5349786.952
But be careful:
>>> transform.convert(onsala_etrf2000, from_frame = 'ETRF2000', to_frame = 'ITRF2005', epoch = 2005.0)
Traceback (most recent call last):
...
ValueError: No transform 'ETRF2000' -> 'ITRF2005' only 'ETRF2000' <-> 'ITRF2008'.
'''
def __init__(self, from_frame, to_frame, parameters, rates, ref_epoch):
self.from_frame = from_frame
self.to_frame = to_frame
self.parameters = parameters
self.rates = rates
self.ref_epoch = ref_epoch
def __repr__(self):
return ('''%s(from_frame = %r, to_frame = %r,
parameters = %r,
rates = %r,
ref_epoch = %r)''' %
(type(self).__name__, self.from_frame, self.to_frame,
self.parameters, self.rates, self.ref_epoch))
[docs] def propagate_parameters(self, epoch):
r'''
Propagate the parameter set to *epoch*. Only use parameters
propagated with this method to *epoch* whenever you want to do
a coordinate conversion.
**Parameters**
epoch : number
The year at which one desires the parameters,
e.g. 2013.2.
**Returns**
A ParameterSet.
'''
return self.parameters + self.rates*(epoch - self.ref_epoch)
[docs] def convert_fn(self, from_frame, to_frame, epoch):
r'''
Returns a function *convert(xyz_m)* that converts an XYZ
vector from ``from_frame`` to ``to_frame``. If ``from_frame``
is equal to ``self.to_frame`` and v.v., the function performs
the inverse transform.
**Parameters**
from_frame : string
Frame from which to ransform, e.g. 'ITRF2008'.
to_frame : string
Frame to which to transform, e.g. 'ETRF2000'.
epoch : number
Epoch at which the coordinates were observed, or are
required, in years. Example: 2013.5.
**Raises**
ValueError
if *to_frame* or *from_frame* is not in *[self.to_frame,
self.from_frame]*.
**Returns**
A function *f(xyz_m)* that returns a *numpy.array* of length
3.
'''
parameters = self.propagate_parameters(epoch)
translate_m = parameters.translate_m
matrix = parameters.matrix()
if from_frame == self.from_frame and to_frame == self.to_frame:
transform = forward_transform
elif from_frame == self.to_frame and to_frame == self.from_frame:
transform = reverse_transform
else:
raise ValueError('No transform %r -> %r only %r <-> %r.' %
(from_frame, to_frame,
self.to_frame, self.from_frame))
def convert_function(xyz_m):
r'''
Convert *xyz_m* to anorther datum.
**Parameters**
xyz_m : numpy.array of floats of length 3
The coordinates to transform.
**Returns**
A numpy.array of floats of length 3 containing the
transformed coordinates.
'''
return transform(xyz_m, translate_m, matrix)
return convert_function
[docs] def convert(self, xyz_m, from_frame, to_frame, epoch):
r'''
Converts the *xyz_m* vector from *from_frame* to
*to_frame*. If *from_frame* is equal to *self.to_frame* and
v.v., the function performs the inverse transform. Use only if
one has to convert one or two coordinates. Create a conversion
function with *DatumTransformation.convert_fn()* if you have
to convert a large number of coordinates.
**Parameters**
xyz_m : sequence of 3 floats
The coordinates to convert.
from_frame : string
Frame from which to ransform, e.g. 'ITRF2008'.
to_frame : string
Frame to which to transform, e.g. 'ETRF2000'.
epoch : number
Epoch at which the coordinates were observed, or are
required, in years. Example: 2013.5.
**Raises**
ValueError
if *to_frame* or *from_frame* is not in *[self.to_frame,
self.from_frame]*.
**Returns**
A *numpy.array* of length 3.
'''
return self.convert_fn(from_frame, to_frame, epoch)(xyz_m)