Navigation
Inertial navigation, geodesy, and INS/GNSS integration functions.
Navigation utilities for target tracking.
This module provides geodetic and navigation calculations commonly needed in tracking applications, including: - Geodetic coordinate conversions - Inertial Navigation System (INS) mechanization - Alignment algorithms - INS/GNSS integration (loosely and tightly coupled) - Great circle navigation - Rhumb line navigation
INS Mechanization
Strapdown inertial navigation system mechanization and error modeling.
Inertial Navigation System (INS) mechanization.
This module provides strapdown INS mechanization equations for navigation, including: - INS state representation (position, velocity, attitude) - Strapdown mechanization equations (NED and ECEF frames) - Coning and sculling corrections for IMU data - Gravity and transport rate computations - Attitude integration using quaternions
References
D. Titterton and J. Weston, “Strapdown Inertial Navigation Technology”, 2nd ed., IEE, 2004.
P. Groves, “Principles of GNSS, Inertial, and Multisensor Integrated Navigation Systems”, 2nd ed., Artech House, 2013.
Farrell, “Aided Navigation: GPS with High Rate Sensors”, McGraw-Hill, 2008.
- class pytcl.navigation.ins.INSState(position, velocity, quaternion, time)[source]
Bases:
NamedTupleInertial Navigation System state.
- Variables:
position (ndarray) – Position in geodetic coordinates [latitude (rad), longitude (rad), altitude (m)].
velocity (ndarray) – Velocity in NED frame [vN, vE, vD] (m/s).
quaternion (ndarray) – Attitude quaternion [qw, qx, qy, qz] from body to NED frame (scalar-first).
time (float) – Time associated with this state (seconds).
- class pytcl.navigation.ins.IMUData(accel, gyro, dt)[source]
Bases:
NamedTupleIMU measurement data.
- Variables:
accel (ndarray) – Specific force measurements [ax, ay, az] in body frame (m/s^2).
gyro (ndarray) – Angular rate measurements [wx, wy, wz] in body frame (rad/s).
dt (float) – Time interval between measurements (seconds).
- class pytcl.navigation.ins.INSErrorState(position_error, velocity_error, attitude_error, accel_bias, gyro_bias)[source]
Bases:
NamedTupleINS error state for Kalman filtering.
- Variables:
position_error (ndarray) – Position error [delta_lat, delta_lon, delta_alt] (rad, rad, m).
velocity_error (ndarray) – Velocity error in NED [delta_vN, delta_vE, delta_vD] (m/s).
attitude_error (ndarray) – Attitude error angles [phi_N, phi_E, phi_D] (rad).
accel_bias (ndarray) – Accelerometer bias [bax, bay, baz] (m/s^2).
gyro_bias (ndarray) – Gyroscope bias [bwx, bwy, bwz] (rad/s).
- pytcl.navigation.ins.normal_gravity(lat, alt=0.0)[source]
Compute normal gravity using Somigliana’s formula with free-air correction.
- Parameters:
- Returns:
g – Normal gravity magnitude in m/s^2.
- Return type:
Notes
Uses the WGS84 gravity formula with first-order altitude correction.
- pytcl.navigation.ins.earth_rate_ned(lat)[source]
Compute Earth rotation rate in NED frame.
- Parameters:
lat (float) – Geodetic latitude in radians.
- Returns:
omega_ie_n – Earth rotation rate in NED frame [wN, wE, wD] (rad/s).
- Return type:
ndarray
- pytcl.navigation.ins.transport_rate_ned(lat, alt, vN, vE, ellipsoid=(6378137.0, 0.0033528106647474805))[source]
Compute transport rate (navigation frame rotation) in NED frame.
- Parameters:
- Returns:
omega_en_n – Transport rate in NED frame [wN, wE, wD] (rad/s).
- Return type:
ndarray
Notes
The transport rate accounts for navigation frame rotation as the vehicle moves over the curved Earth surface.
- pytcl.navigation.ins.radii_of_curvature(lat, ellipsoid=(6378137.0, 0.0033528106647474805))[source]
Compute principal radii of curvature.
- pytcl.navigation.ins.coning_correction(gyro_prev, gyro_curr)[source]
Compute first-order coning correction for angular increment.
- Parameters:
gyro_prev (array_like) – Previous angular INCREMENT [dtheta_x, dtheta_y, dtheta_z] (rad), not an angular rate – integrate the gyro over the interval first (
gyro_rate * dt).gyro_curr (array_like) – Current angular increment (rad), same convention as
gyro_prev.
- Returns:
delta_theta_coning – Coning correction to angular increment (rad).
- Return type:
ndarray
Notes
Coning error occurs when the rotation axis itself rotates (coning motion). This first-order correction assumes trapezoidal integration of gyro data.
Both arguments are angular increments, not rates: the formula is
(1/12) * (theta_prev x theta_curr), so passing rates inrad/sreturnsrad^2/s^2and is wrong by a factor of1/dt^2.compensate_imu_data()takes rates and does the integration for you; prefer it unless you already hold increments.References
Savage, P.G., “Strapdown Inertial Navigation Integration Algorithm Design Part 1: Attitude Algorithms”, AIAA Journal of Guidance, 1998.
- pytcl.navigation.ins.sculling_correction(accel_prev, accel_curr, gyro_prev, gyro_curr)[source]
Compute first-order sculling correction for velocity increment.
- Parameters:
accel_prev (array_like) – Previous velocity INCREMENT [dv_x, dv_y, dv_z] (m/s), not a specific force – integrate the accelerometer over the interval first (
accel * dt).accel_curr (array_like) – Current velocity increment (m/s), same convention as
accel_prev.gyro_prev (array_like) – Previous angular INCREMENT (rad), not an angular rate.
gyro_curr (array_like) – Current angular increment (rad).
- Returns:
delta_v_sculling – Sculling correction to velocity increment (m/s).
- Return type:
ndarray
Notes
Sculling error occurs when linear vibration and angular vibration are correlated (e.g., on a flexible structure).
All four arguments are increments, not rates: passing
m/s^2andrad/syieldsm*rad/s^3and is wrong by1/dt^2. The formula(1/12) * (theta_prev x dv_curr + dv_prev x theta_curr)is symmetric under swapping the two role pairs, so calling with the velocity and angular arguments transposed happens to give the same answer – do not rely on that.compensate_imu_data()takes rates and integrates for you.References
Savage, P.G., “Strapdown Inertial Navigation Integration Algorithm Design Part 2: Velocity and Position Algorithms”, AIAA, 1998.
- pytcl.navigation.ins.compensate_imu_data(accel_prev, accel_curr, gyro_prev, gyro_curr, dt)[source]
Compute compensated angular and velocity increments with coning/sculling corrections.
- Parameters:
accel_prev (array_like) – Previous specific force [ax, ay, az] (m/s^2).
accel_curr (array_like) – Current specific force [ax, ay, az] (m/s^2).
gyro_prev (array_like) – Previous angular rate [wx, wy, wz] (rad/s).
gyro_curr (array_like) – Current angular rate [wx, wy, wz] (rad/s).
dt (float) – Time interval (seconds).
- Returns:
delta_theta (ndarray) – Compensated angular increment (rad).
delta_v (ndarray) – Compensated velocity increment (m/s).
- Return type:
Tuple[ndarray[tuple[Any, …], dtype[floating]], ndarray[tuple[Any, …], dtype[floating]]]
- pytcl.navigation.ins.skew_symmetric(v)[source]
Create skew-symmetric matrix from vector.
- Parameters:
v (array_like) – 3-element vector.
- Returns:
S – 3x3 skew-symmetric matrix.
- Return type:
ndarray
- pytcl.navigation.ins.update_quaternion(q, delta_theta)[source]
Update quaternion using angular increment.
- Parameters:
q (array_like) – Current quaternion [qw, qx, qy, qz].
delta_theta (array_like) – Angular increment in body frame (rad).
- Returns:
q_new – Updated quaternion (normalized).
- Return type:
ndarray
Notes
Uses first-order quaternion update: q_new = q * delta_q where delta_q represents the incremental rotation.
- pytcl.navigation.ins.update_attitude_ned(q_b_n, omega_ib_b, omega_in_n, dt)[source]
Update attitude quaternion (body to NED) for one time step.
- Parameters:
q_b_n (array_like) – Current quaternion from body to NED frame.
omega_ib_b (array_like) – Angular rate of body w.r.t. inertial, in body frame (rad/s).
omega_in_n (array_like) – Angular rate of NED w.r.t. inertial, in NED frame (rad/s).
dt (float) – Time step (seconds).
- Returns:
q_new – Updated quaternion (body to NED).
- Return type:
ndarray
Notes
The attitude update accounts for both body rotation and navigation frame rotation. omega_in_n = omega_ie_n + omega_en_n (Earth rate + transport rate)
- pytcl.navigation.ins.mechanize_ins_ned(state, imu, ellipsoid=(6378137.0, 0.0033528106647474805), accel_prev=None, gyro_prev=None)[source]
Perform one step of strapdown INS mechanization in NED frame.
- Parameters:
state (INSState) – Current INS state.
imu (IMUData) – IMU measurements for this time step.
ellipsoid (Ellipsoid, optional) – Reference ellipsoid (default: WGS84).
accel_prev (array_like, optional) – Previous accelerometer reading for coning/sculling corrections.
gyro_prev (array_like, optional) – Previous gyroscope reading for coning/sculling corrections.
- Returns:
new_state – Updated INS state after mechanization.
- Return type:
Notes
This implements the standard NED-frame strapdown mechanization: 1. Attitude update (quaternion integration) 2. Specific force transformation to NED 3. Velocity update (with gravity and Coriolis) 4. Position update
The algorithm follows Groves (2013), Chapter 5.
- pytcl.navigation.ins.initialize_ins_state(lat, lon, alt, vN=0.0, vE=0.0, vD=0.0, roll=0.0, pitch=0.0, yaw=0.0, time=0.0)[source]
Initialize INS state from position, velocity, and attitude.
- Parameters:
lat (float) – Geodetic latitude in radians.
lon (float) – Geodetic longitude in radians.
alt (float) – Altitude above ellipsoid in meters.
vN (float, optional) – North velocity in m/s (default: 0).
vE (float, optional) – East velocity in m/s (default: 0).
vD (float, optional) – Down velocity in m/s (default: 0).
roll (float, optional) – Roll angle in radians (default: 0).
pitch (float, optional) – Pitch angle in radians (default: 0).
yaw (float, optional) – Yaw/heading angle in radians (default: 0).
time (float, optional) – Initial time in seconds (default: 0).
- Returns:
state – Initialized INS state.
- Return type:
- pytcl.navigation.ins.coarse_alignment(accel, lat)[source]
Perform coarse leveling alignment from accelerometer readings.
- Parameters:
accel (array_like) – Averaged accelerometer readings [ax, ay, az] in body frame (m/s^2). Vehicle should be stationary. For a level vehicle, accel = [0, 0, -g].
lat (float) – Approximate latitude in radians.
- Returns:
roll (float) – Estimated roll angle in radians.
pitch (float) – Estimated pitch angle in radians.
- Return type:
Notes
This assumes the vehicle is stationary so the accelerometer measures only the reaction to gravity (specific force = -g when level). Does not estimate heading (yaw).
For a level vehicle pointing north: - Accelerometer reads [0, 0, -g] (z-axis up measures negative gravity) - Roll = 0, Pitch = 0
Sign convention: positive roll is right wing down, positive pitch is nose up.
- pytcl.navigation.ins.gyrocompass_alignment(gyro, roll, pitch, lat)[source]
Perform gyrocompass alignment to estimate heading.
- Parameters:
- Returns:
yaw – Estimated heading (yaw) angle in radians.
- Return type:
Notes
Uses the horizontal component of Earth’s rotation rate to determine heading. Requires accurate roll and pitch (from coarse_alignment).
- pytcl.navigation.ins.ins_error_state_matrix(state, ellipsoid=(6378137.0, 0.0033528106647474805))[source]
Compute the INS error state transition matrix (continuous-time F matrix).
- Parameters:
- Returns:
F – 15x15 error state transition matrix.
- Return type:
ndarray
Notes
The 15-state error model includes: - Position errors (3): delta_lat, delta_lon, delta_alt - Velocity errors (3): delta_vN, delta_vE, delta_vD - Attitude errors (3): phi_N, phi_E, phi_D - Accelerometer biases (3): bax, bay, baz - Gyroscope biases (3): bwx, bwy, bwz
Verification status (EXPERIMENTAL, v2.11.1). This matrix was audited against a central-difference Jacobian of
mechanize_ins_ned(), the mechanization it linearizes. Results:Position/velocity block (rows 0-5): mostly agrees.
F[5,0],F[5,2],F[5,3]were qualitatively wrong (sign errors and a missing term, making the physically divergent vertical channel a bounded oscillation) and are fixed as of this version.F[3,0],F[3,4],F[4,0]remain quantitatively incomplete – they omit higher-order Coriolis/transport-rate curvature corrections (roughly 10-20% relative error at mid-latitude); this is a magnitude gap, not a sign error, and is tracked rather than fixed here.F[4,4](d(vE_dot)/d(vE)) is entirely missing: the analytic matrix has it as an implicit zero, the oracle gives a stable +1.5277e-05, of the same kind and order as the attitude block’s missing self-coupling below.Attitude block (rows 6-8): known broadly incomplete. The
-[omega_in^n x] phiself-coupling submatrix (F[6:9,6:9]) is entirely absent, three entries (F[6,4],F[7,3],F[8,4]) have the wrong sign, andF[7,0]differs from the oracle by roughly two orders of magnitude. Of the 12 non-zero entries the oracle exercises in rows 6-8, 11 disagree. Rebuilding this block is out of scope for a patch release; seetests/validation/test_ins_error_matrix.pyfor the full, entry-by-entry inventory (every agreeing entry asserted, every disagreeing entry anxfail(strict=True)with its analytic and numeric values). Counting the whole 9x9 navigation block by the same rule – every entry non-zero in the analytic matrix, or non-zero in the oracle where the analytic matrix has an implicit zero – 29 entries are exercised in total; 15 disagree.Bias-coupling columns (
F[3:6,9:12],F[6:9,12:15], and rows 9-14 generally): unverified, not verified-correct.mechanize_ins_ned()takes no bias arguments, so no finite-difference oracle can reach these entries. They follow directly from substitutingmeasured = true + biasinto the already-checked specific-force/gyro terms, but that has not been independently checked. Treat their state as unknown, not as a clean bill of health inherited from the entries that do pass.
The module
pytcl.navigation.insis classified EXPERIMENTAL inpytcl.core.maturityfor this reason – see that registry entry for the measured figures.
- pytcl.navigation.ins.ins_process_noise_matrix(accel_noise_std, gyro_noise_std, accel_bias_std, gyro_bias_std, state)[source]
Compute the INS process noise covariance matrix (continuous-time Q matrix).
- Parameters:
accel_noise_std (float) – Accelerometer white noise standard deviation (m/s^2).
gyro_noise_std (float) – Gyroscope white noise standard deviation (rad/s).
accel_bias_std (float) – Accelerometer bias random walk standard deviation (m/s^2/sqrt(s)).
gyro_bias_std (float) – Gyroscope bias random walk standard deviation (rad/s/sqrt(s)).
state (INSState) – Current INS state. Accepted for signature stability; the DCM enters the computation only as
R_b_n @ R_b_n.T, which is the identity for any rotation matrix, so the state cannot changeQ.
- Returns:
Q – 15x15 process noise covariance matrix.
- Return type:
ndarray
INS/GNSS Integration
Loosely-coupled and tightly-coupled INS/GNSS integration algorithms.
INS/GNSS Integration algorithms.
This module provides integrated navigation solutions combining Inertial Navigation System (INS) and Global Navigation Satellite System (GNSS): - GNSS measurement models and state representation - Loosely-coupled INS/GNSS integration (position/velocity aiding) - Tightly-coupled INS/GNSS integration (pseudorange/Doppler aiding) - Error state Kalman filter for INS/GNSS fusion
References
P. Groves, “Principles of GNSS, Inertial, and Multisensor Integrated Navigation Systems”, 2nd ed., Artech House, 2013.
Farrell, “Aided Navigation: GPS with High Rate Sensors”, McGraw-Hill, 2008.
R. Brown and P. Hwang, “Introduction to Random Signals and Applied Kalman Filtering”, 4th ed., Wiley, 2012.
- class pytcl.navigation.ins_gnss.GNSSMeasurement(position, velocity, position_cov, velocity_cov, time, valid=True)[source]
Bases:
NamedTupleGNSS measurement data.
- Variables:
position (ndarray, optional) – GNSS position [lat, lon, alt] in (rad, rad, m).
velocity (ndarray, optional) – GNSS velocity in NED frame [vN, vE, vD] (m/s).
position_cov (ndarray, optional) – Position covariance (3x3) in geodetic frame, units
[rad^2, rad^2, m^2]on the diagonal – matchingposition’s(rad, rad, m). It is used directly as the measurement covariance against a(rad, rad, m)innovation, so supplying metres^2 for the latitude/longitude entries (how GNSS accuracy is usually quoted) inflates R by ~1e13; convert withposition_std_to_error_state_units(). This is the v2.0.0 breaking change documented in the migration guide.velocity_cov (ndarray, optional) – Velocity covariance (3x3) in NED frame.
time (float) – GPS time of measurement (seconds).
valid (bool) – Whether the measurement is valid.
- class pytcl.navigation.ins_gnss.SatelliteInfo(prn, position, velocity, pseudorange, doppler=None, cn0=None, elevation=None, azimuth=None)[source]
Bases:
NamedTupleSatellite information for tightly-coupled integration.
- Variables:
prn (int) – Satellite PRN number.
position (ndarray) – Satellite ECEF position [x, y, z] (m).
velocity (ndarray) – Satellite ECEF velocity [vx, vy, vz] (m/s).
pseudorange (float) – Measured pseudorange (m).
doppler (float, optional) – Measured Doppler shift (Hz).
cn0 (float, optional) – Carrier-to-noise ratio (dB-Hz).
elevation (float, optional) – Satellite elevation angle (rad).
azimuth (float, optional) – Satellite azimuth angle (rad).
- class pytcl.navigation.ins_gnss.INSGNSSState(ins_state, error_state, error_cov, clock_bias=0.0, clock_drift=0.0)[source]
Bases:
NamedTupleCombined INS/GNSS navigation state.
- Variables:
ins_state (INSState) – Current INS navigation state.
error_state (ndarray) – Error state vector, 15 elements. (Previously documented “15 or 17”; no code path produces 17 –
tight_coupled_updatebuilds a 16-element extended vector internally and writes backzeros(15).)error_cov (ndarray) – Error state covariance matrix.
clock_bias (float) – Receiver clock bias (m).
clock_drift (float) – Receiver clock drift (m/s).
- class pytcl.navigation.ins_gnss.LooseCoupledResult(state, innovation, innovation_cov)[source]
Bases:
NamedTupleResult from loosely-coupled INS/GNSS update.
- Variables:
state (INSGNSSState) – Updated navigation state.
innovation (ndarray) – Measurement innovation (residual).
innovation_cov (ndarray) – Innovation covariance.
- state: INSGNSSState
Alias for field number 0
- class pytcl.navigation.ins_gnss.TightCoupledResult(state, innovations, dop)[source]
Bases:
NamedTupleResult from tightly-coupled INS/GNSS update.
- Variables:
state (INSGNSSState) – Updated navigation state.
innovations (ndarray) – Pseudorange/Doppler innovations.
dop (tuple) – Dilution of precision (GDOP, PDOP, HDOP, VDOP).
- state: INSGNSSState
Alias for field number 0
- pytcl.navigation.ins_gnss.position_measurement_matrix()[source]
Create measurement matrix for position-only GNSS update.
- Returns:
H – 3x15 measurement matrix mapping error state to position measurement.
- Return type:
ndarray
- pytcl.navigation.ins_gnss.velocity_measurement_matrix()[source]
Create measurement matrix for velocity-only GNSS update.
- Returns:
H – 3x15 measurement matrix mapping error state to velocity measurement.
- Return type:
ndarray
- pytcl.navigation.ins_gnss.position_velocity_measurement_matrix()[source]
Create measurement matrix for position+velocity GNSS update.
- Returns:
H – 6x15 measurement matrix.
- Return type:
ndarray
- pytcl.navigation.ins_gnss.compute_line_of_sight(user_pos, sat_pos)[source]
Compute line-of-sight unit vector and range from user to satellite.
- Parameters:
user_pos (array_like) – User ECEF position [x, y, z] (m).
sat_pos (array_like) – Satellite ECEF position [x, y, z] (m).
- Returns:
los (ndarray) – Line-of-sight unit vector from user to satellite.
range (float) – Geometric range (m).
- Return type:
- pytcl.navigation.ins_gnss.pseudorange_measurement_matrix(user_pos, satellites, include_clock=True)[source]
Create measurement matrix for pseudorange observations.
- Parameters:
user_pos (array_like) – User ECEF position [x, y, z] (m).
satellites (list of SatelliteInfo) – List of satellite information.
include_clock (bool, optional) – Whether to include clock bias column (default: True).
- Returns:
H – (n_sats x 4) or (n_sats x 3) geometry matrix. Columns are [dx, dy, dz, clock_bias] or [dx, dy, dz].
- Return type:
ndarray
- pytcl.navigation.ins_gnss.compute_dop(H, user_lla=None)[source]
Compute Dilution of Precision from a geometry matrix.
HDOP and VDOP are only horizontal and vertical with respect to some local frame. They are read off the first two and the third diagonal entries of
inv(H^T H), so they mean “horizontal” only if the first two position columns ofHspan the local horizontal plane. An ECEF geometry matrix does not: its x and y axes point at the equator regardless of where the user is, so HDOP computed from one is horizontal only at the poles (gh-19).Pass
user_llaand the position columns are rotated into the local ENU frame first, which is what makes the split meaningful. Without it the matrix is used as given, which is correct whenHis already local.GDOP and PDOP are invariant under rotation – they are traces – so they were correct either way and are unaffected by this argument.
- Parameters:
H (array_like) – Geometry matrix (n_sats x 4) with columns [dx, dy, dz, clock].
user_lla (array_like, optional) – User position
[lat, lon, alt]in (rad, rad, m). When given, the three position columns ofHare rotated from ECEF into ENU before the DOP terms are read off. Omit whenHis already expressed in a local frame.
- Returns:
GDOP (float) – Geometric DOP. Rotation-invariant.
PDOP (float) – Position DOP. Rotation-invariant.
HDOP (float) – Horizontal DOP, in the local frame if
user_llawas supplied.VDOP (float) – Vertical DOP, in the local frame if
user_llawas supplied.
- Return type:
- pytcl.navigation.ins_gnss.satellite_elevation_azimuth(user_lla, sat_ecef, ellipsoid=(6378137.0, 0.0033528106647474805))[source]
Compute satellite elevation and azimuth angles from user position.
- Parameters:
user_lla (array_like) – User position [lat, lon, alt] in (rad, rad, m).
sat_ecef (array_like) – Satellite ECEF position [x, y, z] (m).
ellipsoid (Ellipsoid, optional) – Reference ellipsoid (default: WGS84).
- Returns:
elevation (float) – Elevation angle in radians.
azimuth (float) – Azimuth angle in radians (from north, clockwise).
- Return type:
- pytcl.navigation.ins_gnss.initialize_ins_gnss(ins_state, position_std=10.0, velocity_std=1.0, attitude_std=0.1, accel_bias_std=0.1, gyro_bias_std=0.01)[source]
Initialize INS/GNSS integration state.
- Parameters:
ins_state (INSState) – Initial INS navigation state.
position_std (float, optional) – Initial position uncertainty (m). Default: 10.0.
velocity_std (float, optional) – Initial velocity uncertainty (m/s). Default: 1.0.
attitude_std (float, optional) – Initial attitude uncertainty (rad). Default: 0.1.
accel_bias_std (float, optional) – Initial accelerometer bias uncertainty (m/s^2). Default: 0.1.
gyro_bias_std (float, optional) – Initial gyroscope bias uncertainty (rad/s). Default: 0.01.
- Returns:
state – Initialized INS/GNSS state.
- Return type:
- pytcl.navigation.ins_gnss.loose_coupled_predict(state, imu, accel_noise_std=0.01, gyro_noise_std=0.0001, accel_bias_std=1e-05, gyro_bias_std=1e-07, accel_prev=None, gyro_prev=None)[source]
Perform prediction step for loosely-coupled INS/GNSS.
- Parameters:
state (INSGNSSState) – Current INS/GNSS state.
imu (IMUData) – IMU measurements for this time step.
accel_noise_std (float, optional) – Accelerometer noise std (m/s^2). Default: 0.01.
gyro_noise_std (float, optional) – Gyroscope noise std (rad/s). Default: 1e-4.
accel_bias_std (float, optional) – Accelerometer bias random walk std. Default: 1e-5.
gyro_bias_std (float, optional) – Gyroscope bias random walk std. Default: 1e-7.
accel_prev (array_like, optional) – Previous accelerometer reading for coning/sculling.
gyro_prev (array_like, optional) – Previous gyroscope reading for coning/sculling.
- Returns:
state – Predicted state.
- Return type:
Warning
The covariance propagation here (F = I + F_cont * dt) is driven by
pytcl.navigation.ins.ins_error_state_matrix(), which is EXPERIMENTAL as of v2.11.1: its attitude-error rows are known to disagree with a numeric oracle in 11 of 12 non-zero entries (the-[omega_in^n x] phiself-coupling submatrix is entirely absent), so covariance growth inerror_cov[6:9, 6:9]and its cross-terms is not trustworthy. Seepytcl.navigation.ins.ins_error_state_matrix’s docstring andtests/validation/test_ins_error_matrix.pyfor the full inventory of which entries are verified and which are not.
- pytcl.navigation.ins_gnss.loose_coupled_update_position(state, gnss)[source]
Update INS/GNSS state with GNSS position measurement.
- Parameters:
state (INSGNSSState) – Current INS/GNSS state.
gnss (GNSSMeasurement) – GNSS position measurement.
- Returns:
result – Updated state and innovation statistics.
- Return type:
- pytcl.navigation.ins_gnss.loose_coupled_update_velocity(state, gnss)[source]
Update INS/GNSS state with GNSS velocity measurement.
- Parameters:
state (INSGNSSState) – Current INS/GNSS state.
gnss (GNSSMeasurement) – GNSS velocity measurement.
- Returns:
result – Updated state and innovation statistics.
- Return type:
- pytcl.navigation.ins_gnss.loose_coupled_update(state, gnss)[source]
Update INS/GNSS state with GNSS position and velocity measurements.
- Parameters:
state (INSGNSSState) – Current INS/GNSS state.
gnss (GNSSMeasurement) – GNSS measurement with position and/or velocity.
- Returns:
result – Updated state and innovation statistics.
- Return type:
- pytcl.navigation.ins_gnss.tight_coupled_pseudorange_innovation(state, satellites, ellipsoid=(6378137.0, 0.0033528106647474805))[source]
Compute pseudorange innovations for tightly-coupled update.
- Parameters:
state (INSGNSSState) – Current INS/GNSS state.
satellites (list of SatelliteInfo) – Satellite observations.
ellipsoid (Ellipsoid, optional) – Reference ellipsoid.
- Returns:
innovations (ndarray) – Pseudorange innovations (measured - predicted).
predicted (ndarray) – Predicted pseudoranges.
- Return type:
Tuple[ndarray[tuple[Any, …], dtype[floating]], ndarray[tuple[Any, …], dtype[floating]]]
- pytcl.navigation.ins_gnss.tight_coupled_measurement_matrix(state, satellites, ellipsoid=(6378137.0, 0.0033528106647474805))[source]
Compute measurement matrix for tightly-coupled pseudorange update.
Maps a 17-column error layout (15 INS + clock bias + clock drift) to pseudorange measurements. Only the clock-BIAS column (index 15) is ever populated; the drift column is identically zero, and
tight_coupled_updateslices the matrix to its first 16 columns before use.- Parameters:
state (INSGNSSState) – Current INS/GNSS state.
satellites (list of SatelliteInfo) – Satellite observations.
ellipsoid (Ellipsoid, optional) – Reference ellipsoid.
- Returns:
H – (n_sats x 17) measurement matrix.
- Return type:
ndarray
- pytcl.navigation.ins_gnss.tight_coupled_update(state, satellites, pseudorange_std=3.0, ellipsoid=(6378137.0, 0.0033528106647474805))[source]
Perform tightly-coupled INS/GNSS update using pseudoranges.
- Parameters:
state (INSGNSSState) – Current INS/GNSS state.
satellites (list of SatelliteInfo) – Satellite observations with pseudoranges.
pseudorange_std (float, optional) – Pseudorange measurement noise std (m). Default: 3.0.
ellipsoid (Ellipsoid, optional) – Reference ellipsoid.
- Returns:
result – Updated state and DOP values.
- Return type:
Geodesy
Geodetic computations and reference ellipsoid functions.
Geodetic calculations for navigation and tracking.
This module provides geodetic utilities including: - Geodetic to ECEF conversions - Direct and inverse geodetic problems - Local tangent plane (ENU/NED) conversions - Earth ellipsoid parameters
- class pytcl.navigation.geodesy.Ellipsoid(a, f)[source]
Bases:
NamedTupleEarth ellipsoid parameters.
- Variables:
- pytcl.navigation.geodesy.geodetic_to_ecef(lat, lon, alt, ellipsoid=(6378137.0, 0.0033528106647474805))[source]
Convert geodetic coordinates to ECEF (Earth-Centered, Earth-Fixed).
- Parameters:
lat (array_like) – Geodetic latitude in radians.
lon (array_like) – Geodetic longitude in radians.
alt (array_like) – Altitude above ellipsoid in meters.
ellipsoid (Ellipsoid, optional) – Reference ellipsoid (default: WGS84).
- Returns:
x, y, z – ECEF coordinates in meters.
- Return type:
ndarray
Examples
>>> import numpy as np >>> # Philadelphia (40°N, 75°W) at 100m altitude >>> lat, lon, alt = np.radians(40.0), np.radians(-75.0), 100.0 >>> x, y, z = geodetic_to_ecef(lat, lon, alt) >>> round(x / 1e6, 4) # ~1.27 million meters 1.2663 >>> # Equator at prime meridian >>> x, y, z = geodetic_to_ecef(0.0, 0.0, 0.0) >>> x # Semi-major axis (equatorial radius) 6378137.0 >>> y, z (0.0, 0.0)
- pytcl.navigation.geodesy.ecef_to_geodetic(x, y, z, ellipsoid=(6378137.0, 0.0033528106647474805))[source]
Convert ECEF coordinates to geodetic.
- Parameters:
x (array_like) – ECEF coordinates in meters.
y (array_like) – ECEF coordinates in meters.
z (array_like) – ECEF coordinates in meters.
ellipsoid (Ellipsoid, optional) – Reference ellipsoid (default: WGS84).
- Returns:
lat (ndarray) – Geodetic latitude in radians.
lon (ndarray) – Geodetic longitude in radians.
alt (ndarray) – Altitude above ellipsoid in meters.
- Return type:
Tuple[ndarray[tuple[Any, …], dtype[float64]], ndarray[tuple[Any, …], dtype[float64]], ndarray[tuple[Any, …], dtype[float64]]]
Examples
>>> import numpy as np >>> # Point on equator at prime meridian >>> lat, lon, alt = ecef_to_geodetic(6378137.0, 0.0, 0.0) >>> np.degrees(lat), np.degrees(lon), float(alt) (0.0, 0.0, 0.0) >>> # Round-trip conversion >>> x, y, z = geodetic_to_ecef(np.radians(45.0), np.radians(90.0), 1000.0) >>> lat2, lon2, alt2 = ecef_to_geodetic(x, y, z) >>> round(np.degrees(lat2), 6), round(np.degrees(lon2), 6), round(float(alt2), 3) (45.0, 90.0, 1000.0)
Notes
Uses Bowring’s iterative algorithm for robust conversion.
References
Bowring, B.R., “Transformation from spatial to geographical coordinates”, Survey Review, 1976.
- pytcl.navigation.geodesy.ecef_to_enu(x, y, z, lat_ref, lon_ref, alt_ref, ellipsoid=(6378137.0, 0.0033528106647474805))[source]
Convert ECEF to local ENU (East-North-Up) coordinates.
- Parameters:
x (array_like) – ECEF coordinates in meters.
y (array_like) – ECEF coordinates in meters.
z (array_like) – ECEF coordinates in meters.
lat_ref (float) – Reference latitude in radians.
lon_ref (float) – Reference longitude in radians.
alt_ref (float) – Reference altitude in meters.
ellipsoid (Ellipsoid, optional) – Reference ellipsoid (default: WGS84).
- Returns:
east, north, up – ENU coordinates in meters relative to reference point.
- Return type:
ndarray
Examples
>>> import numpy as np >>> # Reference point: Philadelphia >>> lat_ref, lon_ref, alt_ref = np.radians(40.0), np.radians(-75.0), 0.0 >>> # Target point slightly east >>> lat, lon, alt = np.radians(40.0), np.radians(-74.99), 0.0 >>> x, y, z = geodetic_to_ecef(lat, lon, alt) >>> e, n, u = ecef_to_enu(x, y, z, lat_ref, lon_ref, alt_ref) >>> round(e, 1) # East displacement in meters 853.9 >>> abs(n) < 10 # North displacement should be ~0 True >>> abs(u) < 10 # Up displacement should be ~0 True
- pytcl.navigation.geodesy.enu_to_ecef(east, north, up, lat_ref, lon_ref, alt_ref, ellipsoid=(6378137.0, 0.0033528106647474805))[source]
Convert local ENU to ECEF coordinates.
- Parameters:
east (array_like) – ENU coordinates in meters relative to reference point.
north (array_like) – ENU coordinates in meters relative to reference point.
up (array_like) – ENU coordinates in meters relative to reference point.
lat_ref (float) – Reference latitude in radians.
lon_ref (float) – Reference longitude in radians.
alt_ref (float) – Reference altitude in meters.
ellipsoid (Ellipsoid, optional) – Reference ellipsoid (default: WGS84).
- Returns:
x, y, z – ECEF coordinates in meters.
- Return type:
ndarray
Examples
>>> import numpy as np >>> # Reference point >>> lat_ref, lon_ref, alt_ref = np.radians(40.0), np.radians(-75.0), 0.0 >>> # 1 km east, 500 m north, 100 m up >>> x, y, z = enu_to_ecef(1000.0, 500.0, 100.0, lat_ref, lon_ref, alt_ref) >>> # Convert back to verify >>> e, n, u = ecef_to_enu(x, y, z, lat_ref, lon_ref, alt_ref) >>> round(e, 3), round(n, 3), round(u, 3) (1000.0, 500.0, 100.0)
- pytcl.navigation.geodesy.ecef_to_ned(x, y, z, lat_ref, lon_ref, alt_ref, ellipsoid=(6378137.0, 0.0033528106647474805))[source]
Convert ECEF to local NED (North-East-Down) coordinates.
- Parameters:
x (array_like) – ECEF coordinates in meters.
y (array_like) – ECEF coordinates in meters.
z (array_like) – ECEF coordinates in meters.
lat_ref (float) – Reference latitude in radians.
lon_ref (float) – Reference longitude in radians.
alt_ref (float) – Reference altitude in meters.
ellipsoid (Ellipsoid, optional) – Reference ellipsoid (default: WGS84).
- Returns:
north, east, down – NED coordinates in meters relative to reference point.
- Return type:
ndarray
Examples
>>> import numpy as np >>> # Reference point >>> lat_ref, lon_ref, alt_ref = np.radians(40.0), np.radians(-75.0), 0.0 >>> # Target above reference >>> x, y, z = geodetic_to_ecef(lat_ref, lon_ref, 1000.0) # 1km above >>> n, e, d = ecef_to_ned(x, y, z, lat_ref, lon_ref, alt_ref) >>> abs(n) < 1, abs(e) < 1, d # Should be ~0, ~0, -1000 (True, True, -1000.0...)
- pytcl.navigation.geodesy.ned_to_ecef(north, east, down, lat_ref, lon_ref, alt_ref, ellipsoid=(6378137.0, 0.0033528106647474805))[source]
Convert local NED to ECEF coordinates.
- Parameters:
north (array_like) – NED coordinates in meters relative to reference point.
east (array_like) – NED coordinates in meters relative to reference point.
down (array_like) – NED coordinates in meters relative to reference point.
lat_ref (float) – Reference latitude in radians.
lon_ref (float) – Reference longitude in radians.
alt_ref (float) – Reference altitude in meters.
ellipsoid (Ellipsoid, optional) – Reference ellipsoid (default: WGS84).
- Returns:
x, y, z – ECEF coordinates in meters.
- Return type:
ndarray
Examples
>>> import numpy as np >>> lat_ref, lon_ref, alt_ref = np.radians(40.0), np.radians(-75.0), 0.0 >>> # 100m north, 50m east, 10m down >>> x, y, z = ned_to_ecef(100.0, 50.0, 10.0, lat_ref, lon_ref, alt_ref) >>> # Verify round-trip >>> n, e, d = ecef_to_ned(x, y, z, lat_ref, lon_ref, alt_ref) >>> n, e, d (100.0..., 50.0..., 10.0...)
- pytcl.navigation.geodesy.direct_geodetic(lat1, lon1, azimuth, distance, ellipsoid=(6378137.0, 0.0033528106647474805))[source]
Solve the direct geodetic problem (Vincenty).
Given a starting point, azimuth, and distance, find the destination point. Results are cached for repeated queries with the same parameters.
- Parameters:
- Returns:
lat2 (float) – Destination latitude in radians.
lon2 (float) – Destination longitude in radians.
azimuth2 (float) – Back azimuth at destination in radians.
- Return type:
Examples
>>> import numpy as np >>> # From New York, travel 1000 km northeast >>> lat1, lon1 = np.radians(40.7), np.radians(-74.0) >>> azimuth = np.radians(45) # Northeast >>> distance = 1_000_000 # 1000 km >>> lat2, lon2, az2 = direct_geodetic(lat1, lon1, azimuth, distance) >>> round(np.degrees(lat2), 4), round(np.degrees(lon2), 4) # Destination (46.7148, -64.7496)
References
Vincenty, T., “Direct and Inverse Solutions of Geodesics on the Ellipsoid with Application of Nested Equations”, Survey Review, 1975.
- pytcl.navigation.geodesy.inverse_geodetic(lat1, lon1, lat2, lon2, ellipsoid=(6378137.0, 0.0033528106647474805))[source]
Solve the inverse geodetic problem (Vincenty).
Given two points, find the distance and azimuths between them. Results are cached for repeated queries with the same coordinates.
- Parameters:
- Returns:
distance (float) – Geodesic distance in meters.
azimuth1 (float) – Forward azimuth at start in radians.
azimuth2 (float) – Back azimuth at destination in radians.
- Return type:
Examples
>>> import numpy as np >>> # Distance from New York to London >>> lat1, lon1 = np.radians(40.7128), np.radians(-74.0060) # NYC >>> lat2, lon2 = np.radians(51.5074), np.radians(-0.1278) # London >>> dist, az1, az2 = inverse_geodetic(lat1, lon1, lat2, lon2) >>> round(dist / 1000, 1) # Geodesic distance in km 5585.2 >>> np.degrees(az1) # Initial heading from NYC 51.2...
Notes
For nearly antipodal points, where Vincenty’s iteration does not converge, the computation falls back to geographiclib’s Karney algorithm (the
geodesyextra) or raisesConvergenceErrorwhen it is not installed – it never returns a silently wrong result (the unconverged partial answer was measured 3.7 km / 22 degrees off).References
Vincenty, T., “Direct and Inverse Solutions of Geodesics on the Ellipsoid with Application of Nested Equations”, Survey Review, 1975.
- pytcl.navigation.geodesy.haversine_distance(lat1, lon1, lat2, lon2, radius=6371000.0)[source]
Compute great-circle distance using the haversine formula.
- Parameters:
- Returns:
Great-circle distance in meters.
- Return type:
Examples
>>> import numpy as np >>> # Distance from equator to 45°N along prime meridian >>> lat1, lon1 = 0.0, 0.0 >>> lat2, lon2 = np.radians(45.0), 0.0 >>> dist = haversine_distance(lat1, lon1, lat2, lon2) >>> dist / 1000 # ~5000 km 5003... >>> # Same point -> 0 distance >>> haversine_distance(0.0, 0.0, 0.0, 0.0) 0.0
Notes
This is a spherical approximation. For higher accuracy on an ellipsoid, use inverse_geodetic().
- class pytcl.navigation.geodesy.OsculatingSphere(radius, center)[source]
Bases:
NamedTupleA local spherical approximation to the reference ellipsoid.
- Variables:
radius (float) – Radius of the osculating sphere in meters.
center (ndarray) – Offset of the sphere’s center from the ellipsoid’s center in ECEF coordinates, shape (3,). Subtract this from ECEF positions to work in the sphere-centered frame.
- pytcl.navigation.geodesy.osculating_sphere(lat, lon, ellipsoid=(6378137.0, 0.0033528106647474805))[source]
Osculating sphere of the reference ellipsoid at a surface point.
The sphere has the Gaussian radius of curvature
sqrt(M * N)(the geometric mean of the meridian and prime-vertical radii of curvature) and is positioned to touch the ellipsoid at the given latitude and longitude. Used as the local spherical-Earth approximation in the standard-exponential-model refraction routines.Port of
osculatingSpher4LatLon.m(the default 3D, no-geoid path).- Parameters:
- Returns:
sphere – The radius and the ECEF offset of the sphere’s center.
- Return type:
Examples
>>> sph = osculating_sphere(0.7, 0.1) >>> print(f"{sph.radius:.3f}") 6374462.341
Great Circle Navigation
Shortest-path (orthodrome) navigation on a spherical Earth.
Great circle navigation algorithms.
This module provides great circle (orthodrome) navigation functions for computing the shortest path on a sphere, including: - Great circle distance and initial azimuth - Intermediate waypoint computation - Great circle intersection - Cross-track and along-track distances - TDOA localization on a sphere
- class pytcl.navigation.great_circle.GreatCircleResult(distance, azimuth1, azimuth2)[source]
Bases:
NamedTupleResult of great circle computation.
- Variables:
- class pytcl.navigation.great_circle.WaypointResult(lat, lon)[source]
Bases:
NamedTupleResult of waypoint computation.
- Variables:
- class pytcl.navigation.great_circle.IntersectionResult(lat1, lon1, lat2, lon2, valid)[source]
Bases:
NamedTupleResult of great circle intersection.
- Variables:
lat1 (float) – Latitude of first intersection in radians.
lon1 (float) – Longitude of first intersection in radians.
lat2 (float) – Latitude of second intersection (antipodal) in radians.
lon2 (float) – Longitude of second intersection (antipodal) in radians.
valid (bool) – True if intersection exists.
- class pytcl.navigation.great_circle.CrossTrackResult(cross_track, along_track)[source]
Bases:
NamedTupleResult of cross-track distance computation.
- Variables:
- pytcl.navigation.great_circle.great_circle_distance(lat1, lon1, lat2, lon2, radius=6371000.0)[source]
Compute great circle distance between two points.
Uses the haversine formula for numerical stability at small distances. Results are cached for repeated queries with the same coordinates.
- Parameters:
- Returns:
Great circle distance in meters.
- Return type:
Examples
>>> import numpy as np >>> # New York to London >>> lat1, lon1 = np.radians(40.7128), np.radians(-74.0060) >>> lat2, lon2 = np.radians(51.5074), np.radians(-0.1278) >>> dist = great_circle_distance(lat1, lon1, lat2, lon2) >>> print(f"Distance: {dist/1000:.0f} km") Distance: 5570 km
- pytcl.navigation.great_circle.great_circle_azimuth(lat1, lon1, lat2, lon2)[source]
Compute initial azimuth (bearing) from point 1 to point 2.
Results are cached for repeated queries with the same coordinates.
- Parameters:
- Returns:
Initial azimuth in radians (from north, clockwise, 0 to 2π).
- Return type:
Examples
>>> import numpy as np >>> # Bearing from New York to London >>> lat1, lon1 = np.radians(40.7128), np.radians(-74.0060) >>> lat2, lon2 = np.radians(51.5074), np.radians(-0.1278) >>> az = great_circle_azimuth(lat1, lon1, lat2, lon2) >>> print(f"Initial bearing: {np.degrees(az):.1f}°") Initial bearing: 51.2°
- pytcl.navigation.great_circle.great_circle_inverse(lat1, lon1, lat2, lon2, radius=6371000.0)[source]
Solve the inverse great circle problem.
Given two points, compute distance and azimuths.
- Parameters:
- Returns:
Distance and azimuths.
- Return type:
Examples
>>> lat1, lon1 = np.radians(40.7128), np.radians(-74.0060) # NYC >>> lat2, lon2 = np.radians(51.5074), np.radians(-0.1278) # London >>> result = great_circle_inverse(lat1, lon1, lat2, lon2) >>> result.distance > 5000000 # Over 5000 km True
- pytcl.navigation.great_circle.angular_distance(lat1, lon1, lat2, lon2)[source]
Compute angular distance (central angle) between two points.
- Parameters:
- Returns:
Angular distance in radians.
- Return type:
Examples
Compute angular distance between New York and London:
>>> import numpy as np >>> # NYC: 40.7°N, 74.0°W; London: 51.5°N, 0.1°W >>> lat1, lon1 = np.radians(40.7), np.radians(-74.0) >>> lat2, lon2 = np.radians(51.5), np.radians(-0.1) >>> angle = angular_distance(lat1, lon1, lat2, lon2) >>> round(np.degrees(angle), 2) # about 50 degrees 50.12
See also
great_circle_distanceCompute distance on sphere with given radius.
- pytcl.navigation.great_circle.great_circle_waypoint(lat1, lon1, lat2, lon2, fraction)[source]
Compute intermediate waypoint along a great circle.
- Parameters:
- Returns:
Latitude and longitude of waypoint.
- Return type:
Examples
>>> import numpy as np >>> # Midpoint between New York and London >>> lat1, lon1 = np.radians(40.7128), np.radians(-74.0060) >>> lat2, lon2 = np.radians(51.5074), np.radians(-0.1278) >>> mid = great_circle_waypoint(lat1, lon1, lat2, lon2, 0.5) >>> print(f"Midpoint: {np.degrees(mid.lat):.2f}°, {np.degrees(mid.lon):.2f}°") Midpoint: 52.37°, -41.29°
- pytcl.navigation.great_circle.great_circle_waypoints(lat1, lon1, lat2, lon2, n_points)[source]
Compute multiple waypoints along a great circle.
- Parameters:
- Returns:
lats, lons – Arrays of waypoint latitudes and longitudes in radians.
- Return type:
ndarray
Examples
>>> lat1, lon1 = 0.0, 0.0 >>> lat2, lon2 = np.pi/4, np.pi/4 >>> lats, lons = great_circle_waypoints(lat1, lon1, lat2, lon2, 5) >>> len(lats) 5
- pytcl.navigation.great_circle.great_circle_direct(lat1, lon1, azimuth, distance, radius=6371000.0)[source]
Solve the direct great circle problem.
Given starting point, azimuth, and distance, find destination.
- Parameters:
- Returns:
Destination latitude and longitude.
- Return type:
Examples
>>> import numpy as np >>> # 1000 km northeast from origin >>> lat, lon = 0.0, 0.0 >>> az = np.radians(45) # Northeast >>> dest = great_circle_direct(lat, lon, az, 1000000) >>> print(f"Destination: {np.degrees(dest.lat):.2f}°, {np.degrees(dest.lon):.2f}°") Destination: 6.35°, 6.39°
- pytcl.navigation.great_circle.destination_point(lat, lon, bearing, angular_distance)[source]
Compute destination point given start, bearing, and angular distance.
- Parameters:
- Returns:
Destination coordinates.
- Return type:
Examples
>>> lat, lon = 0.0, 0.0 >>> bearing = np.radians(90) # Due East >>> ang_dist = np.radians(10) # 10 degrees >>> dest = destination_point(lat, lon, bearing, ang_dist) >>> round(np.degrees(dest.lon), 6) # Should be ~10 degrees East 10.0
- pytcl.navigation.great_circle.cross_track_distance(lat_point, lon_point, lat1, lon1, lat2, lon2, radius=6371000.0)[source]
Compute cross-track and along-track distances.
The cross-track distance is the perpendicular distance from a point to the great circle path between two other points.
- Parameters:
lat_point (float) – Point coordinates in radians.
lon_point (float) – Point coordinates in radians.
lat1 (float) – Path start coordinates in radians.
lon1 (float) – Path start coordinates in radians.
lat2 (float) – Path end coordinates in radians.
lon2 (float) – Path end coordinates in radians.
radius (float, optional) – Sphere radius in meters (default: 6371 km).
- Returns:
Cross-track distance (positive = right of path) and signed along-track distance (positive = ahead of the start point).
- Return type:
Notes
Positive cross-track means the point is to the right of the path (when traveling from start to end). Positive along-track means the point’s projection onto the path is ahead of the start point (toward the end point); negative means it falls behind the start point.
Examples
>>> # Point near a path from origin to northeast >>> lat_pt, lon_pt = np.radians(5), np.radians(2) >>> lat1, lon1 = 0.0, 0.0 >>> lat2, lon2 = np.radians(10), np.radians(10) >>> result = cross_track_distance(lat_pt, lon_pt, lat1, lon1, lat2, lon2) >>> abs(result.cross_track) < 500000 # Within 500 km True
- pytcl.navigation.great_circle.great_circle_intersect(lat1, lon1, azimuth1, lat2, lon2, azimuth2)[source]
Find intersection of two great circles.
Given two points with initial bearings, find where the great circles defined by those bearings intersect.
Port of
greatCircleIntersect.m’s branch selection (Baselga & Martinez-Llario 2018); the underlying vector algebra (intersecting the two great-circle planes via their normals’ cross product) is this module’s own, not MATLAB’s spherical-triangle formulation.- Parameters:
lat1 (float) – First point coordinates in radians.
lon1 (float) – First point coordinates in radians.
azimuth1 (float) – Bearing from first point in radians.
lat2 (float) – Second point coordinates in radians.
lon2 (float) – Second point coordinates in radians.
azimuth2 (float) – Bearing from second point in radians.
- Returns:
Two intersection points (antipodal) and validity flag.
- Return type:
Notes
Great circles always intersect at two antipodal points (unless they are identical or parallel). The result’s
lat1/lon1is the intersection reached by a positive distance headingazimuth1from the inputlat1/lon1, not whichever antipodal point the underlying vector algebra happens to return – and not necessarily the intersection point nearest tolat1/lon1by great-circle distance, which for a forward distance beyond a quarter-circle (pi/2) is the other, antipodal point instead. The branch selection is a port of MATLAB’sgreatCircleIntersect.m(Baselga & Martinez-Llario 2018), which selects the positive-distance solution.Examples
>>> lat1, lon1 = 0.0, 0.0 >>> az1 = np.radians(45) # Northeast >>> lat2, lon2 = 0.0, np.radians(10) >>> az2 = np.radians(315) # Northwest >>> result = great_circle_intersect(lat1, lon1, az1, lat2, lon2, az2) >>> result.valid True
- pytcl.navigation.great_circle.great_circle_path_intersect(lat1a, lon1a, lat2a, lon2a, lat1b, lon1b, lat2b, lon2b)[source]
Find intersection of two great circle paths (defined by endpoints).
- Parameters:
lat1a (float) – Start of first path in radians.
lon1a (float) – Start of first path in radians.
lat2a (float) – End of first path in radians.
lon2a (float) – End of first path in radians.
lat1b (float) – Start of second path in radians.
lon1b (float) – Start of second path in radians.
lat2b (float) – End of second path in radians.
lon2b (float) – End of second path in radians.
- Returns:
Intersection points and validity.
- Return type:
Examples
>>> # Two crossing paths >>> result = great_circle_path_intersect( ... 0.0, 0.0, np.radians(10), np.radians(10), # Path A ... 0.0, np.radians(10), np.radians(10), 0.0 # Path B ... ) >>> result.valid True
- pytcl.navigation.great_circle.clear_great_circle_cache()[source]
Clear all great circle computation caches.
This can be useful to free memory after processing large datasets or when cache statistics are being monitored.
Examples
>>> import numpy as np >>> from pytcl.navigation import great_circle_distance >>> from pytcl.navigation.great_circle import clear_great_circle_cache, get_cache_info >>> # Compute a few distances (cached) >>> d1 = great_circle_distance(0, 0, np.radians(1), 0) >>> d2 = great_circle_distance(0, 0, np.radians(2), 0) >>> # Clear all cached values >>> clear_great_circle_cache() >>> # Cache is now empty >>> get_cache_info()['distance']['currsize'] 0
- pytcl.navigation.great_circle.get_cache_info()[source]
Get cache statistics for great circle computations.
- Returns:
Dictionary with cache statistics for distance and azimuth caches.
- Return type:
Examples
>>> import numpy as np >>> from pytcl.navigation import great_circle_distance >>> from pytcl.navigation.great_circle import get_cache_info, clear_great_circle_cache >>> # Clear cache first >>> clear_great_circle_cache() >>> # Compute some distances (multiple calls to test cache hits) >>> d1 = great_circle_distance(0, 0, np.radians(1), 0) >>> d1_again = great_circle_distance(0, 0, np.radians(1), 0) # Cache hit >>> # Get cache statistics >>> info = get_cache_info() >>> info['distance']['hits'] > 0 # Should have at least one hit True >>> info['distance']['currsize'] > 0 # Cache is not empty True
See also
clear_great_circle_cacheClear all cached values.
Rhumb Line Navigation
Constant-bearing (loxodrome) navigation on spherical and ellipsoidal Earth.
Rhumb line (loxodrome) navigation algorithms.
This module provides rhumb line navigation functions for computing constant-bearing paths on a sphere and ellipsoid, including: - Rhumb line distance and bearing - Direct and inverse rhumb problems - Rhumb line intersection - Spherical and ellipsoidal formulations
- class pytcl.navigation.rhumb.RhumbResult(distance, bearing)[source]
Bases:
NamedTupleResult of rhumb line computation.
- Variables:
- class pytcl.navigation.rhumb.RhumbDirectResult(lat, lon)[source]
Bases:
NamedTupleResult of direct rhumb problem.
- Variables:
- class pytcl.navigation.rhumb.RhumbIntersectionResult(lat, lon, valid)[source]
Bases:
NamedTupleResult of rhumb line intersection.
- Variables:
- pytcl.navigation.rhumb.rhumb_distance_spherical(lat1, lon1, lat2, lon2, radius=6371000.0)[source]
Compute rhumb line distance on a sphere.
- Parameters:
- Returns:
Rhumb line distance in meters.
- Return type:
Examples
>>> import numpy as np >>> lat1, lon1 = np.radians(40), np.radians(-74) >>> lat2, lon2 = np.radians(51), np.radians(0) >>> dist = rhumb_distance_spherical(lat1, lon1, lat2, lon2)
- pytcl.navigation.rhumb.rhumb_bearing(lat1, lon1, lat2, lon2)[source]
Compute rhumb line bearing from point 1 to point 2.
- Parameters:
- Returns:
Constant bearing in radians (from north, clockwise, 0 to 2π).
- Return type:
Examples
>>> import numpy as np >>> lat1, lon1 = np.radians(40), np.radians(-74) >>> lat2, lon2 = np.radians(51), np.radians(0) >>> bearing = rhumb_bearing(lat1, lon1, lat2, lon2) >>> print(f"Bearing: {np.degrees(bearing):.1f}°") Bearing: 78.0°
- pytcl.navigation.rhumb.indirect_rhumb_spherical(lat1, lon1, lat2, lon2, radius=6371000.0)[source]
Solve the indirect rhumb problem on a sphere.
Given two points, find the rhumb line distance and bearing.
- Parameters:
- Returns:
Distance and constant bearing.
- Return type:
Examples
>>> import numpy as np >>> lat1, lon1 = np.radians(40), np.radians(-74) # New York >>> lat2, lon2 = np.radians(51), np.radians(0) # London >>> result = indirect_rhumb_spherical(lat1, lon1, lat2, lon2) >>> result.distance > 5000000 # Over 5000 km True
- pytcl.navigation.rhumb.direct_rhumb_spherical(lat1, lon1, bearing, distance, radius=6371000.0)[source]
Solve the direct rhumb problem on a sphere.
Given starting point, bearing, and distance, find destination.
- Parameters:
- Returns:
Destination latitude and longitude.
- Return type:
Examples
>>> import numpy as np >>> lat, lon = np.radians(40), np.radians(-74) >>> bearing = np.radians(90) # Due east >>> dest = direct_rhumb_spherical(lat, lon, bearing, 1000000)
- pytcl.navigation.rhumb.rhumb_distance_ellipsoidal(lat1, lon1, lat2, lon2, ellipsoid=(6378137.0, 0.0033528106647474805))[source]
Compute rhumb line distance on an ellipsoid.
- Parameters:
- Returns:
Rhumb line distance in meters.
- Return type:
Examples
>>> import numpy as np >>> lat1, lon1 = np.radians(40), np.radians(-74) >>> lat2, lon2 = np.radians(51), np.radians(0) >>> dist = rhumb_distance_ellipsoidal(lat1, lon1, lat2, lon2) >>> dist > 5000000 # Over 5000 km True
Notes
The meridional radius is evaluated once at the midpoint latitude rather than integrated along the leg, which is exact only for a leg short enough that the radius is effectively constant (gh-25).
Quantified: about 0.05% on legs spanning roughly 45 degrees of latitude – 4.5 km over 9,800 km – and up to about 400 m on a 3,000 km due-north leg. Sub-meter on typical legs. Round trips through
direct_rhumbare self-consistent to under a millimeter because the same approximation appears in both directions and cancels; that consistency is therefore not evidence of accuracy.
- pytcl.navigation.rhumb.indirect_rhumb(lat1, lon1, lat2, lon2, ellipsoid=(6378137.0, 0.0033528106647474805))[source]
Solve the indirect rhumb problem on an ellipsoid.
- Parameters:
- Returns:
Distance and constant bearing.
- Return type:
Examples
>>> import numpy as np >>> lat1, lon1 = np.radians(40), np.radians(-74) # New York >>> lat2, lon2 = np.radians(51), np.radians(0) # London >>> result = indirect_rhumb(lat1, lon1, lat2, lon2) >>> 0 < result.bearing < np.pi # Eastward bearing True
- pytcl.navigation.rhumb.direct_rhumb(lat1, lon1, bearing, distance, ellipsoid=(6378137.0, 0.0033528106647474805))[source]
Solve the direct rhumb problem on an ellipsoid.
- Parameters:
- Returns:
Destination latitude and longitude.
- Return type:
Examples
>>> import numpy as np >>> lat, lon = np.radians(40), np.radians(-74) >>> bearing = np.radians(90) # Due east >>> dest = direct_rhumb(lat, lon, bearing, 100000) # 100 km >>> np.degrees(dest.lon) > -74 # Moved east True
- pytcl.navigation.rhumb.rhumb_intersect(lat1, lon1, bearing1, lat2, lon2, bearing2)[source]
Find intersection of two rhumb lines.
- Parameters:
lat1 (float) – First point coordinates in radians.
lon1 (float) – First point coordinates in radians.
bearing1 (float) – Bearing from first point in radians.
lat2 (float) – Second point coordinates in radians.
lon2 (float) – Second point coordinates in radians.
bearing2 (float) – Bearing from second point in radians.
- Returns:
Intersection point and validity flag.
- Return type:
Examples
>>> import numpy as np >>> lat1, lon1 = np.radians(40), np.radians(-74) >>> lat2, lon2 = np.radians(51), np.radians(0) >>> bearing1 = np.radians(45) >>> bearing2 = np.radians(270) >>> result = rhumb_intersect(lat1, lon1, bearing1, lat2, lon2, bearing2) >>> result.valid # May or may not intersect True
Notes
Unlike great circles, two rhumb lines may not intersect (if bearings are parallel or if the lines diverge before meeting).
- pytcl.navigation.rhumb.rhumb_midpoint(lat1, lon1, lat2, lon2)[source]
Compute midpoint along a rhumb line.
- Parameters:
- Returns:
Midpoint latitude and longitude.
- Return type:
Examples
>>> import numpy as np >>> lat1, lon1 = np.radians(0), np.radians(0) >>> lat2, lon2 = np.radians(10), np.radians(10) >>> mid = rhumb_midpoint(lat1, lon1, lat2, lon2) >>> np.isclose(np.degrees(mid.lat), 5, atol=0.1) True
- pytcl.navigation.rhumb.rhumb_waypoints(lat1, lon1, lat2, lon2, n_points, radius=6371000.0)[source]
Compute multiple waypoints along a rhumb line.
- Parameters:
lat1 (float) – Starting point coordinates in radians.
lon1 (float) – Starting point coordinates in radians.
lat2 (float) – Destination point coordinates in radians.
lon2 (float) – Destination point coordinates in radians.
n_points (int) – Number of waypoints (including start and end).
radius (float, optional) – Sphere radius in meters (default: 6371 km).
- Returns:
lats, lons – Arrays of waypoint latitudes and longitudes in radians.
- Return type:
ndarray
Examples
>>> import numpy as np >>> lat1, lon1 = np.radians(40), np.radians(-74) >>> lat2, lon2 = np.radians(51), np.radians(0) >>> lats, lons = rhumb_waypoints(lat1, lon1, lat2, lon2, 5) >>> len(lats) 5
- pytcl.navigation.rhumb.compare_great_circle_rhumb(lat1, lon1, lat2, lon2, radius=6371000.0)[source]
Compare great circle and rhumb line paths.
- Parameters:
- Returns:
gc_distance (float) – Great circle distance in meters.
rhumb_distance (float) – Rhumb line distance in meters.
difference_percent (float) – Percentage difference (rhumb is longer).
- Return type:
Examples
>>> import numpy as np >>> lat1, lon1 = np.radians(40), np.radians(-74) # NYC >>> lat2, lon2 = np.radians(51), np.radians(0) # London >>> gc, rhumb, diff = compare_great_circle_rhumb(lat1, lon1, lat2, lon2) >>> rhumb > gc # Rhumb is always longer True