Skip to content
Snippets Groups Projects
gtest_tdcp.cpp 17.93 KiB
#include "gtest/utils_gtest.h"
#include "gnss_utils/tdcp.h"
#include "gnss_utils/snapshot.h"
#include "gnss_utils/utils/satellite.h"
#include "gnss_utils/utils/transformations.h"

using namespace GnssUtils;
using namespace Eigen;

int N_sats = 20;
int N_tests = 100;
double distortion1 = -30;
double distortion2 = -20;

Vector3d computeRandomReceiverLatLonAlt()
{
    Vector3d receiver_LLA = Vector3d::Random(); // in [-1, 1]
    receiver_LLA(0) *= M_PI / 2; // in [-PI/2, PI/2]
    receiver_LLA(1) *= M_PI;     // in [-PI, PI]
    receiver_LLA(2) += 1;        // ([0, 2])
    receiver_LLA(2) *= 5e2;      // in [0, 1e3]

    return receiver_LLA;
}

void computeVisibleSatellite(const Vector3d& receiver_latlonalt,
                             const int& n_sat,
                             Vector3d& sat_ENU,
                             Vector3d& sat_ECEF,
                             Vector2d& sat_azel,
                             double& range)
{
    Vector3d t_ECEF_ENU, t_ENU_ECEF;
    Matrix3d R_ENU_ECEF;

    t_ECEF_ENU = latLonAltToEcef(receiver_latlonalt);

    computeEnuEcefFromLatLonAlt(receiver_latlonalt, R_ENU_ECEF, t_ENU_ECEF);

    // equidistributed azimuth at elevation 45º
    if (n_sat < 6)
    {
        sat_azel(0) = -M_PI + n_sat * 2*M_PI/6 + 0.1;   // in [-pi, pi]
        sat_azel(1) = M_PI / 4;                 // in [0, pi/2]
    }
    // random elevation and azimuth
    else
    {
        sat_azel = Vector2d::Random();               // in [-1, 1]
        sat_azel(0) *= M_PI;                                // in [-pi, pi]
        sat_azel(1) = (sat_azel(1)/2 + 0.5) * M_PI / 2;     // in [0, pi/2]
    }

    // random range
    range = VectorXd::Random(1)(0) * 5e2 + 1e3;  // in [500, 1500]

    // ENU
    sat_ENU << sin(sat_azel(0)) * cos(sat_azel(1)) * range,
               cos(sat_azel(0)) * cos(sat_azel(1)) * range,
               sin(sat_azel(1)) * range;

    // ECEF
    sat_ECEF = t_ECEF_ENU + R_ENU_ECEF.transpose() * sat_ENU;

    // Range
    range = (sat_ECEF - t_ECEF_ENU).norm();
}

