Skip to content

Instantly share code, notes, and snippets.

@arrieta
Created January 25, 2019 23:54
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save arrieta/8f2c4d47c3a3c2c6148dcbf78b5df4af to your computer and use it in GitHub Desktop.
Save arrieta/8f2c4d47c3a3c2c6148dcbf78b5df4af to your computer and use it in GitHub Desktop.
TLE Parser
// -*- coding:utf-8; mode:c++; mode:auto-fill; fill-column:80; -*-
/// @file tle.cpp
/// @brief Implementation of tle.
/// @author J. Arrieta <juan.arrieta@hesslag.com>
/// @date January 25, 2019
/// @copyright (C) 2019 Hesslag, Inc.
// Component header
#include "tle.hpp"
// C++ Standard Library
#include <cstdlib>
#include <iostream>
#include <string>
// Hesslag Library
namespace hesslag {
namespace tle {
namespace {
static constexpr const std::size_t gLINE_LENGTH{70};
static constexpr const std::size_t gRECORD_COUNT{3};
static constexpr const std::size_t gRECORD_LENGTH{gLINE_LENGTH * gRECORD_COUNT};
double simple_double( std::string_view s, int line, int beg, int count ) {
const int offset = line * gLINE_LENGTH + beg - 1;
char buffer[ 16 ]{'\0'};
std::copy_n( std::next( s.begin(), offset ), count, buffer );
return std::atof( buffer );
}
double assumed_decimal_double( std::string_view s,
int line,
int beg,
int count ) {
const int offset = line * gLINE_LENGTH + beg - 1;
char buffer[ 16 ]{'\0'};
buffer[ 0 ] = '.';
std::copy_n( std::next( s.begin(), offset ), count, buffer + 1 );
return std::atof( buffer );
}
double assumed_decimal_exponent_double( std::string_view s,
int line,
int beg,
int count ) {
const int offset = line * gLINE_LENGTH + beg - 1;
char buffer[ 16 ]{'\0'};
auto loc = &buffer[ 0 ];
s = s.substr( offset, count );
while ( s.front() == ' ' ) {
s.remove_prefix( 1 );
}
if ( s[ 0 ] == '-' ) {
*loc++ = '-';
*loc++ = '.';
s.remove_prefix( 1 );
} else {
*loc++ = '.';
};
for ( auto& c : s ) {
switch ( c ) {
case '+':
*loc++ = 'E';
*loc++ = '+';
break;
case '-':
*loc++ = 'E';
*loc++ = '-';
break;
case ' ':
break;
default:
*loc++ = c;
}
}
return std::atof( buffer );
}
int simple_int( std::string_view s, int line, int beg, int count ) {
const int offset = line * gLINE_LENGTH + beg - 1;
char buffer[ 16 ]{'\0'};
std::copy_n( std::next( s.begin(), offset ), count, buffer );
return std::atoi( buffer );
}
std::string simple_string( std::string_view s, int line, int beg, int count ) {
const int offset = line * gLINE_LENGTH + beg - 1;
std::string_view v( std::next( s.begin(), offset ), count );
while ( v.back() == ' ' ) {
v.remove_suffix( 1 );
}
return {v.data(), v.size()};
}
} // anonymous namespace
int checksum( std::string_view line ) {
int value = 0;
for ( int k = 0; k < 68; ++k ) {
const auto c = line[ k ];
if ( c == '-' ) {
value += 1;
} else if ( std::isdigit( c ) ) {
value += c - '0';
}
}
return value % 10;
}
bool is_valid( std::string_view s ) {
bool has_proper_length = s.size() >= gRECORD_LENGTH;
if ( has_proper_length ) {
bool cksm_1 = checksum( s.substr( 70, 70 ) ) == simple_int( s, 1, 69, 1 );
bool cksm_2 = checksum( s.substr( 140, 70 ) ) == simple_int( s, 2, 69, 1 );
return cksm_1 && cksm_2;
} else {
return false;
}
}
std::string satellite_name( std::string_view s ) {
return simple_string( s, 0, 1, 69 );
}
std::string international_designator( std::string_view s ) {
return simple_string( s, 1, 10, 8 );
}
int satellite_number( std::string_view s ) { return simple_int( s, 1, 3, 5 ); }
int epoch_year( std::string_view s ) { return simple_int( s, 1, 19, 2 ); }
int element_set_number( std::string_view s ) {
return simple_int( s, 1, 65, 4 );
}
int revolution_number( std::string_view s ) {
return simple_int( s, 2, 64, 5 );
}
double epoch_day( std::string_view s ) { return simple_double( s, 1, 21, 12 ); }
double first_derivative_mean_motion( std::string_view s ) {
return 2.0 * simple_double( s, 1, 34, 10 );
}
double second_derivative_mean_motion( std::string_view s ) {
return 6.0 * assumed_decimal_exponent_double( s, 1, 45, 8 );
}
double drag( std::string_view s ) {
return assumed_decimal_exponent_double( s, 1, 54, 8 );
}
double inclination( std::string_view s ) { return simple_double( s, 2, 9, 8 ); }
double right_ascension( std::string_view s ) {
return simple_double( s, 2, 18, 8 );
}
double eccentricity( std::string_view s ) {
return assumed_decimal_double( s, 2, 27, 7 );
}
double argument_periapsis( std::string_view s ) {
return simple_double( s, 2, 35, 8 );
}
double mean_anomaly( std::string_view s ) {
return simple_double( s, 2, 44, 8 );
}
double mean_motion( std::string_view s ) {
return simple_double( s, 2, 53, 11 );
}
} // namespace tle
} // namespace hesslag
// -*- coding:utf-8; mode:c++; mode:auto-fill; fill-column:80; -*-
/// @file tle.hpp
/// @brief Component tle.
/// @author J. Arrieta <juan.arrieta@hesslag.com>
/// @date January 25, 2019
/// @copyright (C) 2019 Hesslag, Inc.
#pragma once
// C++ Standard Library
#include <string>
// Hesslag Library
namespace hesslag {
namespace tle {
bool is_valid( std::string_view s );
std::string satellite_name( std::string_view s );
std::string international_designator( std::string_view s );
int satellite_number( std::string_view s );
int epoch_year( std::string_view s );
int element_set_number( std::string_view s );
int revolution_number( std::string_view s );
double epoch_day( std::string_view s );
double first_derivative_mean_motion( std::string_view s );
double second_derivative_mean_motion( std::string_view s );
double drag( std::string_view s );
double inclination( std::string_view s );
double right_ascension( std::string_view s );
double eccentricity( std::string_view s );
double argument_periapsis( std::string_view s );
double mean_anomaly( std::string_view s );
double mean_motion( std::string_view s );
} // namespace tle
} // namespace hesslag
// -*- coding:utf-8; mode:c++; mode:auto-fill; fill-column:80; -*-
/// @file tle.t.cpp
/// @brief Test suite for tle.
/// @author J. Arrieta <juan.arrieta@hesslag.com>
/// @date January 25, 2019
/// @copyright (C) 2019 Hesslag, Inc.
// Component header
#include "tle.hpp"
// C++ Standard Library
// Hesslag Library
// Google Test Framework
#include <gtest/gtest.h>
constexpr auto ISS =
"ISS (ZARYA) \n"
"1 25544U 98067A 08264.51782528 -.00002182 00000-0 -11606-4 0 2927\n"
"2 25544 51.6416 247.4627 0006703 130.5360 325.0288 15.72125391563537\n";
constexpr auto ISAT =
"ISAT \n"
"1 43879U 18111D 19024.90991605 .00000241 00000-0 26704-4 0 9998\n"
"2 43879 97.7268 294.9457 0013986 142.3455 217.8748 14.95877100 4297\n";
using namespace hesslag;
TEST( TLE, Validation ) {
EXPECT_TRUE( tle::is_valid( ISS ) );
EXPECT_TRUE( tle::is_valid( ISAT ) );
}
TEST( TLE, SatelliteName ) {
EXPECT_EQ( tle::satellite_name( ISS ), "ISS (ZARYA)" );
EXPECT_EQ( tle::satellite_name( ISAT ), "ISAT" );
}
TEST( TLE, SatelliteNumber ) {
EXPECT_EQ( tle::satellite_number( ISS ), 25544 );
EXPECT_EQ( tle::satellite_number( ISAT ), 43879 );
}
TEST( TLE, InternationalDesignator ) {
EXPECT_EQ( tle::international_designator( ISS ), "98067A" );
EXPECT_EQ( tle::international_designator( ISAT ), "18111D" );
}
TEST( TLE, EpochYear ) {
EXPECT_EQ( tle::epoch_year( ISS ), 8 );
EXPECT_EQ( tle::epoch_year( ISAT ), 19 );
}
TEST( TLE, EpochDay ) {
EXPECT_EQ( tle::epoch_day( ISS ), 264.51782528 );
EXPECT_EQ( tle::epoch_day( ISAT ), 24.90991605 );
}
TEST( TLE, FirstDerivativeMeanMotion ) {
EXPECT_EQ( tle::first_derivative_mean_motion( ISS ), -.00002182 * 2 );
EXPECT_EQ( tle::first_derivative_mean_motion( ISAT ), .00000241 * 2 );
}
TEST( TLE, SecondDerivativeMeanMotion ) {
EXPECT_EQ( tle::second_derivative_mean_motion( ISS ), 0.0 );
EXPECT_EQ( tle::second_derivative_mean_motion( ISAT ), 0.0 );
}
TEST( TLE, Drag ) {
EXPECT_EQ( tle::drag( ISS ), -0.11606E-4 );
EXPECT_EQ( tle::drag( ISAT ), 0.26704E-4 );
}
TEST( TLE, ElementSetNumber ) {
EXPECT_EQ( tle::element_set_number( ISS ), 292 );
EXPECT_EQ( tle::element_set_number( ISAT ), 999 );
}
TEST( TLE, Inclination ) {
EXPECT_EQ( tle::inclination( ISS ), 51.6416 );
EXPECT_EQ( tle::inclination( ISAT ), 97.7268 );
}
TEST( TLE, RightAscension ) {
EXPECT_EQ( tle::right_ascension( ISS ), 247.4627 );
EXPECT_EQ( tle::right_ascension( ISAT ), 294.9457 );
}
TEST( TLE, Eccentricity ) {
EXPECT_EQ( tle::eccentricity( ISS ), 0.0006703 );
EXPECT_EQ( tle::eccentricity( ISAT ), 0.0013986 );
}
TEST( TLE, ArgumentPeriapsis ) {
EXPECT_EQ( tle::argument_periapsis( ISS ), 130.5360 );
EXPECT_EQ( tle::argument_periapsis( ISAT ), 142.3455 );
}
TEST( TLE, MeanAnomaly ) {
EXPECT_EQ( tle::mean_anomaly( ISS ), 325.0288 );
EXPECT_EQ( tle::mean_anomaly( ISAT ), 217.8748 );
}
TEST( TLE, MeanMotion ) {
EXPECT_EQ( tle::mean_motion( ISS ), 15.72125391 );
EXPECT_EQ( tle::mean_motion( ISAT ), 14.9587710 );
}
TEST( TLE, RevolutionNumber ) {
EXPECT_EQ( tle::revolution_number( ISS ), 56353 );
EXPECT_EQ( tle::revolution_number( ISAT ), 429 );
}
int main( int argc, char** argv ) {
::testing::InitGoogleTest( &argc, argv );
return RUN_ALL_TESTS();
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment