Skip to content

File main.cpp

File List > astrea > trace > trace > drivers > main.cpp

Go to the documentation of this file

/*
 * The GNU Lesser General Public License (LGPL)
 *
 * Copyright (c) 2025 Jay Iuliano
 *
 * This file is part of Astrea.
 * Astrea is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License
 * as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
 * Astrea is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
 * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should
 * have received a copy of the GNU General Public License along with Astrea. If not, see <https://www.gnu.org/licenses/>.
 */

#include <cstdlib>
#include <filesystem>
#include <fstream>
#include <iostream>
#include <ranges>
#include <set>
#include <stdio.h>

#include <sqlite3.h>

#include <csv.hpp>
#include <nlohmann/json.hpp>
#include <sqlite_orm/sqlite_orm.h>

#include <mp-units/systems/angular.h>
#include <mp-units/systems/international.h>
#include <mp-units/systems/isq.h>
#include <mp-units/systems/si.h>

#include <astro/astro.hpp>
#include <snapshot/snapshot.hpp>

#include <trace/trace.hpp>
#include <trace/trace.macros.hpp>

using namespace astrea;
using namespace astro;
using namespace trace;
using namespace snapshot;
using namespace sqlite_orm;

using namespace mp_units;
using mp_units::angular::unit_symbols::deg;
using mp_units::si::unit_symbols::km;
using mp_units::si::unit_symbols::m;
using mp_units::si::unit_symbols::s;

static const Time PROP_TIME         = months(1.0);
static const Time ACCESS_RESOLUTION = minutes(5.0);
static const bool PRINT_PROGRESS    = true;
static const Angle GRID_SPACING     = 5.0 * deg;


int arcturus_starlink_interference_test();
int iceye_test();

template <typename T, typename U>
AccessArray propagate_and_run_access_analysis(
    astro::Constellation<T>& constellation,
    U& grounds,
    const Date& startDate,
    const AstrodynamicsSystem& sys,
    const Time propTime,
    const Time accessResolution
);

int main()
{
    // return arcturus_starlink_interference_test();
    return iceye_test();
}

int arcturus_starlink_interference_test()
{

    // Setup system
    AstrodynamicsSystem sys;
    Date startDate = Date::now();

    // Query database
    auto snapshot = get_snapshot();
    auto geoGp = snapshot.get_all<GeneralPerturbations>(where(like(&GeneralPerturbations::OBJECT_NAME, "%%ARCTURUS%")));
    auto everythingElseGps =
        snapshot.get_all<GeneralPerturbations>(where(like(&GeneralPerturbations::OBJECT_NAME, "%%STARLINK%")));


    if (geoGp.size() == 0 || everythingElseGps.size() == 0) {
        throw std::runtime_error("Error: Nothing pulled from database. Are you sure you filled it?");
    }

    // Build constellation
    Viewer geo(geoGp[0], sys);
    Constellation<Viewer> allSats(everythingElseGps, sys);

    // Add sensors
    CircularFieldOfView fovGeo(15.0 * deg);
    CircularFieldOfView fovLeo(90.0 * deg);
    SensorParameters geoCone(&fovGeo);
    SensorParameters leoCone(&fovLeo);

    // for (auto& viewer : allSats | std::views::join) { // TODO: Figure out how this works
    //     viewer.attach_payload(simpleCone);
    // }

    geo.attach_payload(geoCone);
    for (auto& shell : allSats.get_shells()) {
        for (auto& plane : shell.get_planes()) {
            for (auto& sat : plane.get_all_spacecraft()) {
                sat.attach_payload(leoCone);
            }
        }
    }
    allSats.add_spacecraft(geo);

    // Build out grounds
    GroundStation dc(sys.get_central_body().get(), 38.895 * deg, -77.0366 * deg, 0.0 * km, { "Washington DC" });
    SensorParameters groundCone(
        &fovLeo,
        { 1.0 * m, // Anti-Nadir, units don't matter
          0.0 * m,
          0.0 * m }
    );
    dc.attach_payload(groundCone);

    LatLon corner1{ -50.0 * deg, -180.0 * deg };
    LatLon corner4{ 50.0 * deg, 180.0 * deg };
    Angle spacing = 10.0 * deg;
    Grid grid(sys.get_central_body().get(), corner1, corner4, GridType::UNIFORM, spacing);

    GroundArchitecture grounds({ dc });

    // Propagate and find access
    const AccessArray accesses = propagate_and_run_access_analysis(allSats, grid, startDate, sys, PROP_TIME, ACCESS_RESOLUTION);
    const AccessStats stats(accesses);

    // Save
    std::filesystem::path outdir = std::string(_TRACE_ROOT_) + "/trace/drivers/results/";

    save_risesets_to_file(accesses, outdir, allSats, grid);
    save_riseset_metrics_to_file(accesses, outdir, allSats, grid);
    save_receiver_riseset_metrics_to_file(stats, outdir, allSats, grid);
    save_access_metrics_to_file(stats, outdir, allSats, grid);
    save_number_of_folds_to_file(accesses, outdir, allSats, grid, ACCESS_RESOLUTION, PROP_TIME);

    // Call plotter
    std::filesystem::path plotFile = std::string(_TRACE_ROOT_) + "/pytrace/plots.py --outfile " + outdir.string() +
                                     " --target \"Washington DC\" --main \"ARCTURUS\"";
    const std::string cmd = "python3 " + plotFile.string();

    std::cout << "Plotting results with command: " << cmd << std::endl;
    return std::system(cmd.c_str());
}


int iceye_test()
{
    // Setup system
    AstrodynamicsSystem sys;
    Date startDate = Date::now();

    // Query database
    auto snapshot = get_snapshot();
    auto iceyeSats = snapshot.get_all<GeneralPerturbations>(where(like(&GeneralPerturbations::OBJECT_NAME, "%%ICEYE%%")));
    // auto iceyeSats = snapshot.get_all<GeneralPerturbations>(where(like(&GeneralPerturbations::OBJECT_NAME, "%%ICEYE-X61%%")));

    if (iceyeSats.size() == 0) {
        std::cerr << "No ICEYE satellites found in database!" << std::endl;
        return -1;
    }

    // Build constellation
    Constellation<Viewer> iceyeConstel(iceyeSats, sys);

    // Add sensors
    CircularFieldOfView fovLeo(15.0 * deg);
    SensorParameters leoCone(&fovLeo);

    for (auto& shell : iceyeConstel.get_shells()) {
        for (auto& plane : shell.get_planes()) {
            for (auto& sat : plane.get_all_spacecraft()) {
                sat.attach_payload(leoCone);
                // std::cout << sat.get_name() << " : " << sat.get_initial_state() << std::endl;
            }
        }
    }

    // Build out grounds
    GroundStation home(sys.get_central_body().get(), 60.1869 * deg, 24.8201 * deg, 0.0 * km, { "ICEYE Oy" });
    SensorParameters groundCone(&fovLeo, astro::RADIAL_RIC);
    home.attach_payload(groundCone);

    LatLon corner1{ -90.0 * deg, -180.0 * deg };
    LatLon corner4{ 90.0 * deg, 180.0 * deg };
    Grid grid(sys.get_central_body().get(), corner1, corner4, GridType::UNIFORM, GRID_SPACING);

    // Propagate and find access
    const AccessArray accesses = propagate_and_run_access_analysis(iceyeConstel, grid, startDate, sys, PROP_TIME, ACCESS_RESOLUTION);
    const AccessStats stats(accesses);

    // Save
    std::filesystem::path outdir = std::string(_TRACE_ROOT_) + "/trace/drivers/results/iceye";

    save_risesets_to_file(accesses, outdir, iceyeConstel, grid);
    save_riseset_metrics_to_file(accesses, outdir, iceyeConstel, grid);
    save_receiver_riseset_metrics_to_file(stats, outdir, iceyeConstel, grid);
    save_access_metrics_to_file(stats, outdir, iceyeConstel, grid);
    save_number_of_folds_to_file(accesses, outdir, iceyeConstel, grid, ACCESS_RESOLUTION, PROP_TIME);

    // Call plotter
    std::filesystem::path plotFile = std::string(_TRACE_ROOT_) + "/pytrace/tracer.py " + outdir.string();
    const std::string cmd          = "python3 " + plotFile.string();
    std::cout << "Plotting results with command: \n\t" << cmd << std::endl;
    return std::system(cmd.c_str());
}

template <typename T, typename U>
AccessArray propagate_and_run_access_analysis(
    astro::Constellation<T>& constellation,
    U& grounds,
    const Date& startDate,
    const AstrodynamicsSystem& sys,
    const Time propTime,
    const Time accessResolution
)
{
    // Setup integrator
    J2MeanVop eom;
    Integrator integrator;
    integrator.set_timestep(accessResolution);

    // Propagate
    auto start = std::chrono::steady_clock::now();

    const Date endDate = startDate + propTime;
    constellation.propagate(endDate, eom, integrator);

    auto end  = std::chrono::steady_clock::now();
    auto diff = std::chrono::duration_cast<nanoseconds>(end - start);

    if (PRINT_PROGRESS) {
        std::cout << std::endl << "Propagation Time: " << diff.count() / 1e9 << " (s)" << std::endl << std::endl;
    }
    start = std::chrono::steady_clock::now();

    // Find access
    AccessAnalyzer analyzer(accessResolution, startDate, endDate, sys, true);
    const auto accesses = analyzer.find_accesses(constellation, grounds, true);

    end  = std::chrono::steady_clock::now();
    diff = std::chrono::duration_cast<nanoseconds>(end - start);

    if (PRINT_PROGRESS) {
        std::cout << std::endl
                  << std::endl
                  << "Access Analysis Time: " << diff.count() / 1.0e9 << " (s)" << std::endl
                  << std::endl;
    }

    return accesses;
}