astrix package

AsTrIX — Aerospace Trajectory Imaging & Diagnostics toolbox.

This package provides tools for aerospace trajectory analysis and visualization.

class Frame(rot, loc=None, ref_frame=None, backend=None)[source]

Bases: object

A reference frame defined by a rotation and location. Can be static or time-varying, and can have rotation defined relative to another Frame. Combines RotationLike and Location objects, and manages time associations.

Parameters:
  • rot (Rotation | RotationSequence) – A scipy Rotation object (single rotation) or RotationSequence (time-tagged rotations). If a single Rotation is provided, the frame rotation is static.

  • loc (Location, optional) – A Location object (Point or Path) defining the frame origin in ECEF coordinates. If not provided, the frame origin is assumed to be at the origin of the reference frame. If loc is provided, it must be a singular Point (1x3) for static frames. Use Path objects for time-varying locations.

  • ref_frame (Frame, optional) – A reference Frame object to define the rotation relative to. If not provided, the rotation is assumed to be absolute (e.g., from ECEF frame).

  • backend (BackendArg, optional) – Array backend to use (numpy, jax, etc.). Defaults to numpy.

Examples

Static frame with static rotation and location:

>>> from astrix.primitives import Frame, Point
>>> from scipy.spatial.transform import Rotation
>>>
>>> rot = Rotation.from_euler('xyz', [90, 0, 0], degrees=True)  # 90 degree rotation about x-axis
>>> loc = Point.from_geodet([27.47, 153.03, 0])  # Brisbane location
>>> frame_static = Frame(rot, loc) # Frame with static rotation and location
>>> frame_static.interp_rot().as_euler('xyz', degrees=True)  # Get absolute rotation
array([[90.,  0.,  0.]])
>>> frame_static.loc.geodet  # Get frame location in geodetic coordinates
array([[153.03, 27.47, 0.0]])

Time-varying frame with rotation sequence and static location:

>>> from astrix.primitives import Time
>>> from datetime import datetime, timezone
>>>
>>> times = Time.from_datetime(
...     [
...         datetime(2021, 1, 1, 12, 0, 0, tzinfo=timezone.utc),
...         datetime(2021, 1, 1, 13, 0, 0, tzinfo=timezone.utc),
...         datetime(2021, 1, 1, 14, 0, 0, tzinfo=timezone.utc),
...     ]
... )
>>> rots = Rotation.from_euler(
...     "xyz",
...     [
...         [0, 0, 0],
...         [90, 0, 0],
...         [180, 0, 0],
...     ],
...     degrees=True,
... )
>>> rot_seq = RotationSequence(rots, times)
>>> loc = Point.from_geodet([27.47, 153.03, 0])  # Brisbane location
>>> frame_dynamic_rot = Frame(rot_seq, loc) # Frame with time-varying rotation and static location
>>> interp_rot = frame_dynamic_rot.interp_rot(
...     Time.from_datetime(datetime(2021, 1, 1, 12, 30, 0, tzinfo=timezone.utc))
... )  # Interpolate to halfway between first and second rotation
>>> interp_rot.as_euler(
...     "xyz", degrees=True
... )  # Get interpolated absolute rotation as Euler angles
array([[45.,  0.,  0.]])
>>> frame_dynamic_rot.loc.geodet  # Get frame location in geodetic coordinates
array([[153.03, 27.47, 0.0]])

Frame defined relative to another frame:

>>> rot_ref = Rotation.from_euler('xyz', [0, 30, 0], degrees=True)  # Reference frame
>>> frame_ref = Frame(rot_ref, loc)  # Reference frame
>>> rot_rel = Rotation.from_euler('xyz', [0, 40, 0], degrees=True)
>>> frame = Frame(rot_rel, ref_frame=frame_ref)
>>>
>>> frame.interp_rot().as_euler('xyz', degrees=True)  # Absolute rotation (rot_ref * rot_rel)
array([[ 0., 70.,  0.]])
>>> frame.loc.geodet  # (Same as reference frame)
array([[153.03, 27.47, 0.0]])

Notes

  • If both loc and ref_frame are provided, the new frame location is used and the reference frame location is disregarded.

  • A TimeGroup object is created internally to manage time associations between rotation, location, and reference frame.

  • If the frame is static (single rotation and singular Point), the time properties return TIME_INVARIANT.

  • Use Path objects for time-varying locations.

convert_to(backend)[source]

Convert the Frame object to a different backend.

Return type:

Frame

Parameters:

backend (str | Any | None)

interp_loc(time=None, check_bounds=True)[source]

Get the interpolated location of the frame at the given times. If the location is static, time can be None.

Return type:

Point

Parameters:
  • time (Time | None)

  • check_bounds (bool)

interp_rot(time=None, check_bounds=True)[source]

Get the interpolated absolute rotation of the frame at the given times. If all rotations are time invariant, time can be None.

Return type:

Rotation

Parameters:
  • time (Time | None)

  • check_bounds (bool)

property backend: str

Get the name of the array backend in use (e.g., ‘numpy’, ‘jax’).

property has_ref: bool

Check if the frame has a reference frame.

property rel_rot: RotationLike

Get the last rotation of the frame relative to the reference frame.

property time_bounds: tuple[TimeLike, TimeLike]

Get the time bounds of the frame as a tuple (start_time, end_time). If the frame is static, returns TIME_INVARIANT.

property time_group: TimeGroup

Get the Time object associated with the frame, if any.

class Path(point, backend=None)[source]

Bases: Location[Time]

Path of multiple Point objects with associated Time. Enables interpolation between points over time and calculation of velocity. Must have at least 2 points with associated Time.

Parameters:
  • point (Point | list of Point) – Point object or list of Point objects with associated Time. If a list is provided, all Points must have the same backend and associated Time.

  • backend (BackendArg, optional) – Array backend to use (numpy, jax, etc.). Defaults to numpy.

Examples

Instantiating a Path from a list of Points:

>>> from astrix.primitives import Point, Time, Path
>>> from datetime import datetime, timezone
>>> times = Time.from_datetime(
...     [
...         datetime(2025, 1, 1, 12, 0, 0, tzinfo=timezone.utc),
...         datetime(2025, 1, 1, 12, 0, 1, tzinfo=timezone.utc),
...         datetime(2025, 1, 1, 12, 0, 2, tzinfo=timezone.utc),
...     ]
... )
>>> path = Path(
...     [
...         Point([1, 2, 0], time=times[0]),
...         Point([2, 3.8, 0.4], time=times[1]),
...         Point([3, 6.0, 1], time=times[2]),
...     ]
... )  # Somewhere very hot in the middle of the Earth

Interpolate the Path to a new time and get velocity:

>>> path.interp(
        Time.from_datetime(datetime(2025, 1, 1, 12, 0, 1, 500000, tzinfo=timezone.utc)),
        method="linear"
    ).ecef # Interpolate to halfway between second and third point, return ECEF array
array([[2.5, 4.9, 0.7]])
>>> vel = path.interp_vel(
        Time.from_datetime(datetime(2025, 1, 1, 12, 0, 1, 500000, tzinfo=timezone.utc)),
    )
>>> vel.magnitude  # Interpolated velocity magnitude in m/s
array([2.48997992])
>>> vel.unit  # Interpolated unit velocity vector
array([[0.40160966, 0.88354126, 0.2409658 ]])
convert_to(backend)[source]

Convert the Path object to a different backend.

Return type:

Path

Parameters:

backend (str | Any | None)

interp(time, method='linear', check_bounds=True)[source]

Interpolate the Path to the given times using the specified method.

Parameters:
  • time (Time) – Times to interpolate to.

  • method (str, optional) – Interpolation method. Currently only ‘linear’ is supported. Defaults to ‘linear’.

  • check_bounds (bool, optional) – Whether to check if the interpolation times are within the path time bounds. Defaults to True.

Returns:

Interpolated Point object at the given times.

Return type:

Point

interp_vel(time, method='linear', check_bounds=True)[source]

Interpolate the Path velocity to the given times using the specified method. Currently only ‘linear’ interpolation is supported.

Return type:

Velocity

Parameters:
  • time (Time)

  • method (str)

  • check_bounds (bool)

property end_time: TimeLike

Get the end time of the Path.

property start_time: TimeLike

Get the start time of the Path.

property vel: Velocity

Get the Velocity object associated with the Path.

class Point(ecef, time=TimeInvariant object, backend=None)[source]

Bases: Location[TimeLike]

Point(s) in ECEF coordinates, stored as (x, y, z) in metres. Can represent a single point or multiple points, and can be associated with a Time object for time instances of the points.

Parameters:
  • ecef (Array) – ECEF coordinates as (x, y, z) in metres. Shape (3,) or (1,3) for single points, (n, 3) for multiple points.

  • time (TimeLike, optional) – Time object associated with the points. If provided, the length of time must match the number of points. Defaults to TIME_INVARIANT for static points.

  • backend (BackendArg, optional) – Array backend to use (numpy, jax, etc.). Defaults to numpy.

Examples

Single static point:

>>> from astrix.primitives import Point
>>> p1 = Point(
...     [-5047162.4, 2568329.79, -2924521.17]
... )  # ECEF coordinates of Brisbane in metres
>>> p.geodet  # Convert to geodetic coordinates (lat, lon, alt)
array([[153.03, 27.47, 0.0]])
>>> p2 = Point.from_geodet([27.47, 153.03, 0])  # lat, lon in degrees, alt in metres
>>> p2.ecef  # Convert back to ECEF coordinates
array([[-5047162.4, 2568329.79, -2924521.17]])

Multiple static points:

>>> pts = Point(
...     [
...         [-5047162.4, 2568329.79, -2924521.17],  # Brisbane
...         [-2694045.0, -4293642.0, 3857878.0],  # San Francisco
...         [3877000.0, 350000.0, 5027000.0],  # Somewhere else
...     ]
... )
>>> pt_bris = pts[0]  # First point (Brisbane)
>>> assert len(pts) == 3

Dynamic point with time:

>>> from datetime import datetime, timezone
>>> from astrix.primitives import Time
>>> times = Time.from_datetime(
...     [
...         datetime(2021, 1, 1, 12, 0, 0, tzinfo=timezone.utc),
...         datetime(2021, 1, 1, 13, 0, 0, tzinfo=timezone.utc),
...         datetime(2021, 1, 1, 14, 0, 0, tzinfo=timezone.utc),
...     ]
... )
>>> pts_time = Point(
...     [
...         [-5047162.4, 2568329.79, -2924521.17],  # Brisbane
...         [-2694045.0, -4293642.0, 3857878.0],  # San Francisco
...         [3877000.0, 350000.0, 5027000.0],  # Somewhere else
...     ],
...     time=times,
... )
>>> pts.has_time
True
>>> pts.is_singular
False
>>> pts_new = pts + Point(
...     [[-1000, -1000, -1000]],
...     time=Time.from_datetime(
...         datetime(2021, 1, 1, 15, 0, 0, tzinfo=timezone.utc)
...     ),
... )
>>> assert len(pts_new) == 4

Notes

  • When associating a Time object, the length of the Time must match the number of points.

  • Use Path objects for interpolating between multiple points over time.

classmethod from_geodet(geodet, time=TimeInvariant object, backend=None)[source]

Create a Point object from geodetic coordinates (lat, lon, alt). Lat and lon are in degrees, alt is in meters.

Return type:

Point

Parameters:
  • geodet (Any)

  • time (TimeLike)

  • backend (str | Any | None)

classmethod from_list(points)[source]

Create a Point object from a list of Point objects.

Return type:

Point

Parameters:

points (list[Point])

convert_to(backend)[source]

Convert the Point object to a different backend.

Return type:

Point

Parameters:

backend (str | Any | None)

property has_time: bool

Check if the Point has associated Time.

property is_singular: bool

Check if the Point object represents a single point.

class Ray(origin, dir, time=TimeInvariant object, backend=None)[source]

Bases: object

A ray defined by an origin point and a direction vector. Can represent multiple rays with associated times.

Parameters:
  • origin (Point) – Point object with length N defining the ray origin(s) in ECEF coordinates (meters).

  • dir (Array) – Nx3 array of ray direction vectors in ECEF coordinates (meters). Direction vectors will be normalized to unit vectors.

  • time (Time, optional) – Time object associated with the rays. Must be same length as origin if provided. Defaults to None.

  • backend (BackendArg, optional) – Array backend to use (numpy, jax, etc.). Defaults to numpy.

Notes

  • Origin Point must be same length as dimension 0 of dir array.

  • Time cannot be provided by both origin Point and time argument.
    • If origin Point has associated Time, time argument must be None.

    • If origin Point does not have associated Time, time argument can be provided.

    • If neither origin Point nor time argument have Time, Ray will not have Time.

  • No check is made that times are monotonically increasing for interpolation. This is left to the user.

Examples

TBC

classmethod from_endpoint(origin, endpoint, time=TimeInvariant object, backend=None)[source]

Create a Ray object from origin and endpoint arrays.

Parameters:
  • origin (Point) – Nx3 array of ray origin points in ECEF coordinates (meters).

  • endpoint (Point) – Nx3 array of ray endpoint points in ECEF coordinates (meters).

  • time (Time, optional) – Time object associated with the rays. Must be same length as origin if provided. Defaults to None.

  • backend (BackendArg, optional) – Array backend to use (numpy, jax, etc.). Defaults to numpy.

Returns:

Ray object defined by the origin and direction from origin to endpoint.

Return type:

Ray

classmethod from_head_el(origin, head_el, time=None, backend=None)[source]

Create a Ray object from origin points and heading/elevation angles.

Parameters:
  • origin (Array) – Nx3 array of ray origin points in ECEF coordinates (meters).

  • head_el (Array) – Nx2 array of heading (from north) and elevation (from horizontal) angles in degrees.

  • time (Time, optional) – Time object associated with the rays. Must be same length as origin if provided. Defaults to None.

  • backend (BackendArg, optional) – Array backend to use (numpy, jax, etc.). Defaults to numpy.

Returns:

Ray object defined by the origin and direction from heading/elevation angles.

Return type:

Ray

convert_to(backend)[source]

Convert the Ray object to a different backend.

Return type:

Ray

Parameters:

backend (str | Any | None)

interp(time, check_bounds=True)[source]

Interpolate the Ray origin and direction to the given times.

Parameters:
  • time (Time) – Times to interpolate to.

  • check_bounds (bool, optional) – Whether to check if the interpolation times are within the ray time bounds. Defaults to True.

Returns:

Interpolated Ray object at the given times.

Return type:

Ray

property backend: str

Get the name of the array backend in use (e.g., ‘numpy’, ‘jax’).

property has_time: bool

Check if the Ray has associated Time.

property head_el

Return the heading (from north) and elevation (from horizontal) angles in degrees.

property origin: Point

Get the ray origin point(s).

property time: TimeLike

Get the associated Time object, if any.

property unit: Any

Get the unit direction vector(s) of the ray.

class RotationSequence(rot, time, backend=None)[source]

Bases: RotationLike

A sequence of time-tagged rotations, enabling interpolation between them. Uses scipy.spatial.transform.Slerp for interpolation.

Parameters:
  • rot (Rotation | list of Rotation) – A scipy Rotation object containing multiple rotations, or a list of such objects. If a list is provided, all elements must be scipy Rotation objects.

  • time (Time) – A Time object with time instances corresponding to each rotation. Must be the same length as the number of rotations and strictly increasing.

  • backend (BackendArg, optional) – Array backend to use (numpy, jax, etc.). Defaults to numpy.

Examples

>>> from astrix.primitives import Time, RotationSequence
>>> from scipy.spatial.transform import Rotation
>>> from datetime import datetime, timezone
>>> times = Time.from_datetime(
...     [
...         datetime(2021, 1, 1, 12, 0, 0, tzinfo=timezone.utc),
...         datetime(2021, 1, 1, 13, 0, 0, tzinfo=timezone.utc),
...         datetime(2021, 1, 1, 14, 0, 0, tzinfo=timezone.utc),
...     ]
... )
>>> rots = Rotation.from_euler(
...     "xyz",
...     [
...         [0, 0, 0],
...         [90, 0, 0],
...         [180, 0, 0],
...     ],
...     degrees=True,
... )
>>> rot_seq = RotationSequence(rots, times)
>>> interp_rot = rot_seq.interp(
...     Time.from_datetime(datetime(2021, 1, 1, 12, 30, 0, tzinfo=timezone.utc))
... )  # Interpolate to halfway between first and second rotation
>>> interp_rot.as_euler(
...     "xyz", degrees=True
... )  # Get interpolated rotation as Euler angles
array([[45.,  0.,  0.]])
convert_to(backend)[source]

Convert the RotationSequence object to a different backend.

Return type:

RotationSequence

Parameters:

backend (str | Any | None)

interp(time, check_bounds=True)[source]

Interpolate the rotation sequence at the given times to return Rotation(s).

Return type:

Rotation

Parameters:
  • time (Time)

  • check_bounds (bool)

property time: Time

Get the Time object associated with the rotation sequence.

class Time(secs, backend=None)[source]

Bases: TimeLike

One or more time instances.

Represents time using seconds since Unix epoch (1970-01-01 00:00:00 UTC). Can handle single time instances or arrays of times with consistent backend support for JAX/NumPy compatibility.

Parameters:
  • secs (Array | list of float | float) – Time values in seconds since Unix epoch (1970-01-01 UTC)

  • backend (BackendArg, optional) – Array backend to use (numpy, jax, etc.). Defaults to numpy.

Examples

Single time instance:

>>> t = Time(1609459200.0)  # 2021-01-01 00:00:00 UTC

Multiple times:

>>> times = Time([1609459200.0, 1609545600.0])  # Jan 1-2, 2021

From datetime:

>>> from datetime import datetime, timezone
>>> dt = datetime(2021, 1, 1, tzinfo=timezone.utc)
>>> t = Time.from_datetime(dt)
>>> dt_list = [
...     datetime(2021, 1, 1, tzinfo=timezone.utc),
...     datetime(2021, 1, 2, tzinfo=timezone.utc),
... ]
>>> times = Time.from_datetime(dt_list)

Notes

All datetime objects must be timezone-aware to avoid ambiguity.

classmethod from_datetime(time, backend=None)[source]

Create a Time object from a single or list of datetime objects. Will not accept timezone-unaware datetime obejects due to ambiguity.

Return type:

Time

Parameters:
  • time (datetime | list[datetime])

  • backend (str | Any | None)

convert_to(backend)[source]

Convert the Time object to a different backend.

Return type:

Time

Parameters:

backend (str | Any | None)

in_bounds(time)[source]

Check if the given time(s) are within the bounds of this Time object.

Return type:

bool

Parameters:

time (Time)

offset(offset)[source]
Return type:

Time

Parameters:

offset (float)

property backend: str

Get the name of the array backend in use (e.g., ‘numpy’, ‘jax.numpy’).

property datetime: list[datetime]
property end_sec: float | Any

Get the end time in seconds since epoch.

property is_increasing: bool

Check if the time values are strictly increasing.

property secs: Any

Get the time values in seconds since epoch.

property start_sec: float | Any

Get the start time in seconds since epoch.

resolve_backend(name_or_mod=None)[source]

Resolve the backend (array namespace) from a string or module.

Return type:

ModuleType

Parameters:

name_or_mod (str | ModuleType | None)

Subpackages

Submodules