arepytools 1.7.6__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- arepytools/__init__.py +8 -0
- arepytools/constants.py +109 -0
- arepytools/geometry/__init__.py +6 -0
- arepytools/geometry/_geodetic.py +107 -0
- arepytools/geometry/_interpolator.py +210 -0
- arepytools/geometry/anx_time.py +134 -0
- arepytools/geometry/attitude_utils.py +88 -0
- arepytools/geometry/conversions.py +120 -0
- arepytools/geometry/curve.py +344 -0
- arepytools/geometry/curve_protocols.py +113 -0
- arepytools/geometry/direct_geocoding.py +1062 -0
- arepytools/geometry/ellipsoid.py +233 -0
- arepytools/geometry/generalsarattitude.py +528 -0
- arepytools/geometry/generalsarorbit.py +831 -0
- arepytools/geometry/geometric_functions.py +493 -0
- arepytools/geometry/inverse_geocoding.py +315 -0
- arepytools/geometry/inverse_geocoding_core.py +806 -0
- arepytools/geometry/orbit.py +160 -0
- arepytools/geometry/reference_frames.py +289 -0
- arepytools/geometry/rotation.py +144 -0
- arepytools/geometry/wgs84.py +22 -0
- arepytools/io/__init__.py +59 -0
- arepytools/io/channel.py +513 -0
- arepytools/io/channel_iteration.py +147 -0
- arepytools/io/create_orbit.py +35 -0
- arepytools/io/debugio.py +65 -0
- arepytools/io/io_support.py +683 -0
- arepytools/io/manifest.py +100 -0
- arepytools/io/metadata.py +2679 -0
- arepytools/io/parsing/__init__.py +6 -0
- arepytools/io/parsing/metadata_models/__init__.py +93 -0
- arepytools/io/parsing/metadata_models/aresys_generic_metadata.py +222 -0
- arepytools/io/parsing/metadata_models/aresys_types.py +3391 -0
- arepytools/io/parsing/metadata_parsing.py +49 -0
- arepytools/io/parsing/parsing.py +70 -0
- arepytools/io/parsing/translate.py +1701 -0
- arepytools/io/point_target_binary.py +406 -0
- arepytools/io/point_target_file.py +299 -0
- arepytools/io/point_target_models/__init__.py +25 -0
- arepytools/io/point_target_models/aresys_point_target_models.py +181 -0
- arepytools/io/productfolder.py +467 -0
- arepytools/io/productfolder2.py +538 -0
- arepytools/io/productfolder_layout.py +184 -0
- arepytools/math/__init__.py +6 -0
- arepytools/math/axis.py +434 -0
- arepytools/math/genericpoly.py +155 -0
- arepytools/timing/__init__.py +6 -0
- arepytools/timing/conversions.py +54 -0
- arepytools/timing/precisedatetime.py +863 -0
- arepytools-1.7.6.dist-info/METADATA +64 -0
- arepytools-1.7.6.dist-info/RECORD +53 -0
- arepytools-1.7.6.dist-info/WHEEL +4 -0
- arepytools-1.7.6.dist-info/licenses/LICENSE.txt +21 -0
arepytools/__init__.py
ADDED
arepytools/constants.py
ADDED
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
# SPDX-FileCopyrightText: Aresys S.r.l. <info@aresys.it>
|
|
2
|
+
# SPDX-License-Identifier: MIT
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
"""
|
|
6
|
+
**Deprecated** Constants module
|
|
7
|
+
-------------------------------
|
|
8
|
+
|
|
9
|
+
.. deprecated:: v1.7.0
|
|
10
|
+
Importing constants is deprecated.
|
|
11
|
+
Use scipy.constants.speed_of_light for LIGHT_SPEED.
|
|
12
|
+
|
|
13
|
+
Examples
|
|
14
|
+
--------
|
|
15
|
+
>>> import arepytools.constants as cst
|
|
16
|
+
>>> print(cst.LIGHT_SPEED)
|
|
17
|
+
299792458.0
|
|
18
|
+
"""
|
|
19
|
+
import warnings
|
|
20
|
+
|
|
21
|
+
import scipy
|
|
22
|
+
|
|
23
|
+
warnings.warn(
|
|
24
|
+
"arepytools.constants module is deprecated. Instead of importing LIGHT_SPEED use 'from scipy.constants import speed_of_light'",
|
|
25
|
+
DeprecationWarning,
|
|
26
|
+
stacklevel=2,
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
SECONDS_IN_A_DAY = 86164.09054
|
|
30
|
+
"""
|
|
31
|
+
Number of seconds in a day
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
LIGHT_SPEED = scipy.constants.speed_of_light
|
|
35
|
+
"""
|
|
36
|
+
Speed of light in vacuum (m/s).
|
|
37
|
+
"""
|
|
38
|
+
|
|
39
|
+
# Units of measure
|
|
40
|
+
SECOND_STR = "s"
|
|
41
|
+
"""
|
|
42
|
+
Second symbol.
|
|
43
|
+
"""
|
|
44
|
+
|
|
45
|
+
HERTZ_STR = "Hz"
|
|
46
|
+
"""
|
|
47
|
+
Hertz symbol.
|
|
48
|
+
"""
|
|
49
|
+
|
|
50
|
+
JOULE_STR = "j"
|
|
51
|
+
"""
|
|
52
|
+
Joule symbol.
|
|
53
|
+
"""
|
|
54
|
+
|
|
55
|
+
DEGREE_STR = "deg"
|
|
56
|
+
"""
|
|
57
|
+
Degree symbol.
|
|
58
|
+
"""
|
|
59
|
+
|
|
60
|
+
RAD_STR = "rad"
|
|
61
|
+
"""
|
|
62
|
+
Radian symbol.
|
|
63
|
+
"""
|
|
64
|
+
|
|
65
|
+
UTC_STR = "Utc"
|
|
66
|
+
"""
|
|
67
|
+
Coordinated Universal Time abbreviation.
|
|
68
|
+
"""
|
|
69
|
+
|
|
70
|
+
METER_STR = "m"
|
|
71
|
+
"""
|
|
72
|
+
Meter symbol.
|
|
73
|
+
"""
|
|
74
|
+
|
|
75
|
+
# Metric prefixes
|
|
76
|
+
KILO = 1e3
|
|
77
|
+
"""
|
|
78
|
+
Number of units in one kilounit (kilounits-to-units conversion factor).
|
|
79
|
+
"""
|
|
80
|
+
|
|
81
|
+
MEGA = KILO**2
|
|
82
|
+
"""
|
|
83
|
+
Number of units in one megaunit (megaunits-to-units conversion factor).
|
|
84
|
+
"""
|
|
85
|
+
|
|
86
|
+
GIGA = KILO**3
|
|
87
|
+
"""
|
|
88
|
+
Number of units in one gigaunit (gigaunits-to-units conversion factor).
|
|
89
|
+
"""
|
|
90
|
+
|
|
91
|
+
MILLI = KILO**-1
|
|
92
|
+
"""
|
|
93
|
+
Number of units in one milliunit (milliunits-to-units conversion factor).
|
|
94
|
+
"""
|
|
95
|
+
|
|
96
|
+
MICRO = KILO**-2
|
|
97
|
+
"""
|
|
98
|
+
Number of units in one microunit (microunits-to-units conversion factor).
|
|
99
|
+
"""
|
|
100
|
+
|
|
101
|
+
NANO = KILO**-3
|
|
102
|
+
"""
|
|
103
|
+
Number of units in one nanounit (nanounits-to-units conversion factor).
|
|
104
|
+
"""
|
|
105
|
+
|
|
106
|
+
PICO = KILO**-4
|
|
107
|
+
"""
|
|
108
|
+
Number of units in one picounit (picounits-to-units conversion factor).
|
|
109
|
+
"""
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
# SPDX-FileCopyrightText: Aresys S.r.l. <info@aresys.it>
|
|
2
|
+
# SPDX-License-Identifier: MIT
|
|
3
|
+
|
|
4
|
+
"""
|
|
5
|
+
Geodetic module
|
|
6
|
+
---------------
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import numpy as np
|
|
10
|
+
import numpy.typing as npt
|
|
11
|
+
|
|
12
|
+
from arepytools.geometry.conversions import llh2xyz, xyz2llh
|
|
13
|
+
from arepytools.geometry.ellipsoid import WGS84
|
|
14
|
+
|
|
15
|
+
_semi_axis_ratio_sqr = WGS84.semi_axes_ratio_min_max**2
|
|
16
|
+
_major_semi_axis_sqr = WGS84.semi_major_axis**2
|
|
17
|
+
_minor_semi_axis_sqr = WGS84.semi_minor_axis**2
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def _ze(x: float, y: float) -> float:
|
|
21
|
+
eps = 1.0e-21
|
|
22
|
+
|
|
23
|
+
return np.sqrt(_minor_semi_axis_sqr - _semi_axis_ratio_sqr * (x**2 + y**2)) + eps
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def _ze_x(x: float, y: float) -> float:
|
|
27
|
+
return -WGS84.semi_axes_ratio_min_max * x / _ze(x, y)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def _ze_xy(x: float, y: float) -> float:
|
|
31
|
+
# pylint: disable-next=arguments-out-of-order
|
|
32
|
+
return (_semi_axis_ratio_sqr * x) / _ze(x, y) ** 2 * _ze_x(y, x)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def _ze_xx(x: float, y: float) -> float:
|
|
36
|
+
return -_semi_axis_ratio_sqr / _ze(x, y) + (_semi_axis_ratio_sqr * x) / _ze(x, y) ** 2 * _ze_x(x, y)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def _compute_geodetic_jacobian(point: np.ndarray, sensor_position: np.ndarray) -> np.ndarray:
|
|
40
|
+
jac = np.empty(shape=(3, 3), dtype=float)
|
|
41
|
+
|
|
42
|
+
zed_diff = point[2] - sensor_position[2]
|
|
43
|
+
|
|
44
|
+
jac[0][0] = 2.0 * point[0] / _major_semi_axis_sqr
|
|
45
|
+
jac[0][1] = 2.0 * point[1] / _major_semi_axis_sqr
|
|
46
|
+
jac[0][2] = 2.0 * point[2] / _minor_semi_axis_sqr
|
|
47
|
+
jac[1][0] = 1.0 + zed_diff * _ze_xx(point[0], point[1])
|
|
48
|
+
jac[1][1] = zed_diff * _ze_xy(point[0], point[1])
|
|
49
|
+
jac[1][2] = _ze_x(point[0], point[1])
|
|
50
|
+
jac[2][0] = jac[1][1]
|
|
51
|
+
jac[2][1] = 1.0 + zed_diff * _ze_xx(point[1], point[0])
|
|
52
|
+
jac[2][2] = _ze_x(point[1], point[0])
|
|
53
|
+
|
|
54
|
+
return jac
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def _compute_geodetic_rhs(point: npt.NDArray[np.floating], sensor_position: npt.NDArray[np.floating]) -> np.ndarray:
|
|
58
|
+
rhs = np.empty(shape=(3,), dtype=float)
|
|
59
|
+
los = point - sensor_position
|
|
60
|
+
rhs[0] = (point[0] ** 2 + point[1] ** 2) / _major_semi_axis_sqr + point[2] ** 2 / _minor_semi_axis_sqr - 1
|
|
61
|
+
rhs[1] = los[0] + los[2] * _ze_x(point[0], point[1])
|
|
62
|
+
rhs[2] = los[1] + los[2] * _ze_x(point[1], point[0])
|
|
63
|
+
return rhs
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def compute_geodetic_point(sensor_positions: npt.ArrayLike) -> np.ndarray:
|
|
67
|
+
"""Compute the geodetic point that corresponds to a sensor position
|
|
68
|
+
|
|
69
|
+
:param sensor_position: sensor position array like with shape (3,) or (N, 3)
|
|
70
|
+
|
|
71
|
+
:return: geodetic point as a (3,) or (N, 3) numpy array
|
|
72
|
+
"""
|
|
73
|
+
sensor_positions = np.asarray(sensor_positions)
|
|
74
|
+
|
|
75
|
+
if sensor_positions.ndim > 2 or sensor_positions.shape[-1] != 3:
|
|
76
|
+
raise ValueError(f"sensor_positions has invalid shape: {sensor_positions.shape}, it should be (3,) or (N, 3)")
|
|
77
|
+
|
|
78
|
+
sensor_positions = sensor_positions.copy()
|
|
79
|
+
change_sign = sensor_positions[..., 2] < 0
|
|
80
|
+
sensor_positions[change_sign, 2] *= -1
|
|
81
|
+
|
|
82
|
+
geodetic_points = xyz2llh(sensor_positions.T)
|
|
83
|
+
geodetic_points[2, ...] = 0
|
|
84
|
+
geodetic_points = llh2xyz(geodetic_points).T.reshape(sensor_positions.shape)
|
|
85
|
+
|
|
86
|
+
for sensor_position, point in zip(
|
|
87
|
+
sensor_positions.reshape((-1, 3)),
|
|
88
|
+
geodetic_points.reshape((-1, 3)),
|
|
89
|
+
):
|
|
90
|
+
increment_norm_threshold = 1e-9
|
|
91
|
+
max_num_iterations = 10
|
|
92
|
+
for _ in range(max_num_iterations):
|
|
93
|
+
jacobian = _compute_geodetic_jacobian(point[:], sensor_position)
|
|
94
|
+
rhs = _compute_geodetic_rhs(point[:], sensor_position)
|
|
95
|
+
|
|
96
|
+
increment = -np.linalg.solve(jacobian, rhs)
|
|
97
|
+
|
|
98
|
+
increment_norm = np.linalg.norm(increment)
|
|
99
|
+
|
|
100
|
+
point[:] += increment
|
|
101
|
+
|
|
102
|
+
if increment_norm < increment_norm_threshold:
|
|
103
|
+
break
|
|
104
|
+
|
|
105
|
+
geodetic_points[change_sign, 2] *= -1
|
|
106
|
+
|
|
107
|
+
return geodetic_points
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
# SPDX-FileCopyrightText: Aresys S.r.l. <info@aresys.it>
|
|
2
|
+
# SPDX-License-Identifier: MIT
|
|
3
|
+
|
|
4
|
+
"""
|
|
5
|
+
Geometry interpolator module
|
|
6
|
+
----------------------------
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import numpy as np
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def check_eval_function_input(fun):
|
|
13
|
+
"""
|
|
14
|
+
Interpolator eval function input checker
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
def decorated_fun(self, *args_f, **kwargs_f):
|
|
18
|
+
if isinstance(args_f[0], (list, np.ndarray)):
|
|
19
|
+
interpolation_axis = np.asarray(args_f[0])
|
|
20
|
+
else:
|
|
21
|
+
interpolation_axis = np.full((1,), args_f[0])
|
|
22
|
+
return fun(self, interpolation_axis, *args_f[1:], **kwargs_f)
|
|
23
|
+
|
|
24
|
+
return decorated_fun
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class GeometryInterpolator:
|
|
28
|
+
"""
|
|
29
|
+
Internal class for handling interpolation in GeneralSarOrbit and in GeneralSarAttitude
|
|
30
|
+
It interpolates vector-valued functions defined on a given axis
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
_POLYNOMIAL_ORDER = 4
|
|
34
|
+
_DATA_POINTS_PER_POLY = _POLYNOMIAL_ORDER + 1
|
|
35
|
+
_POLY_CENTER_REL_INTERVAL_INDEX = _DATA_POINTS_PER_POLY // 2
|
|
36
|
+
|
|
37
|
+
@classmethod
|
|
38
|
+
def get_min_num_of_data_points(cls):
|
|
39
|
+
"""Return the minimum number of data points needed by the interpolator
|
|
40
|
+
|
|
41
|
+
:return: minimum number of data points"""
|
|
42
|
+
return cls._DATA_POINTS_PER_POLY
|
|
43
|
+
|
|
44
|
+
@classmethod
|
|
45
|
+
def get_polynomial_order(cls):
|
|
46
|
+
"""Return the order of the interpolation polynomial
|
|
47
|
+
|
|
48
|
+
:return: order of the interpolation polynomial
|
|
49
|
+
"""
|
|
50
|
+
return cls._POLYNOMIAL_ORDER
|
|
51
|
+
|
|
52
|
+
def __init__(self, data_axis, data):
|
|
53
|
+
"""Initialize the object as interpolator of the input data defined on the specified axis
|
|
54
|
+
|
|
55
|
+
:param data_axis: the axis of length N on which the data is defined
|
|
56
|
+
:param data: data defined as MxN array of M-dimensional values (e.g. state vectors or attitude angles)
|
|
57
|
+
"""
|
|
58
|
+
self._data_axis = data_axis
|
|
59
|
+
self._num_of_data_components = data.shape[0]
|
|
60
|
+
self._num_of_polynomials = self._data_axis.size - self._POLYNOMIAL_ORDER
|
|
61
|
+
|
|
62
|
+
# Matrix of polynomials
|
|
63
|
+
self._pos = np.ndarray((self._num_of_polynomials, self._num_of_data_components), dtype=np.poly1d)
|
|
64
|
+
self._vel = np.ndarray((self._num_of_polynomials, self._num_of_data_components), dtype=np.poly1d)
|
|
65
|
+
self._acc = np.ndarray((self._num_of_polynomials, self._num_of_data_components), dtype=np.poly1d)
|
|
66
|
+
# Relative azimuth axis [s]
|
|
67
|
+
relative_axis = self._data_axis.get_relative_array()
|
|
68
|
+
self._data_axis_array = self._data_axis.get_array()
|
|
69
|
+
for poly_index in range(self._num_of_polynomials):
|
|
70
|
+
# Each polynomial domain is defined by the poly_axis
|
|
71
|
+
poly_axis = relative_axis[poly_index : poly_index + self._DATA_POINTS_PER_POLY]
|
|
72
|
+
|
|
73
|
+
# Each polynomial relative axis starts at the start of the interval:
|
|
74
|
+
# P(t) = c0 + c1 * (t-poly_time_axis[0]) + ...
|
|
75
|
+
vandermonde_matrix = np.vander((poly_axis - poly_axis[0]).astype(float))
|
|
76
|
+
|
|
77
|
+
# Computation of polynomial coefficients
|
|
78
|
+
for component_index in range(self._num_of_data_components):
|
|
79
|
+
values = data[
|
|
80
|
+
component_index,
|
|
81
|
+
poly_index : poly_index + self._DATA_POINTS_PER_POLY,
|
|
82
|
+
]
|
|
83
|
+
coefficients = np.linalg.solve(vandermonde_matrix, values).tolist()
|
|
84
|
+
position = np.poly1d(coefficients)
|
|
85
|
+
self._pos[poly_index, component_index] = position
|
|
86
|
+
self._vel[poly_index, component_index] = np.polyder(position, 1)
|
|
87
|
+
self._acc[poly_index, component_index] = np.polyder(position, 2)
|
|
88
|
+
|
|
89
|
+
@check_eval_function_input
|
|
90
|
+
def eval(self, interpolation_axis, interval_indexes, components_to_interpolate=None):
|
|
91
|
+
"""Interpolate the data on the given interpolation axis
|
|
92
|
+
|
|
93
|
+
:param interpolation_axis: interpolation axis
|
|
94
|
+
:param interval_indexes: vector or single scalar or None. Denotes the intervals where the given interpolation
|
|
95
|
+
points are expected
|
|
96
|
+
:param components_to_interpolate: list of the components to interpolate, identified by their
|
|
97
|
+
index [optional]
|
|
98
|
+
|
|
99
|
+
:return: CxP array of interpolated values, where C is the number of interpolated components and P is the length
|
|
100
|
+
of the interpolation axis
|
|
101
|
+
"""
|
|
102
|
+
return self._eval(interpolation_axis, interval_indexes, self._pos, components_to_interpolate)
|
|
103
|
+
|
|
104
|
+
@check_eval_function_input
|
|
105
|
+
def eval_first_derivative(self, interpolation_axis, interval_indexes, components_to_interpolate=None):
|
|
106
|
+
"""Interpolate the first derivative of the data on the given interpolation axis
|
|
107
|
+
|
|
108
|
+
:param interpolation_axis: interpolation axis
|
|
109
|
+
:param interval_indexes: vector or single scalar or None. Denotes the interval where the given interpolation
|
|
110
|
+
points are expected
|
|
111
|
+
:param components_to_interpolate: list of the components to interpolate, identified by their index [optional]
|
|
112
|
+
|
|
113
|
+
:return: CxP array of interpolated values, where C is the number of interpolated components and P is the length
|
|
114
|
+
of the interpolation axis
|
|
115
|
+
"""
|
|
116
|
+
return self._eval(interpolation_axis, interval_indexes, self._vel, components_to_interpolate)
|
|
117
|
+
|
|
118
|
+
@check_eval_function_input
|
|
119
|
+
def eval_second_derivative(self, interpolation_axis, interval_indexes, components_to_interpolate=None):
|
|
120
|
+
"""Interpolate the second derivative of the data on the given interpolation axis
|
|
121
|
+
|
|
122
|
+
:param interpolation_axis: interpolation axis
|
|
123
|
+
:param interval_indexes: vector or single scalar or None. Denotes the interval where the given interpolation
|
|
124
|
+
points are expected
|
|
125
|
+
:param components_to_interpolate: list of the components to interpolate, identified by their index [optional]
|
|
126
|
+
|
|
127
|
+
:return: CxP array of interpolated values, where C is the number of interpolated components and P is the length
|
|
128
|
+
of the interpolation axis
|
|
129
|
+
"""
|
|
130
|
+
|
|
131
|
+
return self._eval(interpolation_axis, interval_indexes, self._acc, components_to_interpolate)
|
|
132
|
+
|
|
133
|
+
def _eval(
|
|
134
|
+
self,
|
|
135
|
+
interpolation_axis,
|
|
136
|
+
interval_indexes,
|
|
137
|
+
polynomial,
|
|
138
|
+
components_to_interpolate=None,
|
|
139
|
+
):
|
|
140
|
+
"""Interpolate the data on the given interpolation axis by evaluating interpolation
|
|
141
|
+
polynomials (or a their modified version) on the interpolation axis
|
|
142
|
+
|
|
143
|
+
:param interpolation_axis: interpolation axis
|
|
144
|
+
:param interval_indexes: vector or single scalar or None. Denotes the interval where the given time is expected
|
|
145
|
+
:param polynomial: interpolation polynomials to evaluate
|
|
146
|
+
:param components_to_interpolate: list of the components to interpolate, identified by their index [optional]
|
|
147
|
+
|
|
148
|
+
:return: CxP array of interpolated values, where C is the number of interpolated components and P is the length
|
|
149
|
+
of the interpolation axis
|
|
150
|
+
"""
|
|
151
|
+
|
|
152
|
+
if components_to_interpolate is None:
|
|
153
|
+
components_to_interpolate = range(self._num_of_data_components)
|
|
154
|
+
|
|
155
|
+
poly_indexes = self.get_polynomial_index(
|
|
156
|
+
interpolation_points=interpolation_axis, interval_indexes=interval_indexes
|
|
157
|
+
)
|
|
158
|
+
|
|
159
|
+
interpolation_results = np.zeros((len(components_to_interpolate), interpolation_axis.size))
|
|
160
|
+
|
|
161
|
+
for interpolation_point_index, (poly_index, interpolation_point) in enumerate(
|
|
162
|
+
zip(poly_indexes, interpolation_axis)
|
|
163
|
+
):
|
|
164
|
+
interpolation_point_poly_rel = interpolation_point - self._data_axis_array[poly_index]
|
|
165
|
+
|
|
166
|
+
for out_component_index, component_index in enumerate(components_to_interpolate):
|
|
167
|
+
interpolation_results[out_component_index, interpolation_point_index] = polynomial[
|
|
168
|
+
poly_index, component_index
|
|
169
|
+
](interpolation_point_poly_rel)
|
|
170
|
+
|
|
171
|
+
return interpolation_results
|
|
172
|
+
|
|
173
|
+
def get_interval_index(self, interpolation_points):
|
|
174
|
+
"""Get the indexes of the intervals where the interpolation_points lie
|
|
175
|
+
|
|
176
|
+
:param interpolation_points: vector of required interpolation points
|
|
177
|
+
|
|
178
|
+
:return: array of interval indexes
|
|
179
|
+
"""
|
|
180
|
+
return self._data_axis.get_interval_id(interpolation_points)
|
|
181
|
+
|
|
182
|
+
def get_polynomial_index(self, interpolation_points=None, interval_indexes=None):
|
|
183
|
+
"""Get the indexes of the polynomials to be used for interpolation
|
|
184
|
+
|
|
185
|
+
:param interpolation_points: vector of required interpolation points
|
|
186
|
+
:param interval_indexes: interval indexes of the interpolation points
|
|
187
|
+
|
|
188
|
+
:return: array of polynomial indexes
|
|
189
|
+
|
|
190
|
+
If interpolation points are specified, the positions on the interpolation axis are computed internally to find
|
|
191
|
+
the corresponding polynomial indexes;
|
|
192
|
+
If the interval indexes of the interpolation points on the axis are given, the polynomial indexes are computed
|
|
193
|
+
directly (ignoring the interpolation points).
|
|
194
|
+
"""
|
|
195
|
+
if interval_indexes is None:
|
|
196
|
+
if interpolation_points is not None:
|
|
197
|
+
interval_indexes = self._data_axis.get_interval_id(interpolation_points)
|
|
198
|
+
else:
|
|
199
|
+
raise RuntimeError("Specify either interpolation points or interval indexes")
|
|
200
|
+
if isinstance(interval_indexes, int):
|
|
201
|
+
interval_indexes = np.asarray([interval_indexes])
|
|
202
|
+
return np.clip(
|
|
203
|
+
interval_indexes - self._POLY_CENTER_REL_INTERVAL_INDEX,
|
|
204
|
+
0,
|
|
205
|
+
self._num_of_polynomials - 1,
|
|
206
|
+
)
|
|
207
|
+
|
|
208
|
+
@property
|
|
209
|
+
def num_of_polynomials(self):
|
|
210
|
+
return self._num_of_polynomials
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
# SPDX-FileCopyrightText: Aresys S.r.l. <info@aresys.it>
|
|
2
|
+
# SPDX-License-Identifier: MIT
|
|
3
|
+
|
|
4
|
+
"""
|
|
5
|
+
ANX Time module
|
|
6
|
+
---------------
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import functools
|
|
12
|
+
|
|
13
|
+
import numpy as np
|
|
14
|
+
import scipy.optimize
|
|
15
|
+
|
|
16
|
+
from arepytools.geometry.curve_protocols import TwiceDifferentiable3DCurve
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def compute_anx_times_core(
|
|
20
|
+
trajectory: TwiceDifferentiable3DCurve,
|
|
21
|
+
time_sampling_step_s: float = 1,
|
|
22
|
+
max_abs_z_error: float = 1e-3,
|
|
23
|
+
max_abs_time_error: float = 1e-6,
|
|
24
|
+
max_search_iterations: float = 100,
|
|
25
|
+
) -> np.ndarray:
|
|
26
|
+
"""Compute ANX times of the specified trajectory.
|
|
27
|
+
|
|
28
|
+
Parameters
|
|
29
|
+
----------
|
|
30
|
+
trajectory : TwiceDifferentiable3DCurve
|
|
31
|
+
sensor's trajectory from which compute the ANX times
|
|
32
|
+
time_sampling_step_s : float, optional
|
|
33
|
+
sampling step in seconds for the trajectory time axis, by default 1
|
|
34
|
+
max_abs_z_error : float, optional
|
|
35
|
+
maximum absolute error on ANX z coordinate, by default 1e-3
|
|
36
|
+
max_abs_time_error : float, optional
|
|
37
|
+
maximum absolute error on ANX time, by default 1e-6
|
|
38
|
+
max_search_iterations : float, optional
|
|
39
|
+
maximum number of search iterations per ANX, by default 100
|
|
40
|
+
|
|
41
|
+
Returns
|
|
42
|
+
-------
|
|
43
|
+
np.ndarray
|
|
44
|
+
(N,) array of ANX times
|
|
45
|
+
"""
|
|
46
|
+
|
|
47
|
+
time_axis_origin = trajectory.domain[0]
|
|
48
|
+
time_axis_rel = np.arange(0, trajectory.domain[1] - trajectory.domain[0], time_sampling_step_s)
|
|
49
|
+
evaluated_positions = trajectory.evaluate(time_axis_rel + time_axis_origin)
|
|
50
|
+
|
|
51
|
+
anx_time_rel_intervals = _find_anx_time_intervals(time_axis_rel=time_axis_rel, positions=evaluated_positions)
|
|
52
|
+
|
|
53
|
+
def get_z_coordinate_bisecting_func(time, origin):
|
|
54
|
+
return trajectory.evaluate(origin + time)[-1]
|
|
55
|
+
|
|
56
|
+
anx_times = []
|
|
57
|
+
for interval in anx_time_rel_intervals:
|
|
58
|
+
central_time = interval[0] + (interval[1] - interval[0]) / 2.0
|
|
59
|
+
velocity_z = trajectory.evaluate_first_derivatives(central_time + time_axis_origin)[-1]
|
|
60
|
+
x_tol = min(max_abs_time_error, max_abs_z_error / abs(velocity_z))
|
|
61
|
+
|
|
62
|
+
get_z_coordinate_bisecting_func = functools.partial(
|
|
63
|
+
get_z_coordinate_bisecting_func, origin=interval[0] + time_axis_origin
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
relative_interval = [t - interval[0] for t in interval]
|
|
67
|
+
anx_time_rel = scipy.optimize.bisect(
|
|
68
|
+
get_z_coordinate_bisecting_func,
|
|
69
|
+
*relative_interval,
|
|
70
|
+
xtol=x_tol,
|
|
71
|
+
maxiter=max_search_iterations,
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
anx_times.append(interval[0] + anx_time_rel)
|
|
75
|
+
|
|
76
|
+
return np.array(anx_times) + time_axis_origin
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def compute_relative_times(time_points: np.ndarray, anx_times: np.ndarray) -> tuple[np.ndarray, np.ndarray]:
|
|
80
|
+
"""Return the relative time since the greatest node less or equal to absolute time.
|
|
81
|
+
|
|
82
|
+
Parameters
|
|
83
|
+
----------
|
|
84
|
+
time_points : np.ndarray
|
|
85
|
+
absolute time points, as an array of (N,)
|
|
86
|
+
anx_times : np.ndarray
|
|
87
|
+
sorted absolute anx times, as an array of (N,)
|
|
88
|
+
|
|
89
|
+
Returns
|
|
90
|
+
-------
|
|
91
|
+
tuple[np.ndarray, np.ndarray]
|
|
92
|
+
a tuple of two 1D numpy arrays of relative times and related time_nodes indices,
|
|
93
|
+
nan value and not valid index are returned when previous time node is not available
|
|
94
|
+
"""
|
|
95
|
+
relative_times = np.array(
|
|
96
|
+
[(t - anx_times[anx_times <= t][-1] if len(anx_times[anx_times <= t]) > 0 else np.nan) for t in time_points]
|
|
97
|
+
)
|
|
98
|
+
anx_indices = np.array(
|
|
99
|
+
[
|
|
100
|
+
(len(anx_times[anx_times <= t]) - 1 if len(anx_times[anx_times <= t]) > 0 else len(anx_times))
|
|
101
|
+
for t in time_points
|
|
102
|
+
]
|
|
103
|
+
).astype(int)
|
|
104
|
+
|
|
105
|
+
return relative_times, anx_indices
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def _find_anx_time_intervals(time_axis_rel: np.ndarray, positions: np.ndarray) -> list[tuple[float, float]]:
|
|
109
|
+
"""Finding ANX time intervals for the input relative time axis and the corresponding sensor's positions.
|
|
110
|
+
|
|
111
|
+
Parameters
|
|
112
|
+
----------
|
|
113
|
+
time_axis_rel : np.ndarray
|
|
114
|
+
relative time axis, as a (N,) array of floats
|
|
115
|
+
positions : np.ndarray
|
|
116
|
+
sensor's positions corresponding to the input times, as a (N, 3) array of floats
|
|
117
|
+
|
|
118
|
+
Returns
|
|
119
|
+
-------
|
|
120
|
+
list[tuple[float, float]]
|
|
121
|
+
list of ANX time intervals as (starting time, ending time) of the interval crossing ANX
|
|
122
|
+
"""
|
|
123
|
+
anx_intervals = []
|
|
124
|
+
for interval_index in range(time_axis_rel.size - 1):
|
|
125
|
+
interval_begin_index, interval_end_index = interval_index, interval_index + 2
|
|
126
|
+
|
|
127
|
+
z_start, z_stop = positions[interval_begin_index:interval_end_index, -1]
|
|
128
|
+
is_anx_interval = z_start <= 0 < z_stop
|
|
129
|
+
|
|
130
|
+
if is_anx_interval:
|
|
131
|
+
t_start, t_stop = time_axis_rel[interval_begin_index:interval_end_index]
|
|
132
|
+
anx_intervals.append((t_start, t_stop))
|
|
133
|
+
|
|
134
|
+
return anx_intervals
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# SPDX-FileCopyrightText: Aresys S.r.l. <info@aresys.it>
|
|
2
|
+
# SPDX-License-Identifier: MIT
|
|
3
|
+
|
|
4
|
+
"""
|
|
5
|
+
Attitude-related utilities
|
|
6
|
+
--------------------------
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import numpy as np
|
|
10
|
+
import numpy.typing as npt
|
|
11
|
+
from scipy.spatial import transform
|
|
12
|
+
|
|
13
|
+
from arepytools.geometry.rotation import (
|
|
14
|
+
RotationOrderLike,
|
|
15
|
+
compute_euler_angles_from_rotation,
|
|
16
|
+
compute_rotation,
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def compute_antenna_reference_frame_from_euler_angles(
|
|
21
|
+
order: RotationOrderLike,
|
|
22
|
+
initial_reference_frame_axis: np.ndarray,
|
|
23
|
+
yaw: npt.ArrayLike,
|
|
24
|
+
pitch: npt.ArrayLike,
|
|
25
|
+
roll: npt.ArrayLike,
|
|
26
|
+
) -> np.ndarray:
|
|
27
|
+
"""Computing the Antenna Reference Frame (ARF) from euler angles (YAW, PITCH and ROLL) giving a rotation order and
|
|
28
|
+
a reference frame.
|
|
29
|
+
|
|
30
|
+
Parameters
|
|
31
|
+
----------
|
|
32
|
+
order : RotationOrderLike
|
|
33
|
+
rotation order for the euler angles
|
|
34
|
+
initial_reference_frame_axis : np.ndarray
|
|
35
|
+
reference frame axis of the sensor
|
|
36
|
+
yaw : npt.ArrayLike
|
|
37
|
+
sensor's yaw
|
|
38
|
+
pitch : npt.ArrayLike
|
|
39
|
+
sensor's pitch
|
|
40
|
+
roll : npt.ArrayLike
|
|
41
|
+
sensor's roll
|
|
42
|
+
|
|
43
|
+
Returns
|
|
44
|
+
-------
|
|
45
|
+
np.ndarray
|
|
46
|
+
antenna reference frame for the sensor
|
|
47
|
+
"""
|
|
48
|
+
|
|
49
|
+
rotation = compute_rotation(order=order, yaw=yaw, pitch=pitch, roll=roll)
|
|
50
|
+
|
|
51
|
+
return np.matmul(initial_reference_frame_axis, rotation.as_matrix())
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def compute_euler_angles_from_antenna_reference_frame(
|
|
55
|
+
initial_reference_frame_axis: np.ndarray,
|
|
56
|
+
antenna_reference_frame: np.ndarray,
|
|
57
|
+
order: RotationOrderLike,
|
|
58
|
+
) -> np.ndarray:
|
|
59
|
+
"""Compute euler angles (YAW, PITCH and ROLL) from Antenna Reference Frame (ARF), the initial reference frame and
|
|
60
|
+
rotation order.
|
|
61
|
+
|
|
62
|
+
Parameters
|
|
63
|
+
----------
|
|
64
|
+
initial_reference_frame_axis : np.ndarray
|
|
65
|
+
initial reference frame axis of the sensor, (3, 3) or (N, 3, 3)
|
|
66
|
+
antenna_reference_frame : np.ndarray
|
|
67
|
+
antenna reference frame of the sensor, (3, 3) or (N, 3, 3)
|
|
68
|
+
order : RotationOrderLike
|
|
69
|
+
rotation order
|
|
70
|
+
|
|
71
|
+
Returns
|
|
72
|
+
-------
|
|
73
|
+
np.ndarray
|
|
74
|
+
euler angles array, (N, 3), columns being in the same rotation order provided as input
|
|
75
|
+
"""
|
|
76
|
+
if initial_reference_frame_axis.shape != antenna_reference_frame.shape:
|
|
77
|
+
raise RuntimeError(
|
|
78
|
+
f"input shape mismatch: init ref frame {initial_reference_frame_axis.shape} != arf "
|
|
79
|
+
+ f"{antenna_reference_frame.shape}"
|
|
80
|
+
)
|
|
81
|
+
init_ref_frame = (
|
|
82
|
+
np.transpose(initial_reference_frame_axis, (0, 2, 1))
|
|
83
|
+
if initial_reference_frame_axis.ndim == 3
|
|
84
|
+
else initial_reference_frame_axis.T
|
|
85
|
+
)
|
|
86
|
+
rotation = transform.Rotation.from_matrix(np.matmul(init_ref_frame, antenna_reference_frame))
|
|
87
|
+
|
|
88
|
+
return compute_euler_angles_from_rotation(order=order, rotation=rotation)
|