A C++17 static library implementing a 15-dimensional Error-State Extended Kalman Filter for fusing Inertial Navigation System (INS) data with GPS measurements. The filter estimates and corrects position, velocity, and orientation errors in real time, with optional GPS-derived azimuth alignment.
An Inertial Navigation System computes position and velocity by integrating accelerometer and gyroscope measurements. This process is autonomous and high-rate, but it drifts over time because sensor biases and noise accumulate through double-integration.
GPS provides absolute position and velocity at a lower rate (~1-10 Hz) with bounded errors, but is subject to outages and multipath.
This library fuses both sources using an Error-State (Indirect) Extended Kalman Filter. Instead of estimating the full navigation state directly, the EKF tracks the errors in the INS solution. When a GPS fix arrives, the filter computes optimal corrections and resets. This indirect formulation keeps the error state small (linearisation stays accurate) and lets the INS run undisturbed between GPS updates.
| Frame | Description |
|---|---|
| Navigation frame (n) | Local NED (North-East-Down) tangent plane |
| Body frame (b) | Sensor-fixed frame aligned with the IMU |
The Direction Cosine Matrix (DCM)
The Earth is modelled as an ellipsoid with:
| Parameter | Symbol | Value |
|---|---|---|
| Semi-major axis | 6,378,137.0 m | |
| Eccentricity | 0.081819 | |
| Turn rate | 7.292 × 10-5 rad/s |
The meridian and prime-vertical radii of curvature are:
where
The 15-dimensional error state vector is:
| Index | Symbol | Dimension | Description |
|---|---|---|---|
| 0-2 | 3 | Position error (latitude, longitude, altitude) | |
| 3-5 | 3 | Velocity error in NED (m/s) | |
| 6-8 | 3 | Attitude error / misalignment angles (rad) | |
| 9-11 | 3 | Accelerometer bias (m/s2) | |
| 12-14 | 3 | Gyroscope bias (rad/s) |
The navigation state is propagated using strapdown mechanization equations in the NED frame:
Position rate:
where
Velocity rate:
where
DCM rate:
where
All states are integrated using first-order Euler integration:
The error state evolves according to:
where
Each
| Block | Coupling | Description |
|---|---|---|
| position → position | Effect of position error on position rate (curvature terms) | |
| velocity → position |
|
|
| attitude → position | Zero (attitude errors do not directly affect position rate) | |
| position → velocity | Coriolis and centripetal terms that depend on latitude | |
| velocity → velocity | Coriolis coupling between velocity components | |
| attitude → velocity | Skew-symmetric of specific force: |
|
| position → attitude | Earth-rate and transport-rate dependence on position | |
| velocity → attitude | Transport-rate dependence on velocity | |
| attitude → attitude | Skew-symmetric of angular velocity: |
|
| accel. bias dynamics | Zero (random walk model) | |
| gyro bias dynamics | Zero (random walk model) |
The state transition matrix is computed via matrix exponential:
The error state covariance is propagated as:
where
When GPS measurements arrive, the measurement vector is:
The observation matrix maps the error state to the measurement space:
Kalman gain:
State correction:
Covariance update:
Navigation state correction:
Reset: After correction, the error state is reset to zero, the navigation state is set to the corrected values, and the covariance is reset.
When horizontal velocity exceeds a threshold (5 m/s), the filter computes a GPS-derived heading correction:
During the error state integration, the down-axis attitude error
At GPS update, the orientation correction uses Euler angle subtraction:
where
┌─────────────────────────────────────────────────────┐
│ EKF │
│ (public API, GPS update, Kalman correction, mutex) │
└──────────────┬──────────────────────────────────────┘
│ owns (unique_ptr)
▼
┌─────────────────────────────────────────────────────┐
│ Tracking │
│ (IMU buffering, dt computation, orchestration) │
└──┬──────────────────┬──────────────────┬────────────┘
│ shared_ptr │ shared_ptr │ shared_ptr
▼ ▼ ▼
┌──────────┐ ┌─────────────┐ ┌─────────────────────┐
│Navigation│ │ ErrorState │ │ErrorStateCovariance │
│ State │ │ (15D, F mat) │ │ (P, Q, Phi) │
└──────────┘ └─────────────┘ └─────────────────────┘
│ uses
▼
┌───────────┐
│ Utils │
│ (WGS-84, │
│ Rm, Rn, │
│ skew-sym)│
└───────────┘
IMU (accel)──► updateWithInertialMeasurement(data, ACCELEROMETER)
│
IMU (gyro) ──► updateWithInertialMeasurement(data, GYRO)
│
▼
Tracking::checkAndUpdate()
(when both accel & gyro available)
│
┌───────┴────────┐
│ Compute dt │
│ Mean of │
│ buffered IMU │
└───────┬────────┘
│
┌─────────────┼─────────────┐
▼ ▼ ▼
NavigationState ErrorState ErrorStateCovariance
propagate() propagate() P = Φ P Φᵀ + Q
│ │ │
└─────────────┼─────────────┘
│
GPS ─────► updateWithGPSMeasurements(gps_data)
│
┌───────┴────────┐
│ Compute z │
│ Kalman gain K │
│ Correct state │
│ Reset errors │
└────────────────┘
Main filter class. All public methods are thread-safe for reading state.
| Method | Description |
|---|---|
EKF() |
Constructor. Initializes default Q and R matrices, logging, and internal components. |
void setInitialState(Vector3d p_0, Vector3d v_0, Matrix3d T_0) |
Set initial navigation state. p_0 = [latitude (rad), longitude (rad), altitude (m)], v_0 = NED velocity (m/s), T_0 = body-to-nav DCM. |
void start() |
Start the filter (resets the clock). Must be called before feeding measurements. |
void updateWithInertialMeasurement(Vector3d data, Type type) |
Feed an IMU sample. type is ACCELEROMETER (m/s2, body frame) or GYRO (rad/s, body frame). |
void updateWithGPSMeasurements(vector<Matrix<double,6,1>> gps_data) |
Feed one or more GPS fixes. Each 6×1 vector: [lat (rad), lon (rad), alt (m), v_N, v_E, v_D] (m/s). Multiple fixes are averaged. |
void setQMatrix(const MatrixXd &Q) |
Set process noise covariance (15×15). |
void setRMatrix(const MatrixXd &R) |
Set measurement noise covariance (6×6). |
tuple<Vector3d, Vector3d, Matrix3d> getNavigationState() |
Returns (position, velocity, DCM). |
Vector3d getPositionState() |
Returns position [lat, lon, alt]. |
Vector3d getVelocityState() |
Returns NED velocity. |
Matrix3d getOrientationState() |
Returns body-to-nav DCM. |
VectorXd getErrorState() |
Returns the 15D error state. |
MatrixXd getErrorStateCovariance() |
Returns 15×15 covariance matrix. |
double getAzimuth() |
Returns current heading (rad). |
Enum: ACCELEROMETER, GYRO.
Static utility class with WGS-84 constants and helper functions:
| Member | Description |
|---|---|
Rm(phi) |
Meridian radius of curvature at latitude phi (rad) |
Rn(phi) |
Prime-vertical radius of curvature at latitude phi (rad) |
toSkewSymmetricMatrix(M, v) |
Fills 3×3 matrix M with the skew-symmetric form of vector v |
toEulerAngles(T) |
Extract Euler angles (roll, pitch, yaw) from rotation matrix |
toRotationMatrix(euler) |
Build rotation matrix from Euler angles |
fromENUtoNED(enu) |
Coordinate transform (note: uses a non-standard transformation matrix) |
calcMeanVector(measurements) |
Online (incremental) mean of a vector of Eigen vectors |
degreeToRadian(x) / radianToDegree(x) |
Angle conversion |
#include "EKF.h"
int main() {
EKF_INS::EKF ekf;
// Set initial state: position (lat, lon, alt), velocity (NED), orientation (DCM)
Eigen::Vector3d p0(0.5585, 0.6109, 100.0); // ~32 deg N, ~35 deg E, 100m
Eigen::Vector3d v0(0.0, 0.0, 0.0);
Eigen::Matrix3d T0 = Eigen::Matrix3d::Identity();
ekf.setInitialState(p0, v0, T0);
ekf.start();
// Main loop: feed IMU data at high rate
Eigen::Vector3d accel_body(0.0, 0.0, -9.81); // body-frame specific force
Eigen::Vector3d gyro_body(0.0, 0.0, 0.0); // body-frame angular rate
ekf.updateWithInertialMeasurement(accel_body, EKF_INS::ACCELEROMETER);
ekf.updateWithInertialMeasurement(gyro_body, EKF_INS::GYRO);
// When GPS is available: feed position + velocity
std::vector<Eigen::Matrix<double, 6, 1>> gps_data;
Eigen::Matrix<double, 6, 1> gps_fix;
gps_fix << 0.5585, 0.6109, 100.0, 0.0, 0.0, 0.0;
gps_data.push_back(gps_fix);
ekf.updateWithGPSMeasurements(gps_data);
// Read corrected state
auto [pos, vel, dcm] = ekf.getNavigationState();
return 0;
}- C++17 compiler (GCC 7+, Clang 5+)
- Eigen3 — linear algebra
- Boost — system component
- spdlog — logging (bundled in
3rdparty/)
mkdir build && cd build
cmake ..
makeThis produces a static library libEKF-INS.a. Link it into your project along with the Eigen3 and Boost include paths.
add_subdirectory(path/to/ins-gps-ekf)
target_link_libraries(your_target EKF-INS)- Groves, P.D. Principles of GNSS, Inertial, and Multisensor Integrated Navigation Systems, 2nd ed., Artech House, 2013.
- Titterton, D. & Weston, J. Strapdown Inertial Navigation Technology, 2nd ed., IET, 2004.