Don't you just hate that awkward moment when you need to propagate Earth-centred spacecraft trajectories using the accurate-ish SGP4 model, but also want to avoid having to understand source code that literally dates back to the 80s? Well you're in luck, cause perturb
provides a simple interface so you don't have to. Throw this library a TLE orbital element and it'll spit out a position and velocity vector.
- Uses Vallado's latest and greatest de-facto standard SGP4 implementation
- Strictly standards compliant C++11 with no dependencies
- Embedded support (i.e. no dynamic memory, no recursion, no exceptions, no virtual, no RTTI, none of that runtime funny business)
- Built with modern CMake with a relatively standard library layout
- Has tests and CI, which is more than I can say for most of my code
See the examples
directory to see how you can try out this exact code snippet.
#include <cassert>
#include <iostream>
#include <perturb/perturb.hpp>
using namespace perturb; // I know, I know. I promise it's only for examples!
int main() {
// Let try simulating the orbit of the International Space Station
// Got TLE from Celestrak sometime around 2022-03-12
std::string ISS_TLE_1 = "1 25544U 98067A 22071.78032407 .00021395 00000-0 39008-3 0 9996";
std::string ISS_TLE_2 = "2 25544 51.6424 94.0370 0004047 256.5103 89.8846 15.49386383330227";
// Create and initialize a satellite object from the TLE
auto sat = Satellite::from_tle(ISS_TLE_1, ISS_TLE_2);
assert(sat.last_error() == Sgp4Error::NONE);
assert(sat.epoch().to_datetime().day == 12);
// Let's see what the ISS is doing on Pi Day
const auto t = JulianDate(DateTime { 2022, 3, 14, 1, 59, 26.535 });
const double delta_days = t - sat.epoch();
assert(1 < delta_days && delta_days < 3); // It's been ~2 days since the epoch
// Calculate the position and velocity at the chosen time
StateVector sv;
const auto err = sat.propagate(t, sv);
assert(err == Sgp4Error::NONE);
const auto &pos = sv.position, &vel = sv.velocity;
// Conclusion: The ISS is going pretty fast (~8 km/s)
std::cout << "Position [km]: { " << pos[0] << ", " << pos[1] << ", " << pos[2] << " }\n";
std::cout << "Velocity [km/s]: { " << vel[0] << ", " << vel[1] << ", " << vel[2] << " }\n";
}
Most C++ build systems kinda suck, but CMake is the closest thing to a standard we have. This library exposes a CMake target (named perturb
) which hopefully should make using it very simple. To get a copy of the source code, you have a few options. In my personal order of preference:
This is a simple CMake-based package manager. Just point it at this Github repo with a tagged release version and it should just work.
Point it at this Github repo with a tagged release version and you should be good to go. Here's a snippet demonstrating this:
include(FetchContent)
# Download and setup the perturb library
FetchContent_Declare(
perturb
GIT_REPOSITORY "https://github.com/gunvirranu/perturb"
GIT_TAG 5723e50a54d438876b7a02e02a326e30b51ead4c # Release v1.0.0
)
FetchContent_MakeAvailable(perturb)
# Link perturb into your project
target_link_libraries(YOUR_VERY_COOL_PROJECT_TARGET perturb)
See the examples
directory for CMake example projects using this approach. You can make more complicated adjustments as necessary, but this demonstrates the basics.
Personally, I'm not a fan, but a lot of people seem to love these. Once you have the perturb
repo set up as a submodule, let's say in the external
folder, you can use it like so:
# Add perturb library root directory
add_subdirectory(external/perturb)
# Link perturb into your project
target_link_libraries(YOUR_VERY_COOL_PROJECT_TARGET perturb)
If you clone this repo manually into a specific location or have any other method of checking it out, you can then follow the exact same steps as using Git Submodules.
You can also just download the headers from include/perturb
and source files from src
and then just check them straight into your project with your own build system. Not very elegant, but it gets the job done I guess.
perturb
currently doesn't support any other build systems, primarily due to my lack of knowledge on them, but I'm open to it. If you're interested, open an issue and we can look into it.
Everything in this library is in the perturb::
namespace. Here's a quick intro to the typical "usage flow".
I won't cover the details of SGP4, but in brief, it's a very popular orbit propagator for Earth-centered spacecraft. Usually, the input orbit ephemeris is through a TLE, such as the ones provided by Celestrek. These TLE inputs can be used to construct a perturb::Satellite
object.
A specific point in time is represented as a perturb::JulianDate
. You can either construct one from a specific date and time via perturb::DateTime
or offset a number of days from the epoch()
of a satellite.
Passing in a time point to the Satellite::propagate(...)
method of a satellite yields a perturb::StateVector
, which contains a time-stamp, and a position and velocity vector. These vectors are just a std::array<double, 3>
, measured in kilometres, and are represented in the TEME coordinate reference frame. The details of this frame can get a bit annoying, so this library does not handle converting it to others. For handling Earth-centered reference frames such as TEME and transformations between them, you may be interested in the gelocus
library.
Check out this page for some slightly more detailed documentation of the interface.
You probably don't need to configure anything.
The TLE parsing code (which accepts a char *
or std::string
) may be undesired for embedded applications, either due to inefficient codegen from sscanf
(which is used for parsing) or to avoid dynamic memory. This I/O functionality can be disabled by defining the PERTURB_DISABLE_IO
preprocessor flag in your build system, which will strip out any mentions of I/O and strings. It is not defined by default, so the functionality is usually included.
In CMake, once you have the perturb
target initialized, you can do:
target_compile_definitions(perturb PRIVATE PERTURB_DISABLE_IO)
You could also set the perturb_DISABLE_IO
option in CMake to ON
before initializing the perturb
target. This is also by default OFF
. Setting this option will handle defining the PERTURB_DISABLE_IO
preprocessor flag.
Do note, this will leave you with no way of parsing TLEs. You will need to pre-parse the TLE and initialize the propagator using numerical values directly. This can be done by initializing the TwoLineElement
type however you wish and using that to construct a Satellite
object via its constructor.
See CHANGELOG.md
.
perturb
is licensed under the MIT license.