TEST(Tdcp, Tdcp)
{
    TdcpBatchParams tdcp_params;
    tdcp_params.max_iterations = 5;
    tdcp_params.min_common_sats = 6;
    tdcp_params.raim_n = 0;
    tdcp_params.residual_opt = 0;
    tdcp_params.max_residual = 1e20;
    tdcp_params.relinearize_jacobian = true;
    tdcp_params.tdcp.multi_freq = false;
    tdcp_params.tdcp.sigma_atm = 1;
    tdcp_params.tdcp.sigma_carrier = 1;

    Vector3d sat_ENU, sat_ECEF;
    Vector3d x_r_LLA, x_r_ECEF, x_k_LLA, x_k_ECEF, d_ECEF;
    Matrix3d R_ENU_ECEF;
    Vector2d azel, azel2;
    Vector4d d_gt;
    Matrix4d cov_d;
    double range, residual;

    // Snapshots
    auto snapshot_r = std::make_shared<Snapshot>();
    auto snapshot_k = std::make_shared<Snapshot>();

    // Observations (just needed for the GPST)
    snapshot_r->setObservations(std::make_shared<Observations>());
    snapshot_k->setObservations(std::make_shared<Observations>());
    obsd_t obs_r;
    obs_r.time.sec = 0;
    obs_r.time.time = 0;
    snapshot_r->getObservations()->addObservation(obs_r);
    obsd_t obs_k;
    obs_k.time.sec = 0;
    obs_k.time.time = 1;
    snapshot_k->getObservations()->addObservation(obs_k);

    // Random receiver position
    for (auto i = 0; i<N_tests; i++)
    {
        // clear satellites and ranges
        snapshot_r->getSatellites().clear();
        snapshot_k->getSatellites().clear();
        snapshot_r->getRanges().clear();
        snapshot_k->getRanges().clear();

        // random setup
        x_r_LLA = computeRandomReceiverLatLonAlt();
        x_r_ECEF = latLonAltToEcef(x_r_LLA);
        d_ECEF = Vector3d::Random() * 10;
        x_k_ECEF = x_r_ECEF + d_ECEF;
        x_k_LLA = ecefToLatLonAlt(x_k_ECEF);

        double clock_r = Vector2d::Random()(0) * 1e-6;
        double clock_k = Vector2d::Random()(0) * 1e-6;

        // groundtruth
        d_gt.head<3>() = d_ECEF;
        d_gt(3) = (clock_k - clock_r) * CLIGHT;

        std::cout << "Test " << i << ":\n";
        std::cout << std::setprecision(10) << "\tx_r_ECEF " << x_r_ECEF.transpose() << ":\n";
        std::cout << "\tclock_r " << clock_r << ":\n";
        std::cout << std::setprecision(10) << "\tx_k_ECEF " << x_k_ECEF.transpose() << ":\n";
        std::cout << "\tclock_k " << clock_k << ":\n";
        std::cout << "\td_gt " << d_gt.transpose() << ":\n";

        std::set<int> common_sats;
        std::set<int> raim_discarded_sats;

        // random visible satellites
        for (auto j = 0; j<N_sats; j++)
        {
            common_sats.insert(j);

            // Satellite r
            computeVisibleSatellite(x_r_LLA, j, sat_ENU, sat_ECEF, azel, range);
            ASSERT_MATRIX_APPROX(azel, computeAzel(sat_ECEF, x_r_ECEF), 1e-3);

            Satellite sat_r({0,j,sat_ECEF, Vector3d::Zero(), 1.0, 0, 0, 1});
            snapshot_r->getSatellites().emplace(j, sat_r);

            // Range r
            Range range_r;
            range_r.sat = j;
            range_r.L = range + CLIGHT * clock_r;
            range_r.L_corrected = range_r.L;
            range_r.L_var = 1;
            range_r.L2 = range_r.L;
            range_r.L2_corrected = range_r.L;
            range_r.L2_var = range_r.L_var;

            snapshot_r->getRanges().emplace(j, range_r);

            //std::cout << "\tsat: " << j << "\n";
            //std::cout << "\t\tsat_r_ECEF " << sat_ECEF.transpose() << ":\n";
            //std::cout << "\t\trange_r.L_corrected " << range_r.L_corrected << ":\n";

            // Satellite k (random)
            computeVisibleSatellite(x_k_LLA, j, sat_ENU, sat_ECEF, azel, range);
            ASSERT_MATRIX_APPROX(azel, computeAzel(sat_ECEF, x_k_ECEF), 1e-3);

            Satellite sat_k({0,j,sat_ECEF, Vector3d::Zero(), 1.0, 0, 0, 1});
            snapshot_k->getSatellites().emplace(j, sat_k);

            // Range k
            Range range_k;
            range_k.sat = j;
            range_k.L = range + CLIGHT * clock_k;
            range_k.L_corrected = range_k.L;
            range_k.L_var = 1;
            range_k.L2 = range_k.L;
            range_k.L2_corrected = range_k.L;
            range_k.L2_var = range_k.L_var;

            snapshot_k->getRanges().emplace(j, range_k);

            //std::cout << "\t\tsat_k_ECEF " << sat_ECEF.transpose() << ":\n";
            //std::cout << "\t\trange_k.L_corrected " << range_k.L_corrected << ":\n";
        }

        // TDCP
        auto tdcp_out = Tdcp(snapshot_r,
                             snapshot_k,
                             x_r_ECEF,
                             common_sats,
                             Vector4d::Zero(),
                             tdcp_params);

        // CHECKS
        std::cout << "CHECKS Test " << i << std::endl;
        ASSERT_TRUE(tdcp_out.success);
        ASSERT_LE(tdcp_out.residual, 1e-3);
        ASSERT_MATRIX_APPROX(tdcp_out.d, d_gt, 1e-3);
    }
}

TEST(Tdcp, Tdcp_raim_residual_rmse)
{
    int N_sats = 20;

    TdcpBatchParams tdcp_params;
    tdcp_params.max_iterations = 5;
    tdcp_params.min_common_sats = 6;
    tdcp_params.raim_n = 2;
    tdcp_params.relinearize_jacobian = true;
    tdcp_params.residual_opt = 0; // Normalized RMSE
    tdcp_params.max_residual = 0.1; // low threshold to detect outliers...
    tdcp_params.tdcp.multi_freq = false;
    tdcp_params.tdcp.sigma_atm = 1;
    tdcp_params.tdcp.sigma_carrier = 1;


    Vector3d sat_ENU, sat_ECEF;
    Vector3d x_r_LLA, x_r_ECEF, x_k_LLA, x_k_ECEF, d_ECEF;
    Matrix3d R_ENU_ECEF;
    Vector2d azel, azel2;
    Vector4d d, d_gt;
    Matrix4d cov_d;
    double range, residual;

    // Snapshots
    auto snapshot_r = std::make_shared<Snapshot>();
    auto snapshot_k = std::make_shared<Snapshot>();

    // Observations (just needed for the GPST)
    snapshot_r->setObservations(std::make_shared<Observations>());
    snapshot_k->setObservations(std::make_shared<Observations>());
    obsd_t obs_r;
    obs_r.time.sec = 0;
    obs_r.time.time = 0;
    snapshot_r->getObservations()->addObservation(obs_r);
    obsd_t obs_k;
    obs_k.time.sec = 0;
    obs_k.time.time = 1;
    snapshot_k->getObservations()->addObservation(obs_k);

    // Random receiver position
    for (auto i = 0; i<N_tests; i++)
    {
        // clear satellites and ranges
        snapshot_r->getSatellites().clear();
        snapshot_k->getSatellites().clear();
        snapshot_r->getRanges().clear();
        snapshot_k->getRanges().clear();

        // random setup
        x_r_LLA = computeRandomReceiverLatLonAlt();
        x_r_ECEF = latLonAltToEcef(x_r_LLA);
        d_ECEF = Vector3d::Random() * 10;
        x_k_ECEF = x_r_ECEF + d_ECEF;
        x_k_LLA = ecefToLatLonAlt(x_k_ECEF);

        double clock_r = Vector2d::Random()(0) * 1e-6;
        double clock_k = Vector2d::Random()(0) * 1e-6;

        // groundtruth
        d_gt.head<3>() = d_ECEF;
        d_gt(3) = (clock_k - clock_r) * CLIGHT;

        // distorted sats
        int wrong_sat1 = i % N_sats;
        int wrong_sat2 = (i + N_sats / 2) % N_sats;

        std::cout << "Test " << i << ":\n";
        std::cout << std::setprecision(10) << "\tx_r_ECEF " << x_r_ECEF.transpose() << ":\n";
        std::cout << "\tclock_r " << clock_r << ":\n";
        std::cout << std::setprecision(10) << "\tx_k_ECEF " << x_k_ECEF.transpose() << ":\n";
        std::cout << "\tclock_k " << clock_k << ":\n";
        std::cout << "\td_gt " << d_gt.transpose() << ":\n";
        std::cout << "\tdistorted sats: " << wrong_sat1 << ", " << wrong_sat2 << std::endl;
        std::set<int> common_sats;
        std::set<int> raim_discarded_sats;

        // random visible satellites
        for (auto j = 0; j<N_sats; j++)
        {
            common_sats.insert(j);

            // Satellite r (random)
            computeVisibleSatellite(x_r_LLA, j, sat_ENU, sat_ECEF, azel, range);
            ASSERT_MATRIX_APPROX(azel, computeAzel(sat_ECEF, x_r_ECEF), 1e-3);

            Satellite sat_r({0,j,sat_ECEF, Vector3d::Zero(), 1.0, 0, 0, 1});
            snapshot_r->getSatellites().emplace(j, sat_r);

            // Range r
            Range range_r;
            range_r.sat = j;
            range_r.L = range + CLIGHT * clock_r;
            range_r.L_corrected = range_r.L;
            range_r.L_var = 1;
            range_r.L2 = range_r.L;
            range_r.L2_corrected = range_r.L;
            range_r.L2_var = range_r.L_var;

            snapshot_r->getRanges().emplace(j, range_r);

            //std::cout << "\tsat: " << j << "\n";
            //std::cout << "\t\tsat_r_ECEF " << sat_ECEF.transpose() << ":\n";
            //std::cout << "\t\trange_r.L_corrected " << range_r.L_corrected << ":\n";

            // Satellite k (random)
            computeVisibleSatellite(x_k_LLA, j, sat_ENU, sat_ECEF, azel, range);
            ASSERT_MATRIX_APPROX(azel, computeAzel(sat_ECEF, x_k_ECEF), 1e-3);

            Satellite sat_k({0,j,sat_ECEF, Vector3d::Zero(), 1.0, 0, 0, 1});
            snapshot_k->getSatellites().emplace(j, sat_k);

            // Range k
            Range range_k;
            range_k.sat = j;
            range_k.L = range + CLIGHT * clock_k;
            range_k.L_corrected = range_k.L;
            range_k.L_var = 1;
            range_k.L2 = range_k.L;
            range_k.L2_corrected = range_k.L;
            range_k.L2_var = range_k.L_var;

            snapshot_k->getRanges().emplace(j, range_k);

            //std::cout << "\t\tsat_k_ECEF " << sat_ECEF.transpose() << ":\n";
            //std::cout << "\t\trange_k.L_corrected " << range_k.L_corrected << ":\n";
        }

        // Distort 2 ranges -> to be detected by RAIM
        snapshot_k->getRanges().at(wrong_sat1).L_corrected += distortion1;
        snapshot_k->getRanges().at(wrong_sat1).L += distortion1;
        snapshot_r->getRanges().at(wrong_sat2).L_corrected += distortion2;
        snapshot_r->getRanges().at(wrong_sat2).L += distortion2;


        // TDCP
        auto tdcp_out = Tdcp(snapshot_r,
                             snapshot_k,
                             x_r_ECEF,
                             common_sats,
                             Vector4d::Zero(),
                             tdcp_params);

        // CHECKS
        std::cout << "CHECKS iteration " << i << std::endl;
        ASSERT_EQ(tdcp_out.raim_discarded_sats.size(), 2);
        ASSERT_TRUE(tdcp_out.raim_discarded_sats.count(wrong_sat1));
        ASSERT_TRUE(tdcp_out.raim_discarded_sats.count(wrong_sat2));
        ASSERT_TRUE(tdcp_out.success);
        ASSERT_LE(tdcp_out.residual, 1e-3);
        ASSERT_MATRIX_APPROX(tdcp_out.d, d_gt, 1e-3);
    }
}

TEST(Tdcp, Tdcp_raim_residual_max_mah)
{
    int N_sats = 20;

    TdcpBatchParams tdcp_params;
    tdcp_params.max_iterations = 5;
    tdcp_params.min_common_sats = 6;
    tdcp_params.raim_n = 2;
    tdcp_params.relinearize_jacobian = true;
    tdcp_params.residual_opt = 1; // Max residual in Mahalanobis distance
    tdcp_params.max_residual = 3.84; // 95% of confidence
    tdcp_params.tdcp.multi_freq = false;
    tdcp_params.tdcp.sigma_atm = 1;
    tdcp_params.tdcp.sigma_carrier = 1;


    Vector3d sat_ENU, sat_ECEF;
    Vector3d x_r_LLA, x_r_ECEF, x_k_LLA, x_k_ECEF, d_ECEF;
    Matrix3d R_ENU_ECEF;
    Vector2d azel, azel2;
    Vector4d d, d_gt;
    Matrix4d cov_d;
    double range, residual;

    // Snapshots
    auto snapshot_r = std::make_shared<Snapshot>();
    auto snapshot_k = std::make_shared<Snapshot>();

    // Observations (just needed for the GPST)
    snapshot_r->setObservations(std::make_shared<Observations>());
    snapshot_k->setObservations(std::make_shared<Observations>());
    obsd_t obs_r;
    obs_r.time.sec = 0;
    obs_r.time.time = 0;
    snapshot_r->getObservations()->addObservation(obs_r);
    obsd_t obs_k;
    obs_k.time.sec = 0;
    obs_k.time.time = 1;
    snapshot_k->getObservations()->addObservation(obs_k);

    // Random receiver position
    for (auto i = 0; i<N_tests; i++)
    {
        // clear satellites and ranges
        snapshot_r->getSatellites().clear();
        snapshot_k->getSatellites().clear();
        snapshot_r->getRanges().clear();
        snapshot_k->getRanges().clear();

        // random setup
        x_r_LLA = computeRandomReceiverLatLonAlt();
        x_r_ECEF = latLonAltToEcef(x_r_LLA);
        d_ECEF = Vector3d::Random() * 10;
        x_k_ECEF = x_r_ECEF + d_ECEF;
        x_k_LLA = ecefToLatLonAlt(x_k_ECEF);

        double clock_r = Vector2d::Random()(0) * 1e-6;
        double clock_k = Vector2d::Random()(0) * 1e-6;

        // groundtruth
        d_gt.head<3>() = d_ECEF;
        d_gt(3) = (clock_k - clock_r) * CLIGHT;

        // distorted sats
        int wrong_sat1 = i % N_sats;
        int wrong_sat2 = (i + N_sats / 2) % N_sats;

        std::cout << "\nTest " << i << ":\n";
        std::cout << std::setprecision(10) << "\tx_r_ECEF " << x_r_ECEF.transpose() << ":\n";
        std::cout << "\tclock_r " << clock_r << ":\n";
        std::cout << std::setprecision(10) << "\tx_k_ECEF " << x_k_ECEF.transpose() << ":\n";
        std::cout << "\tclock_k " << clock_k << ":\n";
        std::cout << "\td_gt " << d_gt.transpose() << ":\n";
        std::cout << "\tdistorted sats: " << wrong_sat1 << ", " << wrong_sat2 << std::endl;

        std::set<int> common_sats;
        std::set<int> raim_discarded_sats;

        // random visible satellites
        for (auto j = 0; j<N_sats; j++)
        {
            common_sats.insert(j);

            // Satellite r (random)
            computeVisibleSatellite(x_r_LLA, j, sat_ENU, sat_ECEF, azel, range);
            ASSERT_MATRIX_APPROX(azel, computeAzel(sat_ECEF, x_r_ECEF), 1e-3);

            Satellite sat_r({0,j,sat_ECEF, Vector3d::Zero(), 1.0, 0, 0, 1});
            snapshot_r->getSatellites().emplace(j, sat_r);

            // Range r
            Range range_r;
            range_r.sat = j;
            range_r.L = range + CLIGHT * clock_r;
            range_r.L_corrected = range_r.L;
            range_r.L_var = 1;
            range_r.L2 = range_r.L;
            range_r.L2_corrected = range_r.L;
            range_r.L2_var = range_r.L_var;

            snapshot_r->getRanges().emplace(j, range_r);

            //std::cout << "\tsat: " << j << "\n";
            //std::cout << "\t\tsat_r_ECEF " << sat_ECEF.transpose() << ":\n";
            //std::cout << "\t\trange_r.L_corrected " << range_r.L_corrected << ":\n";

            // Satellite k (random)
            computeVisibleSatellite(x_k_LLA, j, sat_ENU, sat_ECEF, azel, range);
            ASSERT_MATRIX_APPROX(azel, computeAzel(sat_ECEF, x_k_ECEF), 1e-3);

            Satellite sat_k({0,j,sat_ECEF, Vector3d::Zero(), 1.0, 0, 0, 1});
            snapshot_k->getSatellites().emplace(j, sat_k);

            // Range k
            Range range_k;
            range_k.sat = j;
            range_k.L = range + CLIGHT * clock_k;
            range_k.L_corrected = range_k.L;
            range_k.L_var = 1;
            range_k.L2 = range_k.L;
            range_k.L2_corrected = range_k.L;
            range_k.L2_var = range_k.L_var;

            snapshot_k->getRanges().emplace(j, range_k);

            //std::cout << "\t\tsat_k_ECEF " << sat_ECEF.transpose() << ":\n";
            //std::cout << "\t\trange_k.L_corrected " << range_k.L_corrected << ":\n";
        }

        // Distort 2 ranges -> to be detected by RAIM
        snapshot_k->getRanges().at(wrong_sat1).L_corrected += distortion1;
        snapshot_k->getRanges().at(wrong_sat1).L += distortion1;
        snapshot_r->getRanges().at(wrong_sat2).L_corrected += distortion2;
        snapshot_r->getRanges().at(wrong_sat2).L += distortion2;

        // TDCP
        auto tdcp_out = Tdcp(snapshot_r,
                             snapshot_k,
                             x_r_ECEF,
                             common_sats,
                             Vector4d::Zero(),
                             tdcp_params);

        // CHECKS
        std::cout << "Checks iteration " << i << std::endl;
        ASSERT_EQ(tdcp_out.raim_discarded_sats.size(), 2);
        ASSERT_TRUE(tdcp_out.raim_discarded_sats.count(wrong_sat1));
        ASSERT_TRUE(tdcp_out.raim_discarded_sats.count(wrong_sat2));
        ASSERT_TRUE(tdcp_out.success);
        ASSERT_LE(tdcp_out.residual, 1e-3);
        ASSERT_MATRIX_APPROX(tdcp_out.d, d_gt, 1e-3);
    }
}

int main(int argc, char **argv)
{
    testing::InitGoogleTest(&argc, argv);
    return RUN_ALL_TESTS();
}