From 79bfe3d01fd2a721cc2b4c1edc75b611da397798 Mon Sep 17 00:00:00 2001 From: vishwa shah Date: Mon, 28 Oct 2024 19:46:50 +0000 Subject: [PATCH 01/22] feat: working version with tests --- .../Astrodynamics/Access/Generator.hpp | 127 ++++++ .../Astrodynamics/Access/Generator.cpp | 378 ++++++++++++++++-- .../Astrodynamics/Access/Generator.test.cpp | 235 ++++++++++- 3 files changed, 710 insertions(+), 30 deletions(-) diff --git a/include/OpenSpaceToolkit/Astrodynamics/Access/Generator.hpp b/include/OpenSpaceToolkit/Astrodynamics/Access/Generator.hpp index 67dbeba50..a5e9685e9 100644 --- a/include/OpenSpaceToolkit/Astrodynamics/Access/Generator.hpp +++ b/include/OpenSpaceToolkit/Astrodynamics/Access/Generator.hpp @@ -9,6 +9,7 @@ #include #include +#include #include #include @@ -34,6 +35,7 @@ using ostk::core::type::Real; using ostk::core::type::Shared; using ostk::mathematics::object::Interval; +using ostk::mathematics::object::Matrix3d; using ostk::physics::coordinate::Position; using ostk::physics::coordinate::spherical::AER; @@ -43,6 +45,7 @@ using ostk::physics::time::Duration; using ostk::physics::time::Instant; using ostk::physics::unit::Angle; using ostk::physics::unit::Length; +using EarthGravitationalModel = ostk::physics::environment::gravitational::Earth; using ostk::astrodynamics::Access; using ostk::astrodynamics::Trajectory; @@ -51,6 +54,117 @@ using ostk::astrodynamics::trajectory::State; #define DEFAULT_STEP Duration::Minutes(1.0) #define DEFAULT_TOLERANCE Duration::Microseconds(1.0) +/// @brief Represents the configuration for a ground target, including azimuth, elevation, and range intervals, as well +/// as position and LLA (Latitude, Longitude, Altitude). +/// +/// @details This class provides methods to retrieve the trajectory, position, LLA, and intervals for azimuth, +/// elevation, and range. It also includes a method to get the SEZ (South-East-Zenith) rotation matrix. +struct GroundTargetConfiguration +{ + /// @brief Constructor + /// @param anAzimuthInterval An azimuth interval [deg] + /// @param anElevationInterval An elevation interval [deg] + /// @param aRangeInterval A range interval [m] + /// @param aPosition A position + GroundTargetConfiguration( + const Interval& anAzimuthInterval, + const Interval& anElevationInterval, + const Interval& aRangeInterval, + const Position& aPosition + ); + + /// @brief Constructor + /// @param anAzimuthInterval An azimuth interval [deg] + /// @param anElevationInterval An elevation interval [deg] + /// @param aRangeInterval A range interval [m] + /// @param aLLA An LLA + GroundTargetConfiguration( + const Interval& anAzimuthInterval, + const Interval& anElevationInterval, + const Interval& aRangeInterval, + const LLA& aLLA + ); + + /// @brief Get the trajectory + /// + /// @code{.cpp} + /// GroundStationConfiguration groundStationConfiguration = { ... } ; + /// Trajectory trajectory = groundStationConfiguration.getTrajectory(); + /// @endcode + /// + /// @return The trajectory + Trajectory getTrajectory() const; + + /// @brief Get the position + /// + /// @code{.cpp} + /// GroundStationConfiguration groundStationConfiguration = { ... } ; + /// Position position = groundStationConfiguration.getPosition(); + /// @endcode + /// + /// @return The position + Position getPosition() const; + + /// @brief Get the latitude, longitude, and altitude (LLA) + /// + /// @code{.cpp} + /// GroundStationConfiguration groundStationConfiguration = { ... } ; + /// LLA lla = groundStationConfiguration.getLLA(); + /// @endcode + /// + /// @return The latitude, longitude, and altitude (LLA) + LLA getLLA() const; + + /// @brief Get the azimuth interval + /// + /// @code{.cpp} + /// GroundStationConfiguration groundStationConfiguration = { ... } ; + /// Interval groundStationConfiguration = generator.getAzimuthInterval(); + /// @endcode + /// + /// @return The azimuth interval + Interval getAzimuthInterval() const; + + /// @brief Get the elevation interval + /// + /// @code{.cpp} + /// GroundStationConfiguration groundStationConfiguration = { ... } ; + /// Interval groundStationConfiguration = generator.getElevationInterval(); + /// @endcode + /// + /// @return The elevation interval + Interval getElevationInterval() const; + + /// @brief Get the range interval + /// + /// @code{.cpp} + /// GroundStationConfiguration groundStationConfiguration = { ... } ; + /// Interval groundStationConfiguration = generator.getRangeInterval(); + /// @endcode + /// + /// @return The range interval + Interval getRangeInterval() const; + + /// @brief Get the rotation matrix (Matrix3d) from ECEF (Earth-Centered-Earth-Fixed) to SEZ (South-East-Zenith) + /// frame + /// + /// @code{.cpp} + /// GroundStationConfiguration groundStationConfiguration = { ... } ; + /// Matrix3d sezRotation = groundStationConfiguration.getR_SEZ_ECEF(); + /// @endcode + /// + /// @return The SEZ rotation matrix + Matrix3d getR_SEZ_ECEF() const; + + private: + Interval azimuthInterval_; + Interval elevationInterval_; + Interval rangeInterval_; + + Position position_; + LLA lla_; +}; + class Generator { public: @@ -89,6 +203,12 @@ class Generator const physics::time::Interval& anInterval, const Trajectory& aFromTrajectory, const Trajectory& aToTrajectory ) const; + Array> computeAccessesWithGroundTargets( + const physics::time::Interval& anInterval, + const Array& someGroundTargetConfigurations, + const Trajectory& aToTrajectory + ) const; + void setStep(const Duration& aStep); void setTolerance(const Duration& aTolerance); @@ -137,6 +257,13 @@ class Generator std::function accessFilter_; std::function stateFilter_; + Array generateAccessesFromIntervals( + const Array& someIntervals, + const physics::time::Interval& anInterval, + const Trajectory& aFromTrajectory, + const Trajectory& aToTrajectory + ) const; + static Access GenerateAccess( const physics::time::Interval& anAccessInterval, const physics::time::Interval& aGlobalInterval, diff --git a/src/OpenSpaceToolkit/Astrodynamics/Access/Generator.cpp b/src/OpenSpaceToolkit/Astrodynamics/Access/Generator.cpp index ac72c30b9..106472ff3 100644 --- a/src/OpenSpaceToolkit/Astrodynamics/Access/Generator.cpp +++ b/src/OpenSpaceToolkit/Astrodynamics/Access/Generator.cpp @@ -1,5 +1,6 @@ /// Apache License 2.0 +#include #include #include @@ -9,17 +10,23 @@ #include #include +#include #include using ostk::mathematics::geometry::d3::object::Point; using ostk::mathematics::geometry::d3::object::Segment; +using ostk::mathematics::object::MatrixXd; +using ostk::mathematics::object::Vector3d; +using ostk::mathematics::object::VectorXd; -using ostk::astrodynamics::solver::TemporalConditionSolver; using ostk::physics::coordinate::Frame; using ostk::physics::coordinate::spherical::LLA; using ostk::physics::environment::Object; using ostk::physics::environment::object::celestial::Earth; +using ostk::astrodynamics::RootSolver; +using ostk::astrodynamics::solver::TemporalConditionSolver; + namespace ostk { namespace astrodynamics @@ -27,6 +34,92 @@ namespace astrodynamics namespace access { +GroundTargetConfiguration::GroundTargetConfiguration( + const Interval& anAzimuthInterval, + const Interval& anElevationInterval, + const Interval& aRangeInterval, + const Position& aPosition +) + : azimuthInterval_(anAzimuthInterval), + elevationInterval_(anElevationInterval), + rangeInterval_(aRangeInterval), + position_(aPosition), + lla_(LLA::Cartesian( + aPosition.getCoordinates(), + EarthGravitationalModel::EGM2008.equatorialRadius_, + EarthGravitationalModel::EGM2008.flattening_ + )) +{ + if (aPosition.isDefined() && aPosition.accessFrame() != Frame::ITRF()) + { + throw ostk::core::error::RuntimeError("The position frame must be ITRF."); + } +} + +GroundTargetConfiguration::GroundTargetConfiguration( + const Interval& anAzimuthInterval, + const Interval& anElevationInterval, + const Interval& aRangeInterval, + const LLA& aLLA +) + : azimuthInterval_(anAzimuthInterval), + elevationInterval_(anElevationInterval), + rangeInterval_(aRangeInterval), + position_(Position::Meters( + aLLA.toCartesian( + EarthGravitationalModel::EGM2008.equatorialRadius_, EarthGravitationalModel::EGM2008.flattening_ + ), + Frame::ITRF() + )), + lla_(aLLA) +{ +} + +Trajectory GroundTargetConfiguration::getTrajectory() const +{ + return Trajectory::Position(position_); +} + +Position GroundTargetConfiguration::getPosition() const +{ + return position_; +} + +LLA GroundTargetConfiguration::getLLA() const +{ + return lla_; +} + +Interval GroundTargetConfiguration::getAzimuthInterval() const +{ + return azimuthInterval_; +} + +Interval GroundTargetConfiguration::getElevationInterval() const +{ + return elevationInterval_; +} + +Interval GroundTargetConfiguration::getRangeInterval() const +{ + return rangeInterval_; +} + +Matrix3d GroundTargetConfiguration::getR_SEZ_ECEF() const +{ + // TBM: Move this to OSTk physics as a utility function + const double sinLat = std::sin(lla_.getLatitude().inRadians()); + const double cosLat = std::cos(lla_.getLatitude().inRadians()); + const double sinLon = std::sin(lla_.getLongitude().inRadians()); + const double cosLon = std::cos(lla_.getLongitude().inRadians()); + + Matrix3d SEZRotation; + SEZRotation << sinLat * cosLon, sinLat * sinLon, -cosLat, -sinLon, cosLon, 0.0, cosLat * cosLon, cosLat * sinLon, + sinLat; + + return SEZRotation; +} + Generator::Generator(const Environment& anEnvironment, const Duration& aStep, const Duration& aTolerance) : environment_(anEnvironment), step_(aStep), @@ -152,23 +245,235 @@ Array Generator::computeAccesses( const Shared earthSPtr = this->environment_.accessCelestialObjectWithName("Earth"); - return accessIntervals - .map( - [&anInterval, &aFromTrajectory, &aToTrajectory, &earthSPtr, this]( - const physics::time::Interval& anAccessInterval - ) -> Access + return generateAccessesFromIntervals(accessIntervals, anInterval, aFromTrajectory, aToTrajectory); +} + +Array> Generator::computeAccessesWithGroundTargets( + const physics::time::Interval& anInterval, + const Array& someGroundTargetConfigurations, + const Trajectory& aToTrajectory +) const +{ + // create a stacked matrix of SEZ rotations for all ground targets + const Index targetCount = someGroundTargetConfigurations.getSize(); + Eigen::Matrix SEZRotations(3, 3 * targetCount); + + for (Index i = 0; i < targetCount; ++i) + { + SEZRotations.block(0, 3 * i, 3, 3) = someGroundTargetConfigurations[i].getR_SEZ_ECEF(); + } + + // create a stacked matrix of ITRF positions for all ground targets + MatrixXd fromPositionCoordinates_ITRF = MatrixXd::Zero(3, targetCount); + + for (Index i = 0; i < targetCount; ++i) + { + fromPositionCoordinates_ITRF.col(i) = someGroundTargetConfigurations[i].getPosition().getCoordinates(); + } + + // create a stacked matrix of azimuth, elevation, and range bounds for all ground targets + MatrixXd aerLowerBounds = MatrixXd::Zero(targetCount, 3); + MatrixXd aerUpperBounds = MatrixXd::Zero(targetCount, 3); + + for (Index i = 0; i < targetCount; ++i) + { + aerLowerBounds(i, 0) = + someGroundTargetConfigurations[i].getAzimuthInterval().accessLowerBound() * Real::Pi() / 180.0; + aerLowerBounds(i, 1) = + someGroundTargetConfigurations[i].getElevationInterval().accessLowerBound() * Real::Pi() / 180.0; + aerLowerBounds(i, 2) = + someGroundTargetConfigurations[i].getRangeInterval().accessLowerBound() * Real::Pi() / 180.0; + + aerUpperBounds(i, 0) = + someGroundTargetConfigurations[i].getAzimuthInterval().accessUpperBound() * Real::Pi() / 180.0; + aerUpperBounds(i, 1) = + someGroundTargetConfigurations[i].getElevationInterval().accessUpperBound() * Real::Pi() / 180.0; + aerUpperBounds(i, 2) = + someGroundTargetConfigurations[i].getRangeInterval().accessUpperBound() * Real::Pi() / 180.0; + } + + // initialize solver and condition + const RootSolver rootSolver = RootSolver(100, this->tolerance_.inSeconds()); + + const auto createAccessCondition = + [&fromPositionCoordinates_ITRF, &SEZRotations, &aToTrajectory, &aerLowerBounds, &aerUpperBounds]( + const Index& targetIdx + ) + { + const Vector3d fromPositionCoordinate_ITRF = fromPositionCoordinates_ITRF.col(targetIdx); + + const Matrix3d SEZRotation = SEZRotations.block(0, 3 * targetIdx, 3, 3); + + const double azimuthLowerBound = aerLowerBounds(targetIdx, 0); + const double azimuthUpperBound = aerUpperBounds(targetIdx, 0); + const double elevationLowerBound = aerLowerBounds(targetIdx, 1); + const double elevationUpperBound = aerUpperBounds(targetIdx, 1); + const double rangeLowerBound = aerLowerBounds(targetIdx, 2); + const double rangeUpperBound = aerUpperBounds(targetIdx, 2); + + return [fromPositionCoordinate_ITRF, + SEZRotation, + azimuthLowerBound, + azimuthUpperBound, + elevationLowerBound, + elevationUpperBound, + rangeLowerBound, + rangeUpperBound, + &aToTrajectory](const Instant& instant) -> bool + { + const auto toPositionCoordinates_ITRF = + aToTrajectory.getStateAt(instant).inFrame(Frame::ITRF()).getPosition().getCoordinates(); + + const VectorXd dx = toPositionCoordinates_ITRF - fromPositionCoordinate_ITRF; + + const MatrixXd dx_SEZ = SEZRotation * dx; + + const double range = dx_SEZ.norm(); + const double elevation_rad = std::asin(dx_SEZ(2) / range); + double azimuth_rad = std::atan2(dx_SEZ(1), dx_SEZ(0)); + + azimuth_rad = azimuth_rad < 0.0 ? azimuth_rad + 2.0 * M_PI : azimuth_rad; + + return azimuth_rad > azimuthLowerBound && azimuth_rad < azimuthUpperBound && + elevation_rad > elevationLowerBound && elevation_rad < elevationUpperBound && + range > rangeLowerBound && range < rangeUpperBound; + }; + }; + + // initialize containers + const Array instants = anInterval.generateGrid(this->step_); + + Array> conditionArray(targetCount, nullptr); + for (Index i = 0; i < targetCount; ++i) + { + conditionArray[i] = createAccessCondition(i); + } + + Array> accessIntervals = + Array>(targetCount, Array::Empty()); + Array previousAccessStates = Array(targetCount, false); + + for (Index index = 0; index < instants.getSize(); ++index) + { + const Instant& instant = instants[index]; + + // calculate target to satellite vector in ITRF + const Vector3d toPositionCoordinates_ITRF = + aToTrajectory.getStateAt(instant).inFrame(Frame::ITRF()).getPosition().getCoordinates(); + + const MatrixXd dx = + toPositionCoordinates_ITRF.replicate(1, fromPositionCoordinates_ITRF.cols()) - fromPositionCoordinates_ITRF; + + // calculate target to satellite vector in SEZ + MatrixXd dx_SEZ = MatrixXd::Zero(3, dx.cols()); + for (Index i = 0; i < (Index)dx.cols(); ++i) + { + dx_SEZ.col(i) = SEZRotations.block<3, 3>(0, 3 * i) * dx.col(i); + } + + // calculate azimuth, elevation, and range + const VectorXd range = dx_SEZ.colwise().norm(); + const VectorXd elevation_rad = (dx_SEZ.row(2).transpose().array() / range.array()).asin(); + VectorXd azimuth_rad = dx_SEZ.row(0).array().binaryExpr( + dx_SEZ.row(1).array(), + [](double x, double y) { - return Generator::GenerateAccess( - anAccessInterval, anInterval, aFromTrajectory, aToTrajectory, earthSPtr, this->tolerance_ - ); + return std::atan2(y, x); } - ) - .getWhere( - [this](const Access& anAccess) -> bool + ); + + azimuth_rad = azimuth_rad.unaryExpr( + [](double x) { - return this->accessFilter_ ? this->accessFilter_(anAccess) : true; + return x < 0.0 ? x + 2.0 * M_PI : x; } ); + + // check if satellite is in access + const auto inAccess = + (azimuth_rad.array() > aerLowerBounds.col(0).array() && + azimuth_rad.array() < aerUpperBounds.col(0).array() && + elevation_rad.array() > aerLowerBounds.col(1).array() && + elevation_rad.array() < aerUpperBounds.col(1).array() && range.array() > aerLowerBounds.col(2).array() && + range.array() < aerUpperBounds.col(2).array()) + .eval(); + + // First instant handling + if (index == 0) + { + for (Index i = 0; i < (Index)inAccess.size(); ++i) + { + previousAccessStates[i] = inAccess[i]; + if (inAccess[i]) + { + accessIntervals[i].add(physics::time::Interval::Closed(instant, instant)); + } + } + continue; + } + + const Instant& previousInstant = instants[index - 1]; + + // Check for state changes and find exact crossings + + for (Index i = 0; i < (Index)inAccess.size(); ++i) + { + const bool currentAccess = inAccess[i]; + + if (currentAccess != previousAccessStates[i]) + { + // Find exact crossing time + const auto condition = conditionArray[i]; + + const auto result = rootSolver.solve( + [&previousInstant, &condition](double aDurationInSeconds) -> double + { + return condition(previousInstant + Duration::Seconds(aDurationInSeconds)) ? +1.0 : -1.0; + }, + 0.0, + Duration::Between(previousInstant, instant).inSeconds() + ); + + const Instant crossingTime = previousInstant + Duration::Seconds(result.root); + + if (currentAccess) // Rising edge + { + accessIntervals[i].add(physics::time::Interval::Closed(crossingTime, instant)); + } + else // Falling edge + { + if (!accessIntervals[i].isEmpty()) + { + const physics::time::Interval& lastInterval = accessIntervals[i].accessLast(); + accessIntervals[i].accessLast() = + physics::time::Interval::Closed(lastInterval.getStart(), crossingTime); + } + } + } + else if (currentAccess) // Continuing access + { + if (!accessIntervals[i].isEmpty()) + { + const physics::time::Interval& lastInterval = accessIntervals[i].accessLast(); + accessIntervals[i].accessLast() = physics::time::Interval::Closed(lastInterval.getStart(), instant); + } + } + + previousAccessStates[i] = currentAccess; + } + } + + const Shared earthSPtr = this->environment_.accessCelestialObjectWithName("Earth"); + + Array> accesses = Array>(targetCount, Array::Empty()); + for (Index i = 0; i < accessIntervals.getSize(); ++i) + { + const Trajectory fromTrajectory = someGroundTargetConfigurations[i].getTrajectory(); + accesses[i] = + this->generateAccessesFromIntervals(accessIntervals[i], anInterval, fromTrajectory, aToTrajectory); + } + + return accesses; } void Generator::setStep(const Duration& aStep) @@ -224,16 +529,6 @@ Generator Generator::AerRanges( const Interval elevationRange_deg = anElevationRange; const Interval rangeRange_m = aRangeRange; - // const Interval azimuthRange_deg = anAzimuthRange; // anAzimuthRange.isDefined() ? - // Interval(anAzimuthRange.accessLowerBound().inDegrees(0.0, +360.0), - // anAzimuthRange.accessUpperBound().inDegrees(0.0, +360.0), anAzimuthRange.getType()) : Interval::Undefined() - //; const Interval elevationRange_deg = anElevationRange; // anElevationRange.isDefined() ? - // Interval(anElevationRange.accessLowerBound().inDegrees(-180.0, +180.0), - // anElevationRange.accessUpperBound().inDegrees(-180.0, +180.0), anElevationRange.getType()) : - // Interval::Undefined(); const Interval rangeRange_m = aRangeRange; // aRangeRange.isDefined() ? - // Interval(aRangeRange.accessLowerBound().inMeters(), aRangeRange.accessUpperBound().inMeters(), - // aRangeRange.getType()) : Interval::Undefined(); - const std::function aerFilter = [azimuthRange_deg, elevationRange_deg, rangeRange_m](const AER& anAER) -> bool { @@ -311,6 +606,34 @@ Generator Generator::AerMask( return {anEnvironment, aerFilter}; } +Array Generator::generateAccessesFromIntervals( + const Array& someIntervals, + const physics::time::Interval& anInterval, + const Trajectory& aFromTrajectory, + const Trajectory& aToTrajectory +) const +{ + const Shared earthSPtr = this->environment_.accessCelestialObjectWithName("Earth"); + + return someIntervals + .map( + [&anInterval, &aFromTrajectory, &aToTrajectory, &earthSPtr, this]( + const physics::time::Interval& anAccessInterval + ) -> Access + { + return Generator::GenerateAccess( + anAccessInterval, anInterval, aFromTrajectory, aToTrajectory, earthSPtr, this->tolerance_ + ); + } + ) + .getWhere( + [this](const Access& anAccess) -> bool + { + return this->accessFilter_ ? this->accessFilter_(anAccess) : true; + } + ); +} + Access Generator::GenerateAccess( const physics::time::Interval& anAccessInterval, const physics::time::Interval& aGlobalInterval, @@ -474,6 +797,7 @@ bool GeneratorContext::isAccessActive(const Instant& anInstant) const auto [fromPosition, toPosition] = GeneratorContext::GetPositionsFromStates(fromState, toState); // Line of sight + // TBI: Remove this check as it is redundant static const Shared commonFrameSPtr = Frame::GCRF(); @@ -542,12 +866,10 @@ AER GeneratorContext::CalculateAer( ) { // [TBM] This logic is Earth-specific - const Point referencePoint_ITRF = - Point::Vector(aFromPosition.inFrame(Frame::ITRF(), anInstant).accessCoordinates()); + const Vector3d referenceCoordinates_ITRF = aFromPosition.inFrame(Frame::ITRF(), anInstant).accessCoordinates(); - const LLA referencePoint_LLA = LLA::Cartesian( - referencePoint_ITRF.asVector(), anEarthSPtr->getEquatorialRadius(), anEarthSPtr->getFlattening() - ); + const LLA referencePoint_LLA = + LLA::Cartesian(referenceCoordinates_ITRF, anEarthSPtr->getEquatorialRadius(), anEarthSPtr->getFlattening()); const Shared nedFrameSPtr = anEarthSPtr->getFrameAt(referencePoint_LLA, Earth::FrameType::NED); diff --git a/test/OpenSpaceToolkit/Astrodynamics/Access/Generator.test.cpp b/test/OpenSpaceToolkit/Astrodynamics/Access/Generator.test.cpp index 134761821..06d8e4807 100644 --- a/test/OpenSpaceToolkit/Astrodynamics/Access/Generator.test.cpp +++ b/test/OpenSpaceToolkit/Astrodynamics/Access/Generator.test.cpp @@ -7,6 +7,8 @@ #include #include +#include + #include #include #include @@ -17,6 +19,8 @@ #include #include #include +#include +#include #include @@ -25,9 +29,13 @@ using ostk::core::container::Table; using ostk::core::container::Tuple; using ostk::core::filesystem::File; using ostk::core::filesystem::Path; +using ostk::core::type::Index; using ostk::core::type::Real; using ostk::core::type::String; +using ostk::mathematics::object::Matrix3d; +using ostk::mathematics::object::Vector3d; + using ostk::physics::coordinate::Frame; using ostk::physics::coordinate::Position; using ostk::physics::coordinate::spherical::AER; @@ -46,6 +54,7 @@ using ostk::physics::unit::Length; using ostk::astrodynamics::Access; using ostk::astrodynamics::access::Generator; +using ostk::astrodynamics::access::GroundTargetConfiguration; using ostk::astrodynamics::Trajectory; using ostk::astrodynamics::trajectory::Orbit; using ostk::astrodynamics::trajectory::orbit::model::Kepler; @@ -646,6 +655,226 @@ TEST(OpenSpaceToolkit_Astrodynamics_Access_Generator, ComputeAccesses_4) } } +TEST(OpenSpaceToolkit_Astrodynamics_Access_Generator, GroundTargetConfiguration) +{ + const ostk::mathematics::object::Interval azimuthInterval = + ostk::mathematics::object::Interval::Closed(0.0, 360.0); + const ostk::mathematics::object::Interval elevationInterval = + ostk::mathematics::object::Interval::Closed(0.0, 90.0); + const ostk::mathematics::object::Interval rangeInterval = + ostk::mathematics::object::Interval::Closed(0.0, 1.0e10); + const LLA lla = LLA::Vector({0.0, 0.0, 0.0}); + + // Constructor + { + { + EXPECT_NO_THROW(GroundTargetConfiguration(azimuthInterval, elevationInterval, rangeInterval, lla)); + } + + { + { + const Position position = Position::Meters({0.0, 0.0, 0.0}, Frame::ITRF()); + EXPECT_NO_THROW(GroundTargetConfiguration(azimuthInterval, elevationInterval, rangeInterval, position)); + } + + { + const Position position = Position::Meters({0.0, 0.0, 0.0}, Frame::GCRF()); + EXPECT_THROW( + GroundTargetConfiguration(azimuthInterval, elevationInterval, rangeInterval, position), + ostk::core::error::RuntimeError + ); + } + } + } + + // Getters + { + const GroundTargetConfiguration groundTargetConfiguration = + GroundTargetConfiguration(azimuthInterval, elevationInterval, rangeInterval, lla); + EXPECT_EQ(groundTargetConfiguration.getAzimuthInterval(), azimuthInterval); + EXPECT_EQ(groundTargetConfiguration.getElevationInterval(), elevationInterval); + EXPECT_EQ(groundTargetConfiguration.getRangeInterval(), rangeInterval); + EXPECT_EQ(groundTargetConfiguration.getLLA(), lla); + EXPECT_EQ( + groundTargetConfiguration.getPosition(), + Position::Meters( + lla.toCartesian(Earth::EGM2008.equatorialRadius_, Earth::EGM2008.flattening_), Frame::ITRF() + ) + ); + EXPECT_NO_THROW(groundTargetConfiguration.getTrajectory()); + + Matrix3d r_SEZ_ECEF; + r_SEZ_ECEF.row(0) = Vector3d {0.0, 0.0, 1.0}; + r_SEZ_ECEF.row(1) = Vector3d {0.0, 1.0, 0.0}; + r_SEZ_ECEF.row(2) = Vector3d {-1.0, 0.0, 0.0}; + + EXPECT_EQ(groundTargetConfiguration.getR_SEZ_ECEF(), r_SEZ_ECEF); + } +} + +TEST(OpenSpaceToolkit_Astrodynamics_Access_Generator, ComputeAccessesWithGroundTargets) +{ + { + const Environment environment = Environment::Default(); + + const TLE tle = { + "1 60504U 24149AN 24293.10070306 .00000000 00000-0 58313-3 0 08", + "2 60504 97.4383 7.6998 0003154 274.9510 182.9597 15.19652001 9607", + }; + const SGP4 sgp4 = SGP4(tle); + const Orbit aToTrajectory = Orbit(sgp4, environment.accessCelestialObjectWithName("Earth")); + + const Instant startInstant = Instant::Parse("2024-10-19 02:25:00.744.384", Scale::UTC); + const Instant endInstant = startInstant + Duration::Days(1.0); + const Interval interval = Interval::Closed(startInstant, endInstant); + + const Array LLAs = { + LLA(Angle::Degrees(53.406), Angle::Degrees(-6.225), Length::Meters(50.5)), + LLA(Angle::Degrees(13.51), Angle::Degrees(144.82), Length::Meters(46)), + LLA(Angle::Degrees(42.77), Angle::Degrees(141.62), Length::Meters(100)), + LLA(Angle::Degrees(47.2393), Angle::Degrees(-119.88515), Length::Meters(392.5)), + LLA(Angle::Degrees(78.22702), Angle::Degrees(15.38624), Length::Meters(493)), + LLA(Angle::Degrees(60.674), Angle::Degrees(17.142), Length::Meters(100)), + LLA(Angle::Degrees(37.7716), Angle::Degrees(-122.4135), Length::Meters(100)), + LLA(Angle::Degrees(69.09), Angle::Degrees(17.6986), Length::Meters(12)), + LLA(Angle::Degrees(78.23164), Angle::Degrees(15.37725), Length::Meters(483)), + LLA(Angle::Degrees(-72.0021), Angle::Degrees(2.5251), Length::Meters(1401)), + LLA(Angle::Degrees(-25.89), Angle::Degrees(27.71), Length::Meters(1562.66)), + LLA(Angle::Degrees(-46.53), Angle::Degrees(168.38), Length::Meters(13.65)), + LLA(Angle::Degrees(71.275), Angle::Degrees(-156.806), Length::Meters(24)), + LLA(Angle::Degrees(-52.9351), Angle::Degrees(-70.8713), Length::Meters(23)) + }; + + const ostk::mathematics::object::Interval azimuthInterval = + ostk::mathematics::object::Interval::Closed(0.0, 360.0); + const ostk::mathematics::object::Interval elevationInterval = + ostk::mathematics::object::Interval::Closed(0.0, 90.0); + const ostk::mathematics::object::Interval rangeInterval = + ostk::mathematics::object::Interval::Closed(0.0, 1.0e10); + + Array groundTargets = LLAs.map( + [&azimuthInterval, &elevationInterval, &rangeInterval](const LLA& lla) -> GroundTargetConfiguration + { + return GroundTargetConfiguration(azimuthInterval, elevationInterval, rangeInterval, lla); + } + ); + + const Generator generator = + Generator::AerRanges(azimuthInterval, elevationInterval, rangeInterval, environment); + + const Array> accessesPerTarget = + generator.computeAccessesWithGroundTargets(interval, groundTargets, aToTrajectory); + + ASSERT_EQ(accessesPerTarget.getSize(), groundTargets.getSize()); + + for (Index i = 0; i < accessesPerTarget.getSize(); ++i) + { + const Array accesses = accessesPerTarget.at(i); + const GroundTargetConfiguration groundTarget = groundTargets.at(i); + + const Array expectedAccesses = + generator.computeAccesses(interval, groundTarget.getTrajectory(), aToTrajectory); + + ASSERT_EQ(accesses.getSize(), expectedAccesses.getSize()); + + for (Index j = 0; j < accesses.getSize(); ++j) + { + const Access& access = accesses.at(j); + const Access& expectedAccess = expectedAccesses.at(j); + + EXPECT_TRUE(access.getAcquisitionOfSignal().isNear( + expectedAccess.getAcquisitionOfSignal(), Duration::Microseconds(1.0) + )) << access.getAcquisitionOfSignal().toString() + << " ~ " << expectedAccess.getAcquisitionOfSignal().toString(); + EXPECT_TRUE(access.getTimeOfClosestApproach().isNear( + expectedAccess.getTimeOfClosestApproach(), Duration::Microseconds(1.0) + )) << access.getTimeOfClosestApproach().toString() + << " ~ " << expectedAccess.getTimeOfClosestApproach().toString(); + EXPECT_TRUE( + access.getLossOfSignal().isNear(expectedAccess.getLossOfSignal(), Duration::Microseconds(1.0)) + ) << access.getLossOfSignal().toString() + << " ~ " << expectedAccess.getLossOfSignal().toString(); + EXPECT_TRUE(access.getDuration().isNear(expectedAccess.getDuration(), Duration::Microseconds(1.0))) + << access.getDuration().toString() << " ~ " << expectedAccess.getDuration().toString(); + } + } + } +} + +TEST(OpenSpaceToolkit_Astrodynamics_Access_Generator, SpeedTest) +{ + { + using ostk::astrodynamics::trajectory::orbit::model::Tabulated; + using ostk::mathematics::curvefitting::Interpolator; + const Environment environment = Environment::Default(); + + const TLE tle = { + "1 60504U 24149AN 24293.10070306 .00000000 00000-0 58313-3 0 08", + "2 60504 97.4383 7.6998 0003154 274.9510 182.9597 15.19652001 9607", + }; + const SGP4 sgp4 = SGP4(tle); + const Orbit aToTrajectory = Orbit(sgp4, environment.accessCelestialObjectWithName("Earth")); + + const Instant startInstant = Instant::Parse("2024-10-19 02:25:00.744.384", Scale::UTC); + const Instant endInstant = startInstant + Duration::Days(14.0); + const Interval interval = Interval::Closed(startInstant, endInstant); + + const Array instants = interval.generateGrid(Duration::Seconds(20.0)); + const Array states = aToTrajectory.getStatesAt(instants); + const Orbit tabulatedOrbit = Orbit( + Tabulated(states, 1, Interpolator::Type::CubicSpline), environment.accessCelestialObjectWithName("Earth") + ); + + const Array LLAs = { + LLA(Angle::Degrees(53.406), Angle::Degrees(-6.225), Length::Meters(50.5)), + LLA(Angle::Degrees(13.51), Angle::Degrees(144.82), Length::Meters(46)), + LLA(Angle::Degrees(42.77), Angle::Degrees(141.62), Length::Meters(100)), + LLA(Angle::Degrees(47.2393), Angle::Degrees(-119.88515), Length::Meters(392.5)), + LLA(Angle::Degrees(78.22702), Angle::Degrees(15.38624), Length::Meters(493)), + LLA(Angle::Degrees(60.674), Angle::Degrees(17.142), Length::Meters(100)), + LLA(Angle::Degrees(37.7716), Angle::Degrees(-122.4135), Length::Meters(100)), + LLA(Angle::Degrees(69.09), Angle::Degrees(17.6986), Length::Meters(12)), + LLA(Angle::Degrees(78.23164), Angle::Degrees(15.37725), Length::Meters(483)), + LLA(Angle::Degrees(-72.0021), Angle::Degrees(2.5251), Length::Meters(1401)), + LLA(Angle::Degrees(-25.89), Angle::Degrees(27.71), Length::Meters(1562.66)), + LLA(Angle::Degrees(-46.53), Angle::Degrees(168.38), Length::Meters(13.65)), + LLA(Angle::Degrees(71.275), Angle::Degrees(-156.806), Length::Meters(24)), + LLA(Angle::Degrees(-52.9351), Angle::Degrees(-70.8713), Length::Meters(23)), + }; + + const ostk::mathematics::object::Interval azimuthInterval = + ostk::mathematics::object::Interval::Closed(0.0, 360.0); + const ostk::mathematics::object::Interval elevationInterval = + ostk::mathematics::object::Interval::Closed(0.0, 90.0); + const ostk::mathematics::object::Interval rangeInterval = + ostk::mathematics::object::Interval::Closed(0.0, 1.0e10); + + Array groundTargets = LLAs.map( + [&azimuthInterval, &elevationInterval, &rangeInterval](const LLA& lla) -> GroundTargetConfiguration + { + return GroundTargetConfiguration(azimuthInterval, elevationInterval, rangeInterval, lla); + } + ); + + std::cout << "Speed test: ComputeAccessesWithGroundTargets" << std::endl; + // start timer + std::chrono::steady_clock::time_point start = std::chrono::steady_clock::now(); + + const Generator generator = + Generator::AerRanges(azimuthInterval, elevationInterval, rangeInterval, environment); + + const Array> accessesPerTarget = + generator.computeAccessesWithGroundTargets(interval, groundTargets, tabulatedOrbit); + + // end timer + std::chrono::steady_clock::time_point end = std::chrono::steady_clock::now(); + std::cout << "Time elapsed: " << std::chrono::duration_cast(end - start).count() + << " ms" << std::endl; + + ASSERT_EQ(accessesPerTarget.getSize(), groundTargets.getSize()); + } +} + TEST(OpenSpaceToolkit_Astrodynamics_Access_Generator, SetStep) { { @@ -835,7 +1064,8 @@ TEST(OpenSpaceToolkit_Astrodynamics_Access_Generator, AerRanges) << String::Format( "{} ~ {}", reference_acquisitionOfSignal.toString(), access.getAcquisitionOfSignal().toString() ); - // EXPECT_TRUE(access.getTimeOfClosestApproach().isNear(reference_timeOfClosestApproach, toleranceDuration)) + // EXPECT_TRUE(access.getTimeOfClosestApproach().isNear(reference_timeOfClosestApproach, + // toleranceDuration)) // << String::Format("{} ~ {}", reference_timeOfClosestApproach.toString(), // access.getTimeOfClosestApproach().toString()); EXPECT_TRUE(access.getLossOfSignal().isNear(reference_lossOfSignal, toleranceDuration)) @@ -940,7 +1170,8 @@ TEST(OpenSpaceToolkit_Astrodynamics_Access_Generator, AerMask) << String::Format( "{} ~ {}", reference_acquisitionOfSignal.toString(), access.getAcquisitionOfSignal().toString() ); - // EXPECT_TRUE(access.getTimeOfClosestApproach().isNear(reference_timeOfClosestApproach, toleranceDuration)) + // EXPECT_TRUE(access.getTimeOfClosestApproach().isNear(reference_timeOfClosestApproach, + // toleranceDuration)) // << String::Format("{} ~ {}", reference_timeOfClosestApproach.toString(), // access.getTimeOfClosestApproach().toString()); EXPECT_TRUE(access.getLossOfSignal().isNear(reference_lossOfSignal, toleranceDuration)) From 8a27daaa46a44063f8b59e331cf8ada2651c0e0e Mon Sep 17 00:00:00 2001 From: vishwa shah Date: Fri, 1 Nov 2024 05:47:20 +0000 Subject: [PATCH 02/22] feat: clean up --- .../Astrodynamics/Access/Generator.cpp | 41 +++++----- .../Astrodynamics/Access/Generator.test.cpp | 74 ------------------- 2 files changed, 19 insertions(+), 96 deletions(-) diff --git a/src/OpenSpaceToolkit/Astrodynamics/Access/Generator.cpp b/src/OpenSpaceToolkit/Astrodynamics/Access/Generator.cpp index 106472ff3..77437f859 100644 --- a/src/OpenSpaceToolkit/Astrodynamics/Access/Generator.cpp +++ b/src/OpenSpaceToolkit/Astrodynamics/Access/Generator.cpp @@ -275,21 +275,17 @@ Array> Generator::computeAccessesWithGroundTargets( MatrixXd aerLowerBounds = MatrixXd::Zero(targetCount, 3); MatrixXd aerUpperBounds = MatrixXd::Zero(targetCount, 3); + const Real degToRad = Real::Pi() / 180.0; + for (Index i = 0; i < targetCount; ++i) { - aerLowerBounds(i, 0) = - someGroundTargetConfigurations[i].getAzimuthInterval().accessLowerBound() * Real::Pi() / 180.0; - aerLowerBounds(i, 1) = - someGroundTargetConfigurations[i].getElevationInterval().accessLowerBound() * Real::Pi() / 180.0; - aerLowerBounds(i, 2) = - someGroundTargetConfigurations[i].getRangeInterval().accessLowerBound() * Real::Pi() / 180.0; - - aerUpperBounds(i, 0) = - someGroundTargetConfigurations[i].getAzimuthInterval().accessUpperBound() * Real::Pi() / 180.0; - aerUpperBounds(i, 1) = - someGroundTargetConfigurations[i].getElevationInterval().accessUpperBound() * Real::Pi() / 180.0; - aerUpperBounds(i, 2) = - someGroundTargetConfigurations[i].getRangeInterval().accessUpperBound() * Real::Pi() / 180.0; + aerLowerBounds(i, 0) = someGroundTargetConfigurations[i].getAzimuthInterval().accessLowerBound() * degToRad; + aerLowerBounds(i, 1) = someGroundTargetConfigurations[i].getElevationInterval().accessLowerBound() * degToRad; + aerLowerBounds(i, 2) = someGroundTargetConfigurations[i].getRangeInterval().accessLowerBound() * degToRad; + + aerUpperBounds(i, 0) = someGroundTargetConfigurations[i].getAzimuthInterval().accessUpperBound() * degToRad; + aerUpperBounds(i, 1) = someGroundTargetConfigurations[i].getElevationInterval().accessUpperBound() * degToRad; + aerUpperBounds(i, 2) = someGroundTargetConfigurations[i].getRangeInterval().accessUpperBound() * degToRad; } // initialize solver and condition @@ -300,9 +296,9 @@ Array> Generator::computeAccessesWithGroundTargets( const Index& targetIdx ) { - const Vector3d fromPositionCoordinate_ITRF = fromPositionCoordinates_ITRF.col(targetIdx); + const Vector3d& fromPositionCoordinate_ITRF = fromPositionCoordinates_ITRF.col(targetIdx); - const Matrix3d SEZRotation = SEZRotations.block(0, 3 * targetIdx, 3, 3); + const Matrix3d& SEZRotation = SEZRotations.block(0, 3 * targetIdx, 3, 3); const double azimuthLowerBound = aerLowerBounds(targetIdx, 0); const double azimuthUpperBound = aerUpperBounds(targetIdx, 0); @@ -324,9 +320,9 @@ Array> Generator::computeAccessesWithGroundTargets( const auto toPositionCoordinates_ITRF = aToTrajectory.getStateAt(instant).inFrame(Frame::ITRF()).getPosition().getCoordinates(); - const VectorXd dx = toPositionCoordinates_ITRF - fromPositionCoordinate_ITRF; + const Vector3d dx = toPositionCoordinates_ITRF - fromPositionCoordinate_ITRF; - const MatrixXd dx_SEZ = SEZRotation * dx; + const Vector3d dx_SEZ = SEZRotation * dx; const double range = dx_SEZ.norm(); const double elevation_rad = std::asin(dx_SEZ(2) / range); @@ -404,9 +400,12 @@ Array> Generator::computeAccessesWithGroundTargets( for (Index i = 0; i < (Index)inAccess.size(); ++i) { previousAccessStates[i] = inAccess[i]; + + const physics::time::Interval defaultInterval = physics::time::Interval::Closed(instant, instant); + if (inAccess[i]) { - accessIntervals[i].add(physics::time::Interval::Closed(instant, instant)); + accessIntervals[i].add(defaultInterval); } } continue; @@ -418,12 +417,12 @@ Array> Generator::computeAccessesWithGroundTargets( for (Index i = 0; i < (Index)inAccess.size(); ++i) { - const bool currentAccess = inAccess[i]; + const bool& currentAccess = inAccess[i]; if (currentAccess != previousAccessStates[i]) { // Find exact crossing time - const auto condition = conditionArray[i]; + const auto& condition = conditionArray[i]; const auto result = rootSolver.solve( [&previousInstant, &condition](double aDurationInSeconds) -> double @@ -463,8 +462,6 @@ Array> Generator::computeAccessesWithGroundTargets( } } - const Shared earthSPtr = this->environment_.accessCelestialObjectWithName("Earth"); - Array> accesses = Array>(targetCount, Array::Empty()); for (Index i = 0; i < accessIntervals.getSize(); ++i) { diff --git a/test/OpenSpaceToolkit/Astrodynamics/Access/Generator.test.cpp b/test/OpenSpaceToolkit/Astrodynamics/Access/Generator.test.cpp index 06d8e4807..c359b2d04 100644 --- a/test/OpenSpaceToolkit/Astrodynamics/Access/Generator.test.cpp +++ b/test/OpenSpaceToolkit/Astrodynamics/Access/Generator.test.cpp @@ -801,80 +801,6 @@ TEST(OpenSpaceToolkit_Astrodynamics_Access_Generator, ComputeAccessesWithGroundT } } -TEST(OpenSpaceToolkit_Astrodynamics_Access_Generator, SpeedTest) -{ - { - using ostk::astrodynamics::trajectory::orbit::model::Tabulated; - using ostk::mathematics::curvefitting::Interpolator; - const Environment environment = Environment::Default(); - - const TLE tle = { - "1 60504U 24149AN 24293.10070306 .00000000 00000-0 58313-3 0 08", - "2 60504 97.4383 7.6998 0003154 274.9510 182.9597 15.19652001 9607", - }; - const SGP4 sgp4 = SGP4(tle); - const Orbit aToTrajectory = Orbit(sgp4, environment.accessCelestialObjectWithName("Earth")); - - const Instant startInstant = Instant::Parse("2024-10-19 02:25:00.744.384", Scale::UTC); - const Instant endInstant = startInstant + Duration::Days(14.0); - const Interval interval = Interval::Closed(startInstant, endInstant); - - const Array instants = interval.generateGrid(Duration::Seconds(20.0)); - const Array states = aToTrajectory.getStatesAt(instants); - const Orbit tabulatedOrbit = Orbit( - Tabulated(states, 1, Interpolator::Type::CubicSpline), environment.accessCelestialObjectWithName("Earth") - ); - - const Array LLAs = { - LLA(Angle::Degrees(53.406), Angle::Degrees(-6.225), Length::Meters(50.5)), - LLA(Angle::Degrees(13.51), Angle::Degrees(144.82), Length::Meters(46)), - LLA(Angle::Degrees(42.77), Angle::Degrees(141.62), Length::Meters(100)), - LLA(Angle::Degrees(47.2393), Angle::Degrees(-119.88515), Length::Meters(392.5)), - LLA(Angle::Degrees(78.22702), Angle::Degrees(15.38624), Length::Meters(493)), - LLA(Angle::Degrees(60.674), Angle::Degrees(17.142), Length::Meters(100)), - LLA(Angle::Degrees(37.7716), Angle::Degrees(-122.4135), Length::Meters(100)), - LLA(Angle::Degrees(69.09), Angle::Degrees(17.6986), Length::Meters(12)), - LLA(Angle::Degrees(78.23164), Angle::Degrees(15.37725), Length::Meters(483)), - LLA(Angle::Degrees(-72.0021), Angle::Degrees(2.5251), Length::Meters(1401)), - LLA(Angle::Degrees(-25.89), Angle::Degrees(27.71), Length::Meters(1562.66)), - LLA(Angle::Degrees(-46.53), Angle::Degrees(168.38), Length::Meters(13.65)), - LLA(Angle::Degrees(71.275), Angle::Degrees(-156.806), Length::Meters(24)), - LLA(Angle::Degrees(-52.9351), Angle::Degrees(-70.8713), Length::Meters(23)), - }; - - const ostk::mathematics::object::Interval azimuthInterval = - ostk::mathematics::object::Interval::Closed(0.0, 360.0); - const ostk::mathematics::object::Interval elevationInterval = - ostk::mathematics::object::Interval::Closed(0.0, 90.0); - const ostk::mathematics::object::Interval rangeInterval = - ostk::mathematics::object::Interval::Closed(0.0, 1.0e10); - - Array groundTargets = LLAs.map( - [&azimuthInterval, &elevationInterval, &rangeInterval](const LLA& lla) -> GroundTargetConfiguration - { - return GroundTargetConfiguration(azimuthInterval, elevationInterval, rangeInterval, lla); - } - ); - - std::cout << "Speed test: ComputeAccessesWithGroundTargets" << std::endl; - // start timer - std::chrono::steady_clock::time_point start = std::chrono::steady_clock::now(); - - const Generator generator = - Generator::AerRanges(azimuthInterval, elevationInterval, rangeInterval, environment); - - const Array> accessesPerTarget = - generator.computeAccessesWithGroundTargets(interval, groundTargets, tabulatedOrbit); - - // end timer - std::chrono::steady_clock::time_point end = std::chrono::steady_clock::now(); - std::cout << "Time elapsed: " << std::chrono::duration_cast(end - start).count() - << " ms" << std::endl; - - ASSERT_EQ(accessesPerTarget.getSize(), groundTargets.getSize()); - } -} - TEST(OpenSpaceToolkit_Astrodynamics_Access_Generator, SetStep) { { From 390f0f61f15e8008bf261e551ca461f2b6a343aa Mon Sep 17 00:00:00 2001 From: vishwa shah Date: Sun, 3 Nov 2024 23:48:33 +0000 Subject: [PATCH 03/22] feat: refactor code, add more tests and clean up logic --- .../Astrodynamics/Access/Generator.hpp | 48 ++- .../Astrodynamics/Access/Generator.cpp | 346 +++++++++++------- .../Astrodynamics/Access/Generator.test.cpp | 87 ++++- 3 files changed, 338 insertions(+), 143 deletions(-) diff --git a/include/OpenSpaceToolkit/Astrodynamics/Access/Generator.hpp b/include/OpenSpaceToolkit/Astrodynamics/Access/Generator.hpp index a5e9685e9..8b05162e2 100644 --- a/include/OpenSpaceToolkit/Astrodynamics/Access/Generator.hpp +++ b/include/OpenSpaceToolkit/Astrodynamics/Access/Generator.hpp @@ -36,6 +36,8 @@ using ostk::core::type::Shared; using ostk::mathematics::object::Interval; using ostk::mathematics::object::Matrix3d; +using ostk::mathematics::object::Vector3d; +using ostk::mathematics::object::VectorXi; using ostk::physics::coordinate::Position; using ostk::physics::coordinate::spherical::AER; @@ -88,8 +90,8 @@ struct GroundTargetConfiguration /// @brief Get the trajectory /// /// @code{.cpp} - /// GroundStationConfiguration groundStationConfiguration = { ... } ; - /// Trajectory trajectory = groundStationConfiguration.getTrajectory(); + /// GroundTargetConfiguration groundTargetConfiguration = { ... } ; + /// Trajectory trajectory = groundTargetConfiguration.getTrajectory(); /// @endcode /// /// @return The trajectory @@ -98,8 +100,8 @@ struct GroundTargetConfiguration /// @brief Get the position /// /// @code{.cpp} - /// GroundStationConfiguration groundStationConfiguration = { ... } ; - /// Position position = groundStationConfiguration.getPosition(); + /// GroundTargetConfiguration groundTargetConfiguration = { ... } ; + /// Position position = groundTargetConfiguration.getPosition(); /// @endcode /// /// @return The position @@ -108,8 +110,8 @@ struct GroundTargetConfiguration /// @brief Get the latitude, longitude, and altitude (LLA) /// /// @code{.cpp} - /// GroundStationConfiguration groundStationConfiguration = { ... } ; - /// LLA lla = groundStationConfiguration.getLLA(); + /// GroundTargetConfiguration groundTargetConfiguration = { ... } ; + /// LLA lla = groundTargetConfiguration.getLLA(); /// @endcode /// /// @return The latitude, longitude, and altitude (LLA) @@ -118,8 +120,8 @@ struct GroundTargetConfiguration /// @brief Get the azimuth interval /// /// @code{.cpp} - /// GroundStationConfiguration groundStationConfiguration = { ... } ; - /// Interval groundStationConfiguration = generator.getAzimuthInterval(); + /// GroundTargetConfiguration groundTargetConfiguration = { ... } ; + /// Interval groundTargetConfiguration = generator.getAzimuthInterval(); /// @endcode /// /// @return The azimuth interval @@ -128,8 +130,8 @@ struct GroundTargetConfiguration /// @brief Get the elevation interval /// /// @code{.cpp} - /// GroundStationConfiguration groundStationConfiguration = { ... } ; - /// Interval groundStationConfiguration = generator.getElevationInterval(); + /// GroundTargetConfiguration groundTargetConfiguration = { ... } ; + /// Interval groundTargetConfiguration = generator.getElevationInterval(); /// @endcode /// /// @return The elevation interval @@ -138,8 +140,8 @@ struct GroundTargetConfiguration /// @brief Get the range interval /// /// @code{.cpp} - /// GroundStationConfiguration groundStationConfiguration = { ... } ; - /// Interval groundStationConfiguration = generator.getRangeInterval(); + /// GroundTargetConfiguration groundTargetConfiguration = { ... } ; + /// Interval groundTargetConfiguration = generator.getRangeInterval(); /// @endcode /// /// @return The range interval @@ -149,8 +151,8 @@ struct GroundTargetConfiguration /// frame /// /// @code{.cpp} - /// GroundStationConfiguration groundStationConfiguration = { ... } ; - /// Matrix3d sezRotation = groundStationConfiguration.getR_SEZ_ECEF(); + /// GroundTargetConfiguration groundTargetConfiguration = { ... } ; + /// Matrix3d sezRotation = groundTargetConfiguration.getR_SEZ_ECEF(); /// @endcode /// /// @return The SEZ rotation matrix @@ -163,6 +165,8 @@ struct GroundTargetConfiguration Position position_; LLA lla_; + + void validateIntervals_() const; }; class Generator @@ -206,7 +210,8 @@ class Generator Array> computeAccessesWithGroundTargets( const physics::time::Interval& anInterval, const Array& someGroundTargetConfigurations, - const Trajectory& aToTrajectory + const Trajectory& aToTrajectory, + const bool& coarse = false ) const; void setStep(const Duration& aStep); @@ -264,6 +269,19 @@ class Generator const Trajectory& aToTrajectory ) const; + Array computePreciseCrossings( + const Array& accessIntervals, + const physics::time::Interval& anAnalysisInterval, + const Vector3d& fromPositionCoordinate_ITRF, + const Trajectory& aToTrajectory, + const Matrix3d& SEZRotation, + const Interval& anAzimuthInterval, + const Interval& anElevationInterval, + const Interval& aRangeInterval + ) const; + + static Array ComputeIntervals(const VectorXi& inAccess, const Array& instants); + static Access GenerateAccess( const physics::time::Interval& anAccessInterval, const physics::time::Interval& aGlobalInterval, diff --git a/src/OpenSpaceToolkit/Astrodynamics/Access/Generator.cpp b/src/OpenSpaceToolkit/Astrodynamics/Access/Generator.cpp index 77437f859..7a233a806 100644 --- a/src/OpenSpaceToolkit/Astrodynamics/Access/Generator.cpp +++ b/src/OpenSpaceToolkit/Astrodynamics/Access/Generator.cpp @@ -16,8 +16,10 @@ using ostk::mathematics::geometry::d3::object::Point; using ostk::mathematics::geometry::d3::object::Segment; using ostk::mathematics::object::MatrixXd; +using ostk::mathematics::object::MatrixXi; using ostk::mathematics::object::Vector3d; using ostk::mathematics::object::VectorXd; +using ostk::mathematics::object::VectorXi; using ostk::physics::coordinate::Frame; using ostk::physics::coordinate::spherical::LLA; @@ -54,6 +56,8 @@ GroundTargetConfiguration::GroundTargetConfiguration( { throw ostk::core::error::RuntimeError("The position frame must be ITRF."); } + + validateIntervals_(); } GroundTargetConfiguration::GroundTargetConfiguration( @@ -73,6 +77,12 @@ GroundTargetConfiguration::GroundTargetConfiguration( )), lla_(aLLA) { + if (!aLLA.isDefined()) + { + throw ostk::core::error::runtime::Undefined("LLA"); + } + + validateIntervals_(); } Trajectory GroundTargetConfiguration::getTrajectory() const @@ -120,6 +130,41 @@ Matrix3d GroundTargetConfiguration::getR_SEZ_ECEF() const return SEZRotation; } +void GroundTargetConfiguration::validateIntervals_() const +{ + if (!azimuthInterval_.isDefined() || !elevationInterval_.isDefined() || !rangeInterval_.isDefined()) + { + throw ostk::core::error::runtime::Undefined("Interval"); + } + + if (azimuthInterval_.getLowerBound() < 0.0 || azimuthInterval_.getUpperBound() > 360.0) + { + throw ostk::core::error::RuntimeError( + "The azimuth interval [{}, {}] must be in the range [0, 360] deg", + azimuthInterval_.accessLowerBound(), + azimuthInterval_.accessUpperBound() + ); + } + + if (elevationInterval_.getLowerBound() < -90.0 || elevationInterval_.getUpperBound() > 90.0) + { + throw ostk::core::error::RuntimeError( + "The elevation interval [{}, {}] must be in the range [-90, 90] deg.", + elevationInterval_.accessLowerBound(), + elevationInterval_.accessUpperBound() + ); + } + + if (rangeInterval_.getLowerBound() < 0.0) + { + throw ostk::core::error::RuntimeError( + "The range interval [{}, {}] must be positive.", + rangeInterval_.accessLowerBound(), + rangeInterval_.accessUpperBound() + ); + } +} + Generator::Generator(const Environment& anEnvironment, const Duration& aStep, const Duration& aTolerance) : environment_(anEnvironment), step_(aStep), @@ -251,7 +296,8 @@ Array Generator::computeAccesses( Array> Generator::computeAccessesWithGroundTargets( const physics::time::Interval& anInterval, const Array& someGroundTargetConfigurations, - const Trajectory& aToTrajectory + const Trajectory& aToTrajectory, + const bool& coarse ) const { // create a stacked matrix of SEZ rotations for all ground targets @@ -260,7 +306,7 @@ Array> Generator::computeAccessesWithGroundTargets( for (Index i = 0; i < targetCount; ++i) { - SEZRotations.block(0, 3 * i, 3, 3) = someGroundTargetConfigurations[i].getR_SEZ_ECEF(); + SEZRotations.block<3, 3>(0, 3 * i) = someGroundTargetConfigurations[i].getR_SEZ_ECEF(); } // create a stacked matrix of ITRF positions for all ground targets @@ -281,73 +327,16 @@ Array> Generator::computeAccessesWithGroundTargets( { aerLowerBounds(i, 0) = someGroundTargetConfigurations[i].getAzimuthInterval().accessLowerBound() * degToRad; aerLowerBounds(i, 1) = someGroundTargetConfigurations[i].getElevationInterval().accessLowerBound() * degToRad; - aerLowerBounds(i, 2) = someGroundTargetConfigurations[i].getRangeInterval().accessLowerBound() * degToRad; + aerLowerBounds(i, 2) = someGroundTargetConfigurations[i].getRangeInterval().accessLowerBound(); aerUpperBounds(i, 0) = someGroundTargetConfigurations[i].getAzimuthInterval().accessUpperBound() * degToRad; aerUpperBounds(i, 1) = someGroundTargetConfigurations[i].getElevationInterval().accessUpperBound() * degToRad; - aerUpperBounds(i, 2) = someGroundTargetConfigurations[i].getRangeInterval().accessUpperBound() * degToRad; + aerUpperBounds(i, 2) = someGroundTargetConfigurations[i].getRangeInterval().accessUpperBound(); } - // initialize solver and condition - const RootSolver rootSolver = RootSolver(100, this->tolerance_.inSeconds()); - - const auto createAccessCondition = - [&fromPositionCoordinates_ITRF, &SEZRotations, &aToTrajectory, &aerLowerBounds, &aerUpperBounds]( - const Index& targetIdx - ) - { - const Vector3d& fromPositionCoordinate_ITRF = fromPositionCoordinates_ITRF.col(targetIdx); - - const Matrix3d& SEZRotation = SEZRotations.block(0, 3 * targetIdx, 3, 3); - - const double azimuthLowerBound = aerLowerBounds(targetIdx, 0); - const double azimuthUpperBound = aerUpperBounds(targetIdx, 0); - const double elevationLowerBound = aerLowerBounds(targetIdx, 1); - const double elevationUpperBound = aerUpperBounds(targetIdx, 1); - const double rangeLowerBound = aerLowerBounds(targetIdx, 2); - const double rangeUpperBound = aerUpperBounds(targetIdx, 2); - - return [fromPositionCoordinate_ITRF, - SEZRotation, - azimuthLowerBound, - azimuthUpperBound, - elevationLowerBound, - elevationUpperBound, - rangeLowerBound, - rangeUpperBound, - &aToTrajectory](const Instant& instant) -> bool - { - const auto toPositionCoordinates_ITRF = - aToTrajectory.getStateAt(instant).inFrame(Frame::ITRF()).getPosition().getCoordinates(); - - const Vector3d dx = toPositionCoordinates_ITRF - fromPositionCoordinate_ITRF; - - const Vector3d dx_SEZ = SEZRotation * dx; - - const double range = dx_SEZ.norm(); - const double elevation_rad = std::asin(dx_SEZ(2) / range); - double azimuth_rad = std::atan2(dx_SEZ(1), dx_SEZ(0)); - - azimuth_rad = azimuth_rad < 0.0 ? azimuth_rad + 2.0 * M_PI : azimuth_rad; - - return azimuth_rad > azimuthLowerBound && azimuth_rad < azimuthUpperBound && - elevation_rad > elevationLowerBound && elevation_rad < elevationUpperBound && - range > rangeLowerBound && range < rangeUpperBound; - }; - }; - - // initialize containers const Array instants = anInterval.generateGrid(this->step_); - Array> conditionArray(targetCount, nullptr); - for (Index i = 0; i < targetCount; ++i) - { - conditionArray[i] = createAccessCondition(i); - } - - Array> accessIntervals = - Array>(targetCount, Array::Empty()); - Array previousAccessStates = Array(targetCount, false); + MatrixXi inAccessPerTarget = MatrixXi::Zero(instants.getSize(), targetCount); for (Index index = 0; index < instants.getSize(); ++index) { @@ -357,8 +346,7 @@ Array> Generator::computeAccessesWithGroundTargets( const Vector3d toPositionCoordinates_ITRF = aToTrajectory.getStateAt(instant).inFrame(Frame::ITRF()).getPosition().getCoordinates(); - const MatrixXd dx = - toPositionCoordinates_ITRF.replicate(1, fromPositionCoordinates_ITRF.cols()) - fromPositionCoordinates_ITRF; + const MatrixXd dx = (-fromPositionCoordinates_ITRF).colwise() + toPositionCoordinates_ITRF; // calculate target to satellite vector in SEZ MatrixXd dx_SEZ = MatrixXd::Zero(3, dx.cols()); @@ -394,71 +382,31 @@ Array> Generator::computeAccessesWithGroundTargets( range.array() < aerUpperBounds.col(2).array()) .eval(); - // First instant handling - if (index == 0) - { - for (Index i = 0; i < (Index)inAccess.size(); ++i) - { - previousAccessStates[i] = inAccess[i]; - - const physics::time::Interval defaultInterval = physics::time::Interval::Closed(instant, instant); - - if (inAccess[i]) - { - accessIntervals[i].add(defaultInterval); - } - } - continue; - } + inAccessPerTarget.row(index) = inAccess.cast().transpose(); + } - const Instant& previousInstant = instants[index - 1]; + Array> accessIntervals = + Array>(targetCount, Array::Empty()); - // Check for state changes and find exact crossings + for (Index i = 0; i < targetCount; ++i) + { + accessIntervals[i] = ComputeIntervals(inAccessPerTarget.col(i), instants); + } - for (Index i = 0; i < (Index)inAccess.size(); ++i) + if (!coarse) + { + for (Index i = 0; i < accessIntervals.getSize(); ++i) { - const bool& currentAccess = inAccess[i]; - - if (currentAccess != previousAccessStates[i]) - { - // Find exact crossing time - const auto& condition = conditionArray[i]; - - const auto result = rootSolver.solve( - [&previousInstant, &condition](double aDurationInSeconds) -> double - { - return condition(previousInstant + Duration::Seconds(aDurationInSeconds)) ? +1.0 : -1.0; - }, - 0.0, - Duration::Between(previousInstant, instant).inSeconds() - ); - - const Instant crossingTime = previousInstant + Duration::Seconds(result.root); - - if (currentAccess) // Rising edge - { - accessIntervals[i].add(physics::time::Interval::Closed(crossingTime, instant)); - } - else // Falling edge - { - if (!accessIntervals[i].isEmpty()) - { - const physics::time::Interval& lastInterval = accessIntervals[i].accessLast(); - accessIntervals[i].accessLast() = - physics::time::Interval::Closed(lastInterval.getStart(), crossingTime); - } - } - } - else if (currentAccess) // Continuing access - { - if (!accessIntervals[i].isEmpty()) - { - const physics::time::Interval& lastInterval = accessIntervals[i].accessLast(); - accessIntervals[i].accessLast() = physics::time::Interval::Closed(lastInterval.getStart(), instant); - } - } - - previousAccessStates[i] = currentAccess; + accessIntervals[i] = this->computePreciseCrossings( + accessIntervals[i], + anInterval, + fromPositionCoordinates_ITRF.col(i), + aToTrajectory, + someGroundTargetConfigurations[i].getR_SEZ_ECEF(), + someGroundTargetConfigurations[i].getAzimuthInterval(), + someGroundTargetConfigurations[i].getElevationInterval(), + someGroundTargetConfigurations[i].getRangeInterval() + ); } } @@ -631,6 +579,143 @@ Array Generator::generateAccessesFromIntervals( ); } +Array Generator::computePreciseCrossings( + const Array& accessIntervals, + const physics::time::Interval& anAnalysisInterval, + const Vector3d& fromPositionCoordinate_ITRF, + const Trajectory& aToTrajectory, + const Matrix3d& SEZRotation, + const Interval& anAzimuthInterval, + const Interval& anElevationInterval, + const Interval& aRangeInterval +) const +{ + const RootSolver rootSolver = RootSolver(100, this->tolerance_.inSeconds()); + + const auto condition = [&fromPositionCoordinate_ITRF, + &SEZRotation, + &aToTrajectory, + &anAzimuthInterval, + &anElevationInterval, + &aRangeInterval](const Instant& instant) -> bool + { + const double& azimuthLowerBound = anAzimuthInterval.accessLowerBound(); + const double& azimuthUpperBound = anAzimuthInterval.accessUpperBound(); + const double& elevationLowerBound = anElevationInterval.accessLowerBound(); + const double& elevationUpperBound = anElevationInterval.accessUpperBound(); + const double& rangeLowerBound = aRangeInterval.accessLowerBound(); + const double& rangeUpperBound = aRangeInterval.accessUpperBound(); + + const auto toPositionCoordinates_ITRF = + aToTrajectory.getStateAt(instant).inFrame(Frame::ITRF()).getPosition().getCoordinates(); + + const Vector3d dx = toPositionCoordinates_ITRF - fromPositionCoordinate_ITRF; + + const Vector3d dx_SEZ = SEZRotation * dx; + + const double range = dx_SEZ.norm(); + const double elevation_rad = std::asin(dx_SEZ(2) / range); + double azimuth_rad = std::atan2(dx_SEZ(1), dx_SEZ(0)); + + azimuth_rad = azimuth_rad < 0.0 ? azimuth_rad + 2.0 * M_PI : azimuth_rad; + + return azimuth_rad > azimuthLowerBound && azimuth_rad < azimuthUpperBound && + elevation_rad > elevationLowerBound && elevation_rad < elevationUpperBound && range > rangeLowerBound && + range < rangeUpperBound; + }; + + Array preciseAccessIntervals = accessIntervals; + + for (Index i = 0; i < preciseAccessIntervals.getSize(); ++i) + { + const auto& interval = accessIntervals[i]; + + const Instant lowerBoundPreviousInstant = interval.getStart() - this->step_; + const Instant lowerBoundInstant = interval.getStart(); + + Instant intervalStart = anAnalysisInterval.getStart(); + + // compute start crossing if it is not the start of the requested analysis interval + if (lowerBoundInstant != anAnalysisInterval.getStart()) + { + const auto startCrossingDurationSeconds = rootSolver.solve( + [&lowerBoundPreviousInstant, &condition](double aDurationInSeconds) -> double + { + return condition(lowerBoundPreviousInstant + Duration::Seconds(aDurationInSeconds)) ? +1.0 : -1.0; + }, + 0.0, + Duration::Between(lowerBoundPreviousInstant, lowerBoundInstant).inSeconds() + ); + intervalStart = lowerBoundPreviousInstant + Duration::Seconds(startCrossingDurationSeconds.root); + } + + const Instant upperBoundInstant = interval.getEnd(); + const Instant upperBoundNextInstant = interval.getEnd() + this->step_; + + Instant intervalEnd = anAnalysisInterval.getEnd(); + + // compute end crossing if it is not the end of the requested analysis interval + if (upperBoundInstant != anAnalysisInterval.getEnd()) + { + const auto endCrossingDurationSeconds = rootSolver.solve( + [&upperBoundInstant, &condition](double aDurationInSeconds) -> double + { + return condition(upperBoundInstant + Duration::Seconds(aDurationInSeconds)) ? +1.0 : -1.0; + }, + 0.0, + Duration::Between(upperBoundInstant, upperBoundNextInstant).inSeconds() + ); + intervalEnd = upperBoundInstant + Duration::Seconds(endCrossingDurationSeconds.root); + } + + preciseAccessIntervals[i] = physics::time::Interval::Closed(intervalStart, intervalEnd); + } + + return preciseAccessIntervals; +} + +Array Generator::ComputeIntervals(const VectorXi& inAccess, const Array& instants) +{ + Array accessIntervals = Array::Empty(); + + VectorXi padded = VectorXi::Zero(inAccess.size() + 2); // adding zeros to start and end + padded.segment(1, inAccess.size()) = inAccess; + + const Index paddedSize = padded.size() - 1; + const VectorXi diff = padded.tail(paddedSize) - padded.head(paddedSize); + + Instant startInstant = Instant::Undefined(); + Instant endInstant = Instant::Undefined(); + + for (Index j = 0; j < (Index)diff.size() - 1; ++j) + { + if (!diff[j]) + { + continue; + } + + if (diff[j] == 1) + { + startInstant = instants[j]; + } + + if (diff[j] == -1) + { + endInstant = instants[j - 1]; + accessIntervals.add(physics::time::Interval::Closed(startInstant, endInstant)); + } + } + + // account for last partial interval + + if (diff.tail<1>().value() == -1) + { + accessIntervals.add(physics::time::Interval::Closed(startInstant, instants.accessLast())); + } + + return accessIntervals; +} + Access Generator::GenerateAccess( const physics::time::Interval& anAccessInterval, const physics::time::Interval& aGlobalInterval, @@ -650,6 +735,15 @@ Access Generator::GenerateAccess( Generator::FindTimeOfClosestApproach(anAccessInterval, aFromTrajectory, aToTrajectory, aTolerance); const Instant lossOfSignal = anAccessInterval.getEnd(); + if (!timeOfClosestApproach.isDefined()) + { + throw ostk::core::error::RuntimeError( + "Cannot find TCA (solution did not converge): {} - {}.", + acquisitionOfSignal.toString(), + lossOfSignal.toString() + ); + } + const Angle maxElevation = timeOfClosestApproach.isDefined() ? Generator::CalculateElevationAt(timeOfClosestApproach, aFromTrajectory, aToTrajectory, anEarthSPtr) @@ -741,7 +835,7 @@ Instant Generator::FindTimeOfClosestApproach( case nlopt::ROUNDOFF_LIMITED: case nlopt::FORCED_STOP: default: - throw ostk::core::error::RuntimeError("Cannot find TCA (solution did not converge)."); + return Instant::Undefined(); } } catch (const std::exception& anException) diff --git a/test/OpenSpaceToolkit/Astrodynamics/Access/Generator.test.cpp b/test/OpenSpaceToolkit/Astrodynamics/Access/Generator.test.cpp index c359b2d04..1a4ff5eea 100644 --- a/test/OpenSpaceToolkit/Astrodynamics/Access/Generator.test.cpp +++ b/test/OpenSpaceToolkit/Astrodynamics/Access/Generator.test.cpp @@ -685,6 +685,89 @@ TEST(OpenSpaceToolkit_Astrodynamics_Access_Generator, GroundTargetConfiguration) ); } } + + // undefined + { + const Position position = Position::Meters({0.0, 0.0, 0.0}, Frame::ITRF()); + + EXPECT_THROW( + GroundTargetConfiguration( + ostk::mathematics::object::Interval::Undefined(), elevationInterval, rangeInterval, position + ), + ostk::core::error::runtime::Undefined + ); + EXPECT_THROW( + GroundTargetConfiguration( + azimuthInterval, ostk::mathematics::object::Interval::Undefined(), rangeInterval, position + ), + ostk::core::error::runtime::Undefined + ); + EXPECT_THROW( + GroundTargetConfiguration( + azimuthInterval, elevationInterval, ostk::mathematics::object::Interval::Undefined(), position + ), + ostk::core::error::runtime::Undefined + ); + EXPECT_THROW( + GroundTargetConfiguration(azimuthInterval, elevationInterval, rangeInterval, LLA::Undefined()), + ostk::core::error::runtime::Undefined + ); + } + + // incorrect bounds + { + { + EXPECT_THROW( + GroundTargetConfiguration( + ostk::mathematics::object::Interval::Closed(-1.0, 350.0), + elevationInterval, + rangeInterval, + lla + ), + ostk::core::error::RuntimeError + ); + EXPECT_THROW( + GroundTargetConfiguration( + ostk::mathematics::object::Interval::Closed(0.0, 360.1), + elevationInterval, + rangeInterval, + lla + ), + ostk::core::error::RuntimeError + ); + } + { + EXPECT_THROW( + GroundTargetConfiguration( + azimuthInterval, + ostk::mathematics::object::Interval::Closed(-91.0, 0.0), + rangeInterval, + lla + ), + ostk::core::error::RuntimeError + ); + EXPECT_THROW( + GroundTargetConfiguration( + azimuthInterval, + ostk::mathematics::object::Interval::Closed(-45.0, 91.0), + rangeInterval, + lla + ), + ostk::core::error::RuntimeError + ); + } + { + EXPECT_THROW( + GroundTargetConfiguration( + azimuthInterval, + elevationInterval, + ostk::mathematics::object::Interval::Closed(-1.0, 5.0), + lla + ), + ostk::core::error::RuntimeError + ); + } + } } // Getters @@ -704,9 +787,9 @@ TEST(OpenSpaceToolkit_Astrodynamics_Access_Generator, GroundTargetConfiguration) EXPECT_NO_THROW(groundTargetConfiguration.getTrajectory()); Matrix3d r_SEZ_ECEF; - r_SEZ_ECEF.row(0) = Vector3d {0.0, 0.0, 1.0}; + r_SEZ_ECEF.row(0) = Vector3d {0.0, 0.0, -1.0}; r_SEZ_ECEF.row(1) = Vector3d {0.0, 1.0, 0.0}; - r_SEZ_ECEF.row(2) = Vector3d {-1.0, 0.0, 0.0}; + r_SEZ_ECEF.row(2) = Vector3d {1.0, 0.0, 0.0}; EXPECT_EQ(groundTargetConfiguration.getR_SEZ_ECEF(), r_SEZ_ECEF); } From 0c05f84dd5e61bb8b35d02c3bca29f4a2604dede Mon Sep 17 00:00:00 2001 From: vishwa shah Date: Sun, 10 Nov 2024 20:36:36 +0000 Subject: [PATCH 04/22] feat: add azimuth elevation mask option --- .../Astrodynamics/Access/Generator.hpp | 49 ++- .../Astrodynamics/Access/Generator.cpp | 301 ++++++++++++++---- .../Astrodynamics/Access/Generator.test.cpp | 3 +- 3 files changed, 286 insertions(+), 67 deletions(-) diff --git a/include/OpenSpaceToolkit/Astrodynamics/Access/Generator.hpp b/include/OpenSpaceToolkit/Astrodynamics/Access/Generator.hpp index 8b05162e2..e235e2ef7 100644 --- a/include/OpenSpaceToolkit/Astrodynamics/Access/Generator.hpp +++ b/include/OpenSpaceToolkit/Astrodynamics/Access/Generator.hpp @@ -87,6 +87,22 @@ struct GroundTargetConfiguration const LLA& aLLA ); + /// @brief Constructor + /// @param anAzimuthElevationMask An azimuth-elevation mask [deg] + /// @param aRangeInterval A range interval [m] + /// @param aLLA An LLA + GroundTargetConfiguration( + const Map& anAzimuthElevationMask, const Interval& aRangeInterval, const LLA& aLLA + ); + + /// @brief Constructor + /// @param anAzimuthElevationMask An azimuth-elevation mask [deg] + /// @param aRangeInterval A range interval [m] + /// @param aPosition A Position + GroundTargetConfiguration( + const Map& anAzimuthElevationMask, const Interval& aRangeInterval, const Position& aPosition + ); + /// @brief Get the trajectory /// /// @code{.cpp} @@ -147,26 +163,50 @@ struct GroundTargetConfiguration /// @return The range interval Interval getRangeInterval() const; + /// @brief Get the azimuth-elevation mask + /// + /// @code{.cpp} + /// GroundTargetConfiguration groundTargetConfiguration = { ... } ; + /// Map azimuthElevationMask = groundTargetConfiguration.getAzimuthElevationMask(); + /// @endcode + /// + /// @return The azimuth-elevation mask + Map getAzimuthElevationMask() const; + + /// @brief Check if the elevation-azimuth are within the mask + /// + /// @code{.cpp} + /// GroundTargetConfiguration groundTargetConfiguration = { ... } ; + /// bool isAboveMask = groundTargetConfiguration.isAboveMask(elevation, azimuth); + /// @endcode + /// + /// @param anAzimuth An azimuth [deg] + /// @param anElevation An elevation [deg] + /// @return True if the azimuth-elevation are within the mask, false otherwise + bool isAboveMask(const Real& anAzimuth, const Real& anElevation) const; + /// @brief Get the rotation matrix (Matrix3d) from ECEF (Earth-Centered-Earth-Fixed) to SEZ (South-East-Zenith) /// frame /// /// @code{.cpp} /// GroundTargetConfiguration groundTargetConfiguration = { ... } ; - /// Matrix3d sezRotation = groundTargetConfiguration.getR_SEZ_ECEF(); + /// Matrix3d sezRotation = groundTargetConfiguration.computeR_SEZ_ECEF(); /// @endcode /// /// @return The SEZ rotation matrix - Matrix3d getR_SEZ_ECEF() const; + Matrix3d computeR_SEZ_ECEF() const; private: Interval azimuthInterval_; Interval elevationInterval_; Interval rangeInterval_; + Map azimuthElevationMask_; Position position_; LLA lla_; void validateIntervals_() const; + void validateMask_(); }; class Generator @@ -274,10 +314,7 @@ class Generator const physics::time::Interval& anAnalysisInterval, const Vector3d& fromPositionCoordinate_ITRF, const Trajectory& aToTrajectory, - const Matrix3d& SEZRotation, - const Interval& anAzimuthInterval, - const Interval& anElevationInterval, - const Interval& aRangeInterval + const GroundTargetConfiguration& aGroundTargetConfiguration ) const; static Array ComputeIntervals(const VectorXi& inAccess, const Array& instants); diff --git a/src/OpenSpaceToolkit/Astrodynamics/Access/Generator.cpp b/src/OpenSpaceToolkit/Astrodynamics/Access/Generator.cpp index 7a233a806..678f70afb 100644 --- a/src/OpenSpaceToolkit/Astrodynamics/Access/Generator.cpp +++ b/src/OpenSpaceToolkit/Astrodynamics/Access/Generator.cpp @@ -3,6 +3,8 @@ #include #include +#include + #include #include @@ -13,13 +15,17 @@ #include #include +using ostk::core::container::Triple; + using ostk::mathematics::geometry::d3::object::Point; using ostk::mathematics::geometry::d3::object::Segment; using ostk::mathematics::object::MatrixXd; using ostk::mathematics::object::MatrixXi; +using ostk::mathematics::object::Vector2d; using ostk::mathematics::object::Vector3d; using ostk::mathematics::object::VectorXd; using ostk::mathematics::object::VectorXi; +using ArrayXb = Eigen::Array; using ostk::physics::coordinate::Frame; using ostk::physics::coordinate::spherical::LLA; @@ -45,6 +51,7 @@ GroundTargetConfiguration::GroundTargetConfiguration( : azimuthInterval_(anAzimuthInterval), elevationInterval_(anElevationInterval), rangeInterval_(aRangeInterval), + azimuthElevationMask_({}), position_(aPosition), lla_(LLA::Cartesian( aPosition.getCoordinates(), @@ -69,6 +76,7 @@ GroundTargetConfiguration::GroundTargetConfiguration( : azimuthInterval_(anAzimuthInterval), elevationInterval_(anElevationInterval), rangeInterval_(aRangeInterval), + azimuthElevationMask_({}), position_(Position::Meters( aLLA.toCartesian( EarthGravitationalModel::EGM2008.equatorialRadius_, EarthGravitationalModel::EGM2008.flattening_ @@ -85,6 +93,51 @@ GroundTargetConfiguration::GroundTargetConfiguration( validateIntervals_(); } +GroundTargetConfiguration::GroundTargetConfiguration( + const Map& anAzimuthElevationMask, const Interval& aRangeInterval, const Position& aPosition +) + : azimuthInterval_(Interval::Undefined()), + elevationInterval_(Interval::Undefined()), + rangeInterval_(aRangeInterval), + azimuthElevationMask_(anAzimuthElevationMask), + position_(aPosition), + lla_(LLA::Cartesian( + aPosition.getCoordinates(), + EarthGravitationalModel::EGM2008.equatorialRadius_, + EarthGravitationalModel::EGM2008.flattening_ + )) +{ + if (aPosition.isDefined() && aPosition.accessFrame() != Frame::ITRF()) + { + throw ostk::core::error::RuntimeError("The position frame must be ITRF."); + } + + validateMask_(); +} + +GroundTargetConfiguration::GroundTargetConfiguration( + const Map& anAzimuthElevationMask, const Interval& aRangeInterval, const LLA& aLLA +) + : azimuthInterval_(Interval::Undefined()), + elevationInterval_(Interval::Undefined()), + rangeInterval_(aRangeInterval), + azimuthElevationMask_(anAzimuthElevationMask), + position_(Position::Meters( + aLLA.toCartesian( + EarthGravitationalModel::EGM2008.equatorialRadius_, EarthGravitationalModel::EGM2008.flattening_ + ), + Frame::ITRF() + )), + lla_(aLLA) +{ + if (!aLLA.isDefined()) + { + throw ostk::core::error::runtime::Undefined("LLA"); + } + + validateMask_(); +} + Trajectory GroundTargetConfiguration::getTrajectory() const { return Trajectory::Position(position_); @@ -115,7 +168,32 @@ Interval GroundTargetConfiguration::getRangeInterval() const return rangeInterval_; } -Matrix3d GroundTargetConfiguration::getR_SEZ_ECEF() const +Map GroundTargetConfiguration::getAzimuthElevationMask() const +{ + return azimuthElevationMask_; +} + +bool GroundTargetConfiguration::isAboveMask(const Real& anAzimuth_rad, const Real& anElevation_rad) const +{ + auto itLow = azimuthElevationMask_.lower_bound(anAzimuth_rad); + itLow--; + auto itUp = azimuthElevationMask_.upper_bound(anAzimuth_rad); + + // Vector between the two successive mask data points with bounding azimuth values + + const Vector2d lowToUpVector = {itUp->first - itLow->first, itUp->second - itLow->second}; + + // Vector from data point with azimuth lower bound to tested point + + const Vector2d lowToPointVector = {anAzimuth_rad - itLow->first, anElevation_rad - itLow->second}; + + // If the determinant of these two vectors is positive, the tested point lies above the function defined by the + // mask + + return ((lowToUpVector[0] * lowToPointVector[1] - lowToUpVector[1] * lowToPointVector[0]) >= 0.0); +} + +Matrix3d GroundTargetConfiguration::computeR_SEZ_ECEF() const { // TBM: Move this to OSTk physics as a utility function const double sinLat = std::sin(lla_.getLatitude().inRadians()); @@ -165,6 +243,44 @@ void GroundTargetConfiguration::validateIntervals_() const } } +void GroundTargetConfiguration::validateMask_() +{ + if (azimuthElevationMask_.empty() || azimuthElevationMask_.begin()->first < 0.0 || + azimuthElevationMask_.rbegin()->first > 360.0) + { + throw ostk::core::error::runtime::Wrong("Azimuth-Elevation Mask"); + } + + for (const auto& azimuthElevationPair : azimuthElevationMask_) + { + if ((azimuthElevationPair.second).abs() > 90.0) + { + throw ostk::core::error::runtime::Wrong("Azimuth-Elevation Mask"); + } + } + + if (azimuthElevationMask_.begin()->first != 0.0) + { + azimuthElevationMask_.insert({0.0, azimuthElevationMask_.begin()->second}); + } + + if (azimuthElevationMask_.rbegin()->first != 360.0) + { + azimuthElevationMask_.insert({360.0, azimuthElevationMask_.begin()->second}); + } + + // convert to radians + + Map azimuthElevationMaskRad; + for (const auto& azimuthElevationPair : azimuthElevationMask_) + { + const Real azimuthRad = azimuthElevationPair.first * Real::Pi() / 180.0; + const Real elevationRad = azimuthElevationPair.second * Real::Pi() / 180.0; + azimuthElevationMaskRad.insert({azimuthRad, elevationRad}); + } + azimuthElevationMask_ = azimuthElevationMaskRad; +} + Generator::Generator(const Environment& anEnvironment, const Duration& aStep, const Duration& aTolerance) : environment_(anEnvironment), step_(aStep), @@ -306,7 +422,7 @@ Array> Generator::computeAccessesWithGroundTargets( for (Index i = 0; i < targetCount; ++i) { - SEZRotations.block<3, 3>(0, 3 * i) = someGroundTargetConfigurations[i].getR_SEZ_ECEF(); + SEZRotations.block<3, 3>(0, 3 * i) = someGroundTargetConfigurations[i].computeR_SEZ_ECEF(); } // create a stacked matrix of ITRF positions for all ground targets @@ -317,21 +433,78 @@ Array> Generator::computeAccessesWithGroundTargets( fromPositionCoordinates_ITRF.col(i) = someGroundTargetConfigurations[i].getPosition().getCoordinates(); } - // create a stacked matrix of azimuth, elevation, and range bounds for all ground targets - MatrixXd aerLowerBounds = MatrixXd::Zero(targetCount, 3); - MatrixXd aerUpperBounds = MatrixXd::Zero(targetCount, 3); + const bool allGroundTargetsHaveMasks = std::all_of( + someGroundTargetConfigurations.begin(), + someGroundTargetConfigurations.end(), + [](const auto& groundTargetConfiguration) + { + return !groundTargetConfiguration.getAzimuthElevationMask().empty(); + } + ); - const Real degToRad = Real::Pi() / 180.0; + std::function aerFilter; - for (Index i = 0; i < targetCount; ++i) + if (!allGroundTargetsHaveMasks) { - aerLowerBounds(i, 0) = someGroundTargetConfigurations[i].getAzimuthInterval().accessLowerBound() * degToRad; - aerLowerBounds(i, 1) = someGroundTargetConfigurations[i].getElevationInterval().accessLowerBound() * degToRad; - aerLowerBounds(i, 2) = someGroundTargetConfigurations[i].getRangeInterval().accessLowerBound(); + // create a stacked matrix of azimuth, elevation, and range bounds for all ground targets + MatrixXd aerLowerBounds = MatrixXd::Zero(targetCount, 3); + MatrixXd aerUpperBounds = MatrixXd::Zero(targetCount, 3); + + const Real degToRad = Real::Pi() / 180.0; + + for (Index i = 0; i < targetCount; ++i) + { + aerLowerBounds(i, 0) = someGroundTargetConfigurations[i].getAzimuthInterval().accessLowerBound() * degToRad; + aerLowerBounds(i, 1) = + someGroundTargetConfigurations[i].getElevationInterval().accessLowerBound() * degToRad; + aerLowerBounds(i, 2) = someGroundTargetConfigurations[i].getRangeInterval().accessLowerBound(); + + aerUpperBounds(i, 0) = someGroundTargetConfigurations[i].getAzimuthInterval().accessUpperBound() * degToRad; + aerUpperBounds(i, 1) = + someGroundTargetConfigurations[i].getElevationInterval().accessUpperBound() * degToRad; + aerUpperBounds(i, 2) = someGroundTargetConfigurations[i].getRangeInterval().accessUpperBound(); + } - aerUpperBounds(i, 0) = someGroundTargetConfigurations[i].getAzimuthInterval().accessUpperBound() * degToRad; - aerUpperBounds(i, 1) = someGroundTargetConfigurations[i].getElevationInterval().accessUpperBound() * degToRad; - aerUpperBounds(i, 2) = someGroundTargetConfigurations[i].getRangeInterval().accessUpperBound(); + aerFilter = [aerLowerBounds, + aerUpperBounds](const VectorXd& azimuths, const VectorXd& elevations, const VectorXd& ranges) + { + return (azimuths.array() > aerLowerBounds.col(0).array() && + azimuths.array() < aerUpperBounds.col(0).array() && + elevations.array() > aerLowerBounds.col(1).array() && + elevations.array() < aerUpperBounds.col(1).array() && + ranges.array() > aerLowerBounds.col(2).array() && ranges.array() < aerUpperBounds.col(2).array()) + .eval(); + }; + } + else + { + VectorXd rangeLowerBounds = VectorXd::Zero(targetCount, 1); + VectorXd rangeUpperBounds = VectorXd::Zero(targetCount, 1); + + for (Index i = 0; i < targetCount; ++i) + { + rangeLowerBounds(i) = someGroundTargetConfigurations[i].getRangeInterval().accessLowerBound(); + rangeUpperBounds(i) = someGroundTargetConfigurations[i].getRangeInterval().accessUpperBound(); + } + + aerFilter = [&someGroundTargetConfigurations, rangeLowerBounds, rangeUpperBounds]( + const VectorXd& azimuths_rad, const VectorXd& elevations_rad, const VectorXd& ranges_m + ) + { + ArrayXb mask(azimuths_rad.rows()); + mask = ranges_m.array() > rangeLowerBounds.array() && ranges_m.array() < rangeUpperBounds.array(); + + for (Index i = 0; i < (Index)mask.rows(); ++i) + { + const auto& groundTargetConfiguration = someGroundTargetConfigurations[i]; + const auto& azimuth_rad = azimuths_rad(i); + const auto& elevation_rad = elevations_rad(i); + + mask(i) = mask(i) && groundTargetConfiguration.isAboveMask(azimuth_rad, elevation_rad); + } + + return mask; + }; } const Array instants = anInterval.generateGrid(this->step_); @@ -356,13 +529,13 @@ Array> Generator::computeAccessesWithGroundTargets( } // calculate azimuth, elevation, and range - const VectorXd range = dx_SEZ.colwise().norm(); - const VectorXd elevation_rad = (dx_SEZ.row(2).transpose().array() / range.array()).asin(); + const VectorXd range_m = dx_SEZ.colwise().norm(); + const VectorXd elevation_rad = (dx_SEZ.row(2).transpose().array() / range_m.array()).asin(); VectorXd azimuth_rad = dx_SEZ.row(0).array().binaryExpr( dx_SEZ.row(1).array(), [](double x, double y) { - return std::atan2(y, x); + return std::atan2(y, -x); } ); @@ -374,48 +547,38 @@ Array> Generator::computeAccessesWithGroundTargets( ); // check if satellite is in access - const auto inAccess = - (azimuth_rad.array() > aerLowerBounds.col(0).array() && - azimuth_rad.array() < aerUpperBounds.col(0).array() && - elevation_rad.array() > aerLowerBounds.col(1).array() && - elevation_rad.array() < aerUpperBounds.col(1).array() && range.array() > aerLowerBounds.col(2).array() && - range.array() < aerUpperBounds.col(2).array()) - .eval(); - + const auto inAccess = aerFilter(azimuth_rad, elevation_rad, range_m); inAccessPerTarget.row(index) = inAccess.cast().transpose(); } - Array> accessIntervals = + Array> accessIntervalsPerTarget = Array>(targetCount, Array::Empty()); for (Index i = 0; i < targetCount; ++i) { - accessIntervals[i] = ComputeIntervals(inAccessPerTarget.col(i), instants); + accessIntervalsPerTarget[i] = ComputeIntervals(inAccessPerTarget.col(i), instants); } if (!coarse) { - for (Index i = 0; i < accessIntervals.getSize(); ++i) + for (Index i = 0; i < accessIntervalsPerTarget.getSize(); ++i) { - accessIntervals[i] = this->computePreciseCrossings( - accessIntervals[i], + accessIntervalsPerTarget[i] = this->computePreciseCrossings( + accessIntervalsPerTarget[i], anInterval, fromPositionCoordinates_ITRF.col(i), aToTrajectory, - someGroundTargetConfigurations[i].getR_SEZ_ECEF(), - someGroundTargetConfigurations[i].getAzimuthInterval(), - someGroundTargetConfigurations[i].getElevationInterval(), - someGroundTargetConfigurations[i].getRangeInterval() + someGroundTargetConfigurations[i] ); } } Array> accesses = Array>(targetCount, Array::Empty()); - for (Index i = 0; i < accessIntervals.getSize(); ++i) + for (Index i = 0; i < accessIntervalsPerTarget.getSize(); ++i) { const Trajectory fromTrajectory = someGroundTargetConfigurations[i].getTrajectory(); accesses[i] = - this->generateAccessesFromIntervals(accessIntervals[i], anInterval, fromTrajectory, aToTrajectory); + this->generateAccessesFromIntervals(accessIntervalsPerTarget[i], anInterval, fromTrajectory, aToTrajectory); } return accesses; @@ -584,28 +747,18 @@ Array Generator::computePreciseCrossings( const physics::time::Interval& anAnalysisInterval, const Vector3d& fromPositionCoordinate_ITRF, const Trajectory& aToTrajectory, - const Matrix3d& SEZRotation, - const Interval& anAzimuthInterval, - const Interval& anElevationInterval, - const Interval& aRangeInterval + const GroundTargetConfiguration& aGroundTargetConfiguration ) const { const RootSolver rootSolver = RootSolver(100, this->tolerance_.inSeconds()); - const auto condition = [&fromPositionCoordinate_ITRF, - &SEZRotation, - &aToTrajectory, - &anAzimuthInterval, - &anElevationInterval, - &aRangeInterval](const Instant& instant) -> bool - { - const double& azimuthLowerBound = anAzimuthInterval.accessLowerBound(); - const double& azimuthUpperBound = anAzimuthInterval.accessUpperBound(); - const double& elevationLowerBound = anElevationInterval.accessLowerBound(); - const double& elevationUpperBound = anElevationInterval.accessUpperBound(); - const double& rangeLowerBound = aRangeInterval.accessLowerBound(); - const double& rangeUpperBound = aRangeInterval.accessUpperBound(); + const Matrix3d SEZRotation = aGroundTargetConfiguration.computeR_SEZ_ECEF(); + std::function condition; + + const auto computeAER = [&fromPositionCoordinate_ITRF, &SEZRotation, &aToTrajectory](const Instant& instant + ) -> Triple + { const auto toPositionCoordinates_ITRF = aToTrajectory.getStateAt(instant).inFrame(Frame::ITRF()).getPosition().getCoordinates(); @@ -613,17 +766,47 @@ Array Generator::computePreciseCrossings( const Vector3d dx_SEZ = SEZRotation * dx; - const double range = dx_SEZ.norm(); - const double elevation_rad = std::asin(dx_SEZ(2) / range); - double azimuth_rad = std::atan2(dx_SEZ(1), dx_SEZ(0)); - + const double range_m = dx_SEZ.norm(); + const double elevation_rad = std::asin(dx_SEZ(2) / range_m); + double azimuth_rad = std::atan2(dx_SEZ(1), -dx_SEZ(0)); azimuth_rad = azimuth_rad < 0.0 ? azimuth_rad + 2.0 * M_PI : azimuth_rad; - return azimuth_rad > azimuthLowerBound && azimuth_rad < azimuthUpperBound && - elevation_rad > elevationLowerBound && elevation_rad < elevationUpperBound && range > rangeLowerBound && - range < rangeUpperBound; + return {azimuth_rad, elevation_rad, range_m}; }; + if (aGroundTargetConfiguration.getAzimuthElevationMask().empty()) + { + condition = [&computeAER, &aGroundTargetConfiguration](const Instant& instant) -> bool + { + const double& azimuthLowerBound = aGroundTargetConfiguration.getAzimuthInterval().accessLowerBound(); + const double& azimuthUpperBound = aGroundTargetConfiguration.getAzimuthInterval().accessUpperBound(); + const double& elevationLowerBound = aGroundTargetConfiguration.getElevationInterval().accessLowerBound(); + const double& elevationUpperBound = aGroundTargetConfiguration.getElevationInterval().accessUpperBound(); + const double& rangeLowerBound = aGroundTargetConfiguration.getRangeInterval().accessLowerBound(); + const double& rangeUpperBound = aGroundTargetConfiguration.getRangeInterval().accessUpperBound(); + + const auto [azimuth_rad, elevation_rad, range] = computeAER(instant); + + return azimuth_rad > azimuthLowerBound && azimuth_rad < azimuthUpperBound && + elevation_rad > elevationLowerBound && elevation_rad < elevationUpperBound && + range > rangeLowerBound && range < rangeUpperBound; + }; + } + else + { + condition = [&computeAER, &aGroundTargetConfiguration](const Instant& instant) -> bool + { + const double& rangeLowerBound = aGroundTargetConfiguration.getRangeInterval().accessLowerBound(); + const double& rangeUpperBound = aGroundTargetConfiguration.getRangeInterval().accessUpperBound(); + + const auto [azimuth_rad, elevation_rad, range_m] = computeAER(instant); + + const bool inMask = aGroundTargetConfiguration.isAboveMask(azimuth_rad, elevation_rad); + + return inMask && range_m > rangeLowerBound && range_m < rangeUpperBound; + }; + } + Array preciseAccessIntervals = accessIntervals; for (Index i = 0; i < preciseAccessIntervals.getSize(); ++i) diff --git a/test/OpenSpaceToolkit/Astrodynamics/Access/Generator.test.cpp b/test/OpenSpaceToolkit/Astrodynamics/Access/Generator.test.cpp index 1a4ff5eea..eeccef096 100644 --- a/test/OpenSpaceToolkit/Astrodynamics/Access/Generator.test.cpp +++ b/test/OpenSpaceToolkit/Astrodynamics/Access/Generator.test.cpp @@ -791,7 +791,7 @@ TEST(OpenSpaceToolkit_Astrodynamics_Access_Generator, GroundTargetConfiguration) r_SEZ_ECEF.row(1) = Vector3d {0.0, 1.0, 0.0}; r_SEZ_ECEF.row(2) = Vector3d {1.0, 0.0, 0.0}; - EXPECT_EQ(groundTargetConfiguration.getR_SEZ_ECEF(), r_SEZ_ECEF); + EXPECT_EQ(groundTargetConfiguration.computeR_SEZ_ECEF(), r_SEZ_ECEF); } } @@ -1147,7 +1147,6 @@ TEST(OpenSpaceToolkit_Astrodynamics_Access_Generator, AerMask) const Orbit satelliteOrbit = generateSatelliteOrbit(); const Array accesses = generator.computeAccesses(interval, groundStationTrajectory, satelliteOrbit); - // std::cout << accesses << std::endl; // Reference data setup From f41fdf51983e7540147d1f6dcd99c7867658a5c7 Mon Sep 17 00:00:00 2001 From: vishwa shah Date: Mon, 11 Nov 2024 05:09:41 +0000 Subject: [PATCH 05/22] refactor: common private methods --- .../Astrodynamics/Access/Generator.hpp | 281 +++++++++--------- .../Astrodynamics/Access/Generator.cpp | 185 +++++------- 2 files changed, 222 insertions(+), 244 deletions(-) diff --git a/include/OpenSpaceToolkit/Astrodynamics/Access/Generator.hpp b/include/OpenSpaceToolkit/Astrodynamics/Access/Generator.hpp index e235e2ef7..ad2d3079f 100644 --- a/include/OpenSpaceToolkit/Astrodynamics/Access/Generator.hpp +++ b/include/OpenSpaceToolkit/Astrodynamics/Access/Generator.hpp @@ -56,13 +56,158 @@ using ostk::astrodynamics::trajectory::State; #define DEFAULT_STEP Duration::Minutes(1.0) #define DEFAULT_TOLERANCE Duration::Microseconds(1.0) +class GroundTargetConfiguration; + +class Generator +{ + public: + Generator( + const Environment& anEnvironment, + const Duration& aStep = DEFAULT_STEP, + const Duration& aTolerance = DEFAULT_TOLERANCE + ); + + Generator( + const Environment& anEnvironment, + const std::function& anAerFilter, + const std::function& anAccessFilter = {}, + const std::function& aStateFilter = {}, + const Duration& aStep = DEFAULT_STEP, + const Duration& aTolerance = DEFAULT_TOLERANCE + ); + + bool isDefined() const; + + Duration getStep() const; + + Duration getTolerance() const; + + std::function getAerFilter() const; + + std::function getAccessFilter() const; + + std::function getStateFilter() const; + + std::function getConditionFunction( + const Trajectory& aFromTrajectory, const Trajectory& aToTrajectory + ) const; + + Array computeAccesses( + const physics::time::Interval& anInterval, const Trajectory& aFromTrajectory, const Trajectory& aToTrajectory + ) const; + + Array> computeAccessesWithGroundTargets( + const physics::time::Interval& anInterval, + const Array& someGroundTargetConfigurations, + const Trajectory& aToTrajectory, + const bool& coarse = false + ) const; + + void setStep(const Duration& aStep); + + void setTolerance(const Duration& aTolerance); + + void setAerFilter(const std::function& anAerFilter); + + void setAccessFilter(const std::function& anAccessFilter); + + void setStateFilter(const std::function& aStateFilter); + + static Generator Undefined(); + + /// @brief Construct an access generator with defined AER ranges + /// + /// @param anAzimuthRange An azimuth interval [deg] + /// @param anElevationRange An elevation interval [deg] + /// @param aRangeRange A range interval [m] + /// @param anEnvironment An environment + /// @return An access generator + static Generator AerRanges( + const Interval& anAzimuthRange, + const Interval& anElevationRange, + const Interval& aRangeRange, + const Environment& anEnvironment + ); + + /// @brief Construct an access generator with a defined AER mask + /// + /// @param anAzimuthElevationMask An azimuth-elevation mask [deg] + /// @param aRangeRange A range interval [m] + /// @param anEnvironment An environment + /// @return An access generator + static Generator AerMask( + const Map& anAzimuthElevationMask, + const Interval& aRangeRange, + const Environment& anEnvironment + ); + + private: + Environment environment_; + + Duration step_; + Duration tolerance_; + + std::function aerFilter_; + std::function accessFilter_; + std::function stateFilter_; + + Array generateAccessesFromIntervals( + const Array& someIntervals, + const physics::time::Interval& anInterval, + const Trajectory& aFromTrajectory, + const Trajectory& aToTrajectory + ) const; + + Array computePreciseCrossings( + const Array& accessIntervals, + const physics::time::Interval& anAnalysisInterval, + const Vector3d& fromPositionCoordinate_ITRF, + const Trajectory& aToTrajectory, + const GroundTargetConfiguration& aGroundTargetConfiguration + ) const; + + static Array ComputeIntervals(const VectorXi& inAccess, const Array& instants); + + static Access GenerateAccess( + const physics::time::Interval& anAccessInterval, + const physics::time::Interval& aGlobalInterval, + const Trajectory& aFromTrajectory, + const Trajectory& aToTrajectory, + const Shared anEarthSPtr, + const Duration& aTolerance + ); + + static Instant FindTimeOfClosestApproach( + const physics::time::Interval& anAccessInterval, + const Trajectory& aFromTrajectory, + const Trajectory& aToTrajectory, + const Duration& aTolerance + ); + + static Angle CalculateElevationAt( + const Instant& anInstant, + const Trajectory& aFromTrajectory, + const Trajectory& aToTrajectory, + const Shared anEarthSPtr + ); + + static bool IsAboveMask( + const Map& anAzimuthElevationMask, const Real& anAzimuth_rad, const Real& anElevation_rad + ); + + static Map ConvertAzimuthElevationMask(const Map& anAzimuthElevationMask); + + friend class GroundTargetConfiguration; +}; + /// @brief Represents the configuration for a ground target, including azimuth, elevation, and range intervals, as well /// as position and LLA (Latitude, Longitude, Altitude). /// /// @details This class provides methods to retrieve the trajectory, position, LLA, and intervals for azimuth, /// elevation, and range. It also includes a method to get the SEZ (South-East-Zenith) rotation matrix. -struct GroundTargetConfiguration +class GroundTargetConfiguration { + public: /// @brief Constructor /// @param anAzimuthInterval An azimuth interval [deg] /// @param anElevationInterval An elevation interval [deg] @@ -209,140 +354,6 @@ struct GroundTargetConfiguration void validateMask_(); }; -class Generator -{ - public: - Generator( - const Environment& anEnvironment, - const Duration& aStep = DEFAULT_STEP, - const Duration& aTolerance = DEFAULT_TOLERANCE - ); - - Generator( - const Environment& anEnvironment, - const std::function& anAerFilter, - const std::function& anAccessFilter = {}, - const std::function& aStateFilter = {}, - const Duration& aStep = DEFAULT_STEP, - const Duration& aTolerance = DEFAULT_TOLERANCE - ); - - bool isDefined() const; - - Duration getStep() const; - - Duration getTolerance() const; - - std::function getAerFilter() const; - - std::function getAccessFilter() const; - - std::function getStateFilter() const; - - std::function getConditionFunction( - const Trajectory& aFromTrajectory, const Trajectory& aToTrajectory - ) const; - - Array computeAccesses( - const physics::time::Interval& anInterval, const Trajectory& aFromTrajectory, const Trajectory& aToTrajectory - ) const; - - Array> computeAccessesWithGroundTargets( - const physics::time::Interval& anInterval, - const Array& someGroundTargetConfigurations, - const Trajectory& aToTrajectory, - const bool& coarse = false - ) const; - - void setStep(const Duration& aStep); - - void setTolerance(const Duration& aTolerance); - - void setAerFilter(const std::function& anAerFilter); - - void setAccessFilter(const std::function& anAccessFilter); - - void setStateFilter(const std::function& aStateFilter); - - static Generator Undefined(); - - /// @brief Construct an access generator with defined AER ranges - /// - /// @param anAzimuthRange An azimuth interval [deg] - /// @param anElevationRange An elevation interval [deg] - /// @param aRangeRange A range interval [m] - /// @param anEnvironment An environment - /// @return An access generator - static Generator AerRanges( - const Interval& anAzimuthRange, - const Interval& anElevationRange, - const Interval& aRangeRange, - const Environment& anEnvironment - ); - - /// @brief Construct an access generator with a defined AER mask - /// - /// @param anAzimuthElevationMask An azimuth-elevation mask [deg] - /// @param aRangeRange A range interval [m] - /// @param anEnvironment An environment - /// @return An access generator - static Generator AerMask( - const Map& anAzimuthElevationMask, - const Interval& aRangeRange, - const Environment& anEnvironment - ); - - private: - Environment environment_; - - Duration step_; - Duration tolerance_; - - std::function aerFilter_; - std::function accessFilter_; - std::function stateFilter_; - - Array generateAccessesFromIntervals( - const Array& someIntervals, - const physics::time::Interval& anInterval, - const Trajectory& aFromTrajectory, - const Trajectory& aToTrajectory - ) const; - - Array computePreciseCrossings( - const Array& accessIntervals, - const physics::time::Interval& anAnalysisInterval, - const Vector3d& fromPositionCoordinate_ITRF, - const Trajectory& aToTrajectory, - const GroundTargetConfiguration& aGroundTargetConfiguration - ) const; - - static Array ComputeIntervals(const VectorXi& inAccess, const Array& instants); - - static Access GenerateAccess( - const physics::time::Interval& anAccessInterval, - const physics::time::Interval& aGlobalInterval, - const Trajectory& aFromTrajectory, - const Trajectory& aToTrajectory, - const Shared anEarthSPtr, - const Duration& aTolerance - ); - - static Instant FindTimeOfClosestApproach( - const physics::time::Interval& anAccessInterval, - const Trajectory& aFromTrajectory, - const Trajectory& aToTrajectory, - const Duration& aTolerance - ); - - static Angle CalculateElevationAt( - const Instant& anInstant, - const Trajectory& aFromTrajectory, - const Trajectory& aToTrajectory, - const Shared anEarthSPtr - ); -}; - class GeneratorContext { public: diff --git a/src/OpenSpaceToolkit/Astrodynamics/Access/Generator.cpp b/src/OpenSpaceToolkit/Astrodynamics/Access/Generator.cpp index 678f70afb..d33c31cb4 100644 --- a/src/OpenSpaceToolkit/Astrodynamics/Access/Generator.cpp +++ b/src/OpenSpaceToolkit/Astrodynamics/Access/Generator.cpp @@ -15,7 +15,9 @@ #include #include +using ostk::core::container::Map; using ostk::core::container::Triple; +using ostk::core::type::Real; using ostk::mathematics::geometry::d3::object::Point; using ostk::mathematics::geometry::d3::object::Segment; @@ -175,22 +177,7 @@ Map GroundTargetConfiguration::getAzimuthElevationMask() const bool GroundTargetConfiguration::isAboveMask(const Real& anAzimuth_rad, const Real& anElevation_rad) const { - auto itLow = azimuthElevationMask_.lower_bound(anAzimuth_rad); - itLow--; - auto itUp = azimuthElevationMask_.upper_bound(anAzimuth_rad); - - // Vector between the two successive mask data points with bounding azimuth values - - const Vector2d lowToUpVector = {itUp->first - itLow->first, itUp->second - itLow->second}; - - // Vector from data point with azimuth lower bound to tested point - - const Vector2d lowToPointVector = {anAzimuth_rad - itLow->first, anElevation_rad - itLow->second}; - - // If the determinant of these two vectors is positive, the tested point lies above the function defined by the - // mask - - return ((lowToUpVector[0] * lowToPointVector[1] - lowToUpVector[1] * lowToPointVector[0]) >= 0.0); + return Generator::IsAboveMask(azimuthElevationMask_, anAzimuth_rad, anElevation_rad); } Matrix3d GroundTargetConfiguration::computeR_SEZ_ECEF() const @@ -245,40 +232,7 @@ void GroundTargetConfiguration::validateIntervals_() const void GroundTargetConfiguration::validateMask_() { - if (azimuthElevationMask_.empty() || azimuthElevationMask_.begin()->first < 0.0 || - azimuthElevationMask_.rbegin()->first > 360.0) - { - throw ostk::core::error::runtime::Wrong("Azimuth-Elevation Mask"); - } - - for (const auto& azimuthElevationPair : azimuthElevationMask_) - { - if ((azimuthElevationPair.second).abs() > 90.0) - { - throw ostk::core::error::runtime::Wrong("Azimuth-Elevation Mask"); - } - } - - if (azimuthElevationMask_.begin()->first != 0.0) - { - azimuthElevationMask_.insert({0.0, azimuthElevationMask_.begin()->second}); - } - - if (azimuthElevationMask_.rbegin()->first != 360.0) - { - azimuthElevationMask_.insert({360.0, azimuthElevationMask_.begin()->second}); - } - - // convert to radians - - Map azimuthElevationMaskRad; - for (const auto& azimuthElevationPair : azimuthElevationMask_) - { - const Real azimuthRad = azimuthElevationPair.first * Real::Pi() / 180.0; - const Real elevationRad = azimuthElevationPair.second * Real::Pi() / 180.0; - azimuthElevationMaskRad.insert({azimuthRad, elevationRad}); - } - azimuthElevationMask_ = azimuthElevationMaskRad; + azimuthElevationMask_ = Generator::ConvertAzimuthElevationMask(azimuthElevationMask_); } Generator::Generator(const Environment& anEnvironment, const Duration& aStep, const Duration& aTolerance) @@ -496,9 +450,9 @@ Array> Generator::computeAccessesWithGroundTargets( for (Index i = 0; i < (Index)mask.rows(); ++i) { - const auto& groundTargetConfiguration = someGroundTargetConfigurations[i]; - const auto& azimuth_rad = azimuths_rad(i); - const auto& elevation_rad = elevations_rad(i); + const GroundTargetConfiguration& groundTargetConfiguration = someGroundTargetConfigurations[i]; + const double& azimuth_rad = azimuths_rad(i); + const double& elevation_rad = elevations_rad(i); mask(i) = mask(i) && groundTargetConfiguration.isAboveMask(azimuth_rad, elevation_rad); } @@ -631,8 +585,6 @@ Generator Generator::AerRanges( const Environment& anEnvironment ) { - using ostk::core::type::Real; - const Interval azimuthRange_deg = anAzimuthRange; const Interval elevationRange_deg = anElevationRange; const Interval rangeRange_m = aRangeRange; @@ -654,61 +606,16 @@ Generator Generator::AerMask( const Map& anAzimuthElevationMask, const Interval& aRangeRange, const Environment& anEnvironment ) { - using ostk::core::container::Map; - using ostk::core::type::Real; - - using ostk::mathematics::object::Vector2d; - - if ((anAzimuthElevationMask.empty()) || (anAzimuthElevationMask.begin()->first < 0.0) || - (anAzimuthElevationMask.rbegin()->first > 360.0)) - { - throw ostk::core::error::runtime::Wrong("Azimuth-Elevation Mask"); - } - - for (const auto& azimuthElevationPair : anAzimuthElevationMask) - { - if ((azimuthElevationPair.second).abs() > 90.0) - { - throw ostk::core::error::runtime::Wrong("Azimuth-Elevation Mask"); - } - } - - Map anAzimuthElevationMask_deg = anAzimuthElevationMask; - const Interval rangeRange_m = aRangeRange; - - if (anAzimuthElevationMask_deg.begin()->first != 0.0) - { - anAzimuthElevationMask_deg.insert({0.0, anAzimuthElevationMask_deg.begin()->second}); - } - - if (anAzimuthElevationMask_deg.rbegin()->first != 360.0) - { - anAzimuthElevationMask_deg.insert({360.0, anAzimuthElevationMask_deg.begin()->second}); - } + const Map anAzimuthElevationMask_rad = ConvertAzimuthElevationMask(anAzimuthElevationMask); - const std::function aerFilter = [anAzimuthElevationMask_deg, - rangeRange_m](const AER& anAER) -> bool + const std::function aerFilter = [anAzimuthElevationMask_rad, + aRangeRange](const AER& anAER) -> bool { - const Real azimuth = anAER.getAzimuth().inDegrees(0.0, +360.0); - const Real elevation = anAER.getElevation().inDegrees(-180.0, +180.0); - - auto itLow = anAzimuthElevationMask_deg.lower_bound(azimuth); - itLow--; - auto itUp = anAzimuthElevationMask_deg.upper_bound(azimuth); - - // Vector between the two successive mask data points with bounding azimuth values - - const Vector2d lowToUpVector = {itUp->first - itLow->first, itUp->second - itLow->second}; - - // Vector from data point with azimuth lower bound to tested point - - const Vector2d lowToPointVector = {azimuth - itLow->first, elevation - itLow->second}; - - // If the determinant of these two vectors is positive, the tested point lies above the function defined by the - // mask + const Real azimuth = anAER.getAzimuth().inRadians(0.0, Real::TwoPi()); + const Real elevation = anAER.getElevation().inRadians(-Real::Pi(), Real::Pi()); - return (lowToUpVector[0] * lowToPointVector[1] - lowToUpVector[1] * lowToPointVector[0] >= 0.0) && - ((!rangeRange_m.isDefined()) || rangeRange_m.contains(anAER.getRange().inMeters())); + return Generator::IsAboveMask(anAzimuthElevationMask_rad, azimuth, elevation) && + ((!aRangeRange.isDefined()) || aRangeRange.contains(anAER.getRange().inMeters())); }; return {anEnvironment, aerFilter}; @@ -759,7 +666,7 @@ Array Generator::computePreciseCrossings( const auto computeAER = [&fromPositionCoordinate_ITRF, &SEZRotation, &aToTrajectory](const Instant& instant ) -> Triple { - const auto toPositionCoordinates_ITRF = + const Vector3d toPositionCoordinates_ITRF = aToTrajectory.getStateAt(instant).inFrame(Frame::ITRF()).getPosition().getCoordinates(); const Vector3d dx = toPositionCoordinates_ITRF - fromPositionCoordinate_ITRF; @@ -811,7 +718,7 @@ Array Generator::computePreciseCrossings( for (Index i = 0; i < preciseAccessIntervals.getSize(); ++i) { - const auto& interval = accessIntervals[i]; + const physics::time::Interval& interval = accessIntervals[i]; const Instant lowerBoundPreviousInstant = interval.getStart() - this->step_; const Instant lowerBoundInstant = interval.getStart(); @@ -1042,6 +949,66 @@ Angle Generator::CalculateElevationAt( return aer.getElevation(); } +bool Generator::IsAboveMask( + const Map& anAzimuthElevationMask, const Real& anAzimuth_rad, const Real& anElevation_rad +) +{ + auto itLow = anAzimuthElevationMask.lower_bound(anAzimuth_rad); + itLow--; + auto itUp = anAzimuthElevationMask.upper_bound(anAzimuth_rad); + + // Vector between the two successive mask data points with bounding azimuth values + + const Vector2d lowToUpVector = {itUp->first - itLow->first, itUp->second - itLow->second}; + + // Vector from data point with azimuth lower bound to tested point + + const Vector2d lowToPointVector = {anAzimuth_rad - itLow->first, anElevation_rad - itLow->second}; + + // If the determinant of these two vectors is positive, the tested point lies above the function defined by the + // mask + + return ((lowToUpVector[0] * lowToPointVector[1] - lowToUpVector[1] * lowToPointVector[0]) >= 0.0); +} + +Map Generator::ConvertAzimuthElevationMask(const Map& anAzimuthElevationMask) +{ + if ((anAzimuthElevationMask.empty()) || (anAzimuthElevationMask.begin()->first < 0.0) || + (anAzimuthElevationMask.rbegin()->first > 360.0)) + { + throw ostk::core::error::runtime::Wrong("Azimuth-Elevation Mask"); + } + + for (const auto& azimuthElevationPair : anAzimuthElevationMask) + { + if ((azimuthElevationPair.second).abs() > 90.0) + { + throw ostk::core::error::runtime::Wrong("Azimuth-Elevation Mask"); + } + } + + Map anAzimuthElevationMask_deg = anAzimuthElevationMask; + + if (anAzimuthElevationMask_deg.begin()->first != 0.0) + { + anAzimuthElevationMask_deg.insert({0.0, anAzimuthElevationMask_deg.begin()->second}); + } + + if (anAzimuthElevationMask_deg.rbegin()->first != 360.0) + { + anAzimuthElevationMask_deg.insert({360.0, anAzimuthElevationMask_deg.begin()->second}); + } + + Map anAzimuthElevationMask_rad; + + for (auto it = anAzimuthElevationMask_deg.begin(); it != anAzimuthElevationMask_deg.end(); ++it) + { + anAzimuthElevationMask_rad.insert({it->first * M_PI / 180.0, it->second * M_PI / 180.0}); + } + + return anAzimuthElevationMask_rad; +} + GeneratorContext::GeneratorContext( const Trajectory& aFromTrajectory, const Trajectory& aToTrajectory, From 5fdadef937158ce9eb96c0881f5221ba938a0fcd Mon Sep 17 00:00:00 2001 From: vishwa shah Date: Tue, 12 Nov 2024 05:26:11 +0000 Subject: [PATCH 06/22] wip: --- .../Astrodynamics/Access/Constraint.hpp | 88 +++++ .../Astrodynamics/Access/Generator.hpp | 183 +++++----- .../Astrodynamics/Access/Constraint.cpp | 204 +++++++++++ .../Astrodynamics/Access/Generator.cpp | 338 +++++++++--------- 4 files changed, 549 insertions(+), 264 deletions(-) create mode 100644 include/OpenSpaceToolkit/Astrodynamics/Access/Constraint.hpp create mode 100644 src/OpenSpaceToolkit/Astrodynamics/Access/Constraint.cpp diff --git a/include/OpenSpaceToolkit/Astrodynamics/Access/Constraint.hpp b/include/OpenSpaceToolkit/Astrodynamics/Access/Constraint.hpp new file mode 100644 index 000000000..acb663a00 --- /dev/null +++ b/include/OpenSpaceToolkit/Astrodynamics/Access/Constraint.hpp @@ -0,0 +1,88 @@ +/// Apache License 2.0 + +#ifndef __OpenSpaceToolkit_Astrodynamics_Access_Constraint__ +#define __OpenSpaceToolkit_Astrodynamics_Access_Constraint__ + +#include +#include + +#include +#include + +#include +#include + +#include + +namespace ostk +{ +namespace astrodynamics +{ +namespace access +{ + +using ostk::core::container::Map; +using ostk::core::type::Real; + +using ostk::mathematics::object::Interval; +using ostk::mathematics::object::Vector2d; + +using ostk::physics::coordinate::spherical::AER; + +class Constraint +{ + public: + struct IntervalConstraint + { + Interval azimuth; // degrees + Interval elevation; // degrees + Interval range; // meters + + IntervalConstraint( + const Interval& anAzimuth, const Interval& anElevation, const Interval& aRange + ); + + bool isSatisfied(const AER& anAer) const; + }; + + struct MaskConstraint + { + Map azimuthElevationMask; // degrees, degrees + Interval range; // meters + + MaskConstraint(const Map& anAzimuthElevationMask, const Interval& aRange); + + bool isSatisfied(const AER& anAer) const; + }; + + static Constraint FromIntervals( + const Interval& azimuth, const Interval& elevation, const Interval& range + ); + + static Constraint FromMask(const Map& azimuthElevationMask, const Interval& range); + + bool isSatisfied(const AER& aer) const; + + bool isMaskBased() const; + + bool isIntervalBased() const; + + std::optional getIntervalConstraint() const; + + std::optional getMaskConstraint() const; + + Interval getRangeInterval() const; + + private: + std::variant constraint_; + + explicit Constraint(IntervalConstraint constraint); + + explicit Constraint(MaskConstraint constraint); +}; + +} // namespace access +} // namespace astrodynamics +} // namespace ostk + +#endif diff --git a/include/OpenSpaceToolkit/Astrodynamics/Access/Generator.hpp b/include/OpenSpaceToolkit/Astrodynamics/Access/Generator.hpp index ad2d3079f..6c4f38dae 100644 --- a/include/OpenSpaceToolkit/Astrodynamics/Access/Generator.hpp +++ b/include/OpenSpaceToolkit/Astrodynamics/Access/Generator.hpp @@ -19,6 +19,7 @@ #include #include +#include #include namespace ostk @@ -50,6 +51,7 @@ using ostk::physics::unit::Length; using EarthGravitationalModel = ostk::physics::environment::gravitational::Earth; using ostk::astrodynamics::Access; +using ostk::astrodynamics::access::Constraint; using ostk::astrodynamics::Trajectory; using ostk::astrodynamics::trajectory::State; @@ -208,115 +210,107 @@ class Generator class GroundTargetConfiguration { public: - /// @brief Constructor - /// @param anAzimuthInterval An azimuth interval [deg] - /// @param anElevationInterval An elevation interval [deg] - /// @param aRangeInterval A range interval [m] - /// @param aPosition A position - GroundTargetConfiguration( - const Interval& anAzimuthInterval, - const Interval& anElevationInterval, - const Interval& aRangeInterval, - const Position& aPosition - ); - - /// @brief Constructor - /// @param anAzimuthInterval An azimuth interval [deg] - /// @param anElevationInterval An elevation interval [deg] - /// @param aRangeInterval A range interval [m] - /// @param aLLA An LLA - GroundTargetConfiguration( - const Interval& anAzimuthInterval, - const Interval& anElevationInterval, - const Interval& aRangeInterval, - const LLA& aLLA - ); - - /// @brief Constructor - /// @param anAzimuthElevationMask An azimuth-elevation mask [deg] - /// @param aRangeInterval A range interval [m] - /// @param aLLA An LLA - GroundTargetConfiguration( - const Map& anAzimuthElevationMask, const Interval& aRangeInterval, const LLA& aLLA - ); + enum class Type + { + Fixed, + Trajectory + }; /// @brief Constructor - /// @param anAzimuthElevationMask An azimuth-elevation mask [deg] - /// @param aRangeInterval A range interval [m] - /// @param aPosition A Position - GroundTargetConfiguration( - const Map& anAzimuthElevationMask, const Interval& aRangeInterval, const Position& aPosition - ); + /// @param aPosition A position + // GroundTargetConfiguration(const Type& aType, const Constraint& constrant, const Position& aPosition); - /// @brief Get the trajectory + /// @brief Get the type /// /// @code{.cpp} /// GroundTargetConfiguration groundTargetConfiguration = { ... } ; - /// Trajectory trajectory = groundTargetConfiguration.getTrajectory(); + /// GroundTargetConfiguration::Type type = groundTargetConfiguration.getType(); /// @endcode /// - /// @return The trajectory - Trajectory getTrajectory() const; + /// @return The type + Type getType() const; - /// @brief Get the position + /// @brief Get the constraint /// /// @code{.cpp} /// GroundTargetConfiguration groundTargetConfiguration = { ... } ; - /// Position position = groundTargetConfiguration.getPosition(); + /// Constraint constraint = groundTargetConfiguration.getConstraint(); /// @endcode /// - /// @return The position - Position getPosition() const; + /// @return The constraint + Constraint getConstraint() const; - /// @brief Get the latitude, longitude, and altitude (LLA) - /// - /// @code{.cpp} - /// GroundTargetConfiguration groundTargetConfiguration = { ... } ; - /// LLA lla = groundTargetConfiguration.getLLA(); - /// @endcode - /// - /// @return The latitude, longitude, and altitude (LLA) - LLA getLLA() const; - - /// @brief Get the azimuth interval + /// @brief Get the trajectory /// /// @code{.cpp} /// GroundTargetConfiguration groundTargetConfiguration = { ... } ; - /// Interval groundTargetConfiguration = generator.getAzimuthInterval(); + /// Trajectory trajectory = groundTargetConfiguration.getTrajectory(); /// @endcode /// - /// @return The azimuth interval - Interval getAzimuthInterval() const; + /// @return The trajectory + Trajectory getTrajectory() const; - /// @brief Get the elevation interval + /// @brief Get the position /// /// @code{.cpp} /// GroundTargetConfiguration groundTargetConfiguration = { ... } ; - /// Interval groundTargetConfiguration = generator.getElevationInterval(); + /// Position position = groundTargetConfiguration.getPosition(); /// @endcode /// - /// @return The elevation interval - Interval getElevationInterval() const; + /// @return The position + Position getPosition() const; - /// @brief Get the range interval + /// @brief Get the latitude, longitude, and altitude (LLA) /// /// @code{.cpp} /// GroundTargetConfiguration groundTargetConfiguration = { ... } ; - /// Interval groundTargetConfiguration = generator.getRangeInterval(); + /// LLA lla = groundTargetConfiguration.getLLA(); /// @endcode /// - /// @return The range interval - Interval getRangeInterval() const; - - /// @brief Get the azimuth-elevation mask + /// @param aCelestialSPtr A celestial body /// - /// @code{.cpp} - /// GroundTargetConfiguration groundTargetConfiguration = { ... } ; - /// Map azimuthElevationMask = groundTargetConfiguration.getAzimuthElevationMask(); - /// @endcode - /// - /// @return The azimuth-elevation mask - Map getAzimuthElevationMask() const; + /// @return The latitude, longitude, and altitude (LLA) + LLA getLLA(const Shared& aCelestialSPtr) const; + + // /// @brief Get the azimuth interval + // /// + // /// @code{.cpp} + // /// GroundTargetConfiguration groundTargetConfiguration = { ... } ; + // /// Interval groundTargetConfiguration = generator.getAzimuthInterval(); + // /// @endcode + // /// + // /// @return The azimuth interval + // Interval getAzimuthInterval() const; + + // /// @brief Get the elevation interval + // /// + // /// @code{.cpp} + // /// GroundTargetConfiguration groundTargetConfiguration = { ... } ; + // /// Interval groundTargetConfiguration = generator.getElevationInterval(); + // /// @endcode + // /// + // /// @return The elevation interval + // Interval getElevationInterval() const; + + // /// @brief Get the range interval + // /// + // /// @code{.cpp} + // /// GroundTargetConfiguration groundTargetConfiguration = { ... } ; + // /// Interval groundTargetConfiguration = generator.getRangeInterval(); + // /// @endcode + // /// + // /// @return The range interval + // Interval getRangeInterval() const; + + // /// @brief Get the azimuth-elevation mask + // /// + // /// @code{.cpp} + // /// GroundTargetConfiguration groundTargetConfiguration = { ... } ; + // /// Map azimuthElevationMask = groundTargetConfiguration.getAzimuthElevationMask(); + // /// @endcode + // /// + // /// @return The azimuth-elevation mask + // Map getAzimuthElevationMask() const; /// @brief Check if the elevation-azimuth are within the mask /// @@ -328,7 +322,7 @@ class GroundTargetConfiguration /// @param anAzimuth An azimuth [deg] /// @param anElevation An elevation [deg] /// @return True if the azimuth-elevation are within the mask, false otherwise - bool isAboveMask(const Real& anAzimuth, const Real& anElevation) const; + // bool isAboveMask(const Real& anAzimuth, const Real& anElevation) const; /// @brief Get the rotation matrix (Matrix3d) from ECEF (Earth-Centered-Earth-Fixed) to SEZ (South-East-Zenith) /// frame @@ -339,19 +333,34 @@ class GroundTargetConfiguration /// @endcode /// /// @return The SEZ rotation matrix - Matrix3d computeR_SEZ_ECEF() const; + Matrix3d computeR_SEZ_ECEF(const Shared& aCelestialSPtr) const; - private: - Interval azimuthInterval_; - Interval elevationInterval_; - Interval rangeInterval_; - Map azimuthElevationMask_; + /// @brief Construct a ground target configuration from an LLA (Latitude, Longitude, Altitude) + /// + /// @code{.cpp} + /// GroundTargetConfiguration groundTargetConfiguration = GroundTargetConfiguration::LLA( + /// constraint, lla, aCelestialSPtr + /// ); + /// @endcode + /// + /// @param constraint + /// @param anLLA + /// @param aCelestialSPtr + /// @return Ground target configuration + static GroundTargetConfiguration FromLLA( + const Constraint& constraint, const LLA& anLLA, const Shared& aCelestialSPtr + ); - Position position_; - LLA lla_; + static GroundTargetConfiguration FromPosition(const Constraint& constraint, const Position& aPosition); + + static GroundTargetConfiguration FromTrajectory(const Constraint& constraint, const Trajectory& aTrajectory); + + private: + Type type_; + Constraint constraint_; + Trajectory trajectory_; - void validateIntervals_() const; - void validateMask_(); + GroundTargetConfiguration(const Type& aType, const Constraint& constrant, const Trajectory& aTrajectory); }; class GeneratorContext diff --git a/src/OpenSpaceToolkit/Astrodynamics/Access/Constraint.cpp b/src/OpenSpaceToolkit/Astrodynamics/Access/Constraint.cpp new file mode 100644 index 000000000..03287be30 --- /dev/null +++ b/src/OpenSpaceToolkit/Astrodynamics/Access/Constraint.cpp @@ -0,0 +1,204 @@ +/// Apache License 2.0 + +#include + +namespace ostk +{ +namespace astrodynamics +{ +namespace access +{ + +Constraint::IntervalConstraint::IntervalConstraint( + const Interval& anAzimuthInterval, + const Interval& anElevationInterval, + const Interval& aRangeInterval +) + : azimuth(anAzimuthInterval), + elevation(anElevationInterval), + range(aRangeInterval) +{ + if (!azimuth.isDefined() || !elevation.isDefined() || !range.isDefined()) + { + throw ostk::core::error::runtime::Undefined("Interval"); + } + + if (azimuth.getLowerBound() < 0.0 || azimuth.getUpperBound() > 360.0) + { + throw ostk::core::error::RuntimeError( + "The azimuth interval [{}, {}] must be in the range [0, 360] deg", + azimuth.accessLowerBound(), + azimuth.accessUpperBound() + ); + } + + if (elevation.getLowerBound() < -90.0 || elevation.getUpperBound() > 90.0) + { + throw ostk::core::error::RuntimeError( + "The elevation interval [{}, {}] must be in the range [-90, 90] deg.", + elevation.accessLowerBound(), + elevation.accessUpperBound() + ); + } + + if (range.getLowerBound() < 0.0) + { + throw ostk::core::error::RuntimeError( + "The range interval [{}, {}] must be positive.", range.accessLowerBound(), range.accessUpperBound() + ); + } +} + +bool Constraint::IntervalConstraint::isSatisfied(const AER& anAer) const +{ + return azimuth.contains(anAer.getAzimuth().inDegrees()) && elevation.contains(anAer.getElevation().inDegrees()) && + range.contains(anAer.getRange().inMeters()); +} + +Constraint::MaskConstraint::MaskConstraint( + const Map& anAzimuthElevationMask, const Interval& aRangeInterval +) + : azimuthElevationMask(anAzimuthElevationMask), + range(aRangeInterval) +{ + if (range.getLowerBound() < 0.0) + { + throw ostk::core::error::RuntimeError( + "The range interval [{}, {}] must be positive.", range.accessLowerBound(), range.accessUpperBound() + ); + } + + if ((anAzimuthElevationMask.empty()) || (anAzimuthElevationMask.begin()->first < 0.0) || + (anAzimuthElevationMask.rbegin()->first > 360.0)) + { + throw ostk::core::error::runtime::Wrong("Azimuth-Elevation Mask"); + } + + for (const auto& azimuthElevationPair : anAzimuthElevationMask) + { + if ((azimuthElevationPair.second).abs() > 90.0) + { + throw ostk::core::error::runtime::Wrong("Azimuth-Elevation Mask"); + } + } + + Map anAzimuthElevationMask_deg = anAzimuthElevationMask; + + if (anAzimuthElevationMask_deg.begin()->first != 0.0) + { + anAzimuthElevationMask_deg.insert({0.0, anAzimuthElevationMask_deg.begin()->second}); + } + + if (anAzimuthElevationMask_deg.rbegin()->first != 360.0) + { + anAzimuthElevationMask_deg.insert({360.0, anAzimuthElevationMask_deg.begin()->second}); + } + + Map anAzimuthElevationMask_rad; + + for (auto it = anAzimuthElevationMask_deg.begin(); it != anAzimuthElevationMask_deg.end(); ++it) + { + anAzimuthElevationMask_rad.insert({it->first * M_PI / 180.0, it->second * M_PI / 180.0}); + } + + azimuthElevationMask = anAzimuthElevationMask_rad; +} + +bool Constraint::MaskConstraint::isSatisfied(const AER& anAer) const +{ + const Real azimuth_rad = anAer.getAzimuth().inRadians(0.0, Real::TwoPi()); + const Real elevation_rad = anAer.getElevation().inRadians(-Real::Pi(), Real::Pi()); + + auto itLow = azimuthElevationMask.lower_bound(azimuth_rad); + itLow--; + auto itUp = azimuthElevationMask.upper_bound(azimuth_rad); + + // Vector between the two successive mask data points with bounding azimuth values + + const Vector2d lowToUpVector = {itUp->first - itLow->first, itUp->second - itLow->second}; + + // Vector from data point with azimuth lower bound to tested point + + const Vector2d lowToPointVector = {azimuth_rad - itLow->first, elevation_rad - itLow->second}; + + // If the determinant of these two vectors is positive, the tested point lies above the function defined by the + // mask + + return ((lowToUpVector[0] * lowToPointVector[1] - lowToUpVector[1] * lowToPointVector[0]) >= 0.0); +} + +Constraint Constraint::FromIntervals( + const Interval& azimuth, const Interval& elevation, const Interval& range +) +{ + return Constraint(IntervalConstraint {azimuth, elevation, range}); +} + +Constraint Constraint::FromMask(const Map& azimuthElevationMask, const Interval& range) +{ + return Constraint(MaskConstraint {azimuthElevationMask, range}); +} + +bool Constraint::isSatisfied(const AER& aer) const +{ + return std::visit( + [&aer](const auto& constraint) + { + return constraint.isSatisfied(aer); + }, + constraint_ + ); +} + +bool Constraint::isMaskBased() const +{ + return std::holds_alternative(constraint_); +} + +bool Constraint::isIntervalBased() const +{ + return std::holds_alternative(constraint_); +} + +std::optional Constraint::getIntervalConstraint() const +{ + if (const auto* interval = std::get_if(&constraint_)) + { + return *interval; + } + return std::nullopt; +} + +std::optional Constraint::getMaskConstraint() const +{ + if (const auto* mask = std::get_if(&constraint_)) + { + return *mask; + } + return std::nullopt; +} + +Interval Constraint::getRangeInterval() const +{ + return std::visit( + [](const auto& constraint) -> Interval + { + return constraint.range; + }, + constraint_ + ); +} + +Constraint::Constraint(IntervalConstraint constraint) + : constraint_(std::move(constraint)) +{ +} + +Constraint::Constraint(MaskConstraint constraint) + : constraint_(std::move(constraint)) +{ +} + +} // namespace access +} // namespace astrodynamics +} // namespace ostk diff --git a/src/OpenSpaceToolkit/Astrodynamics/Access/Generator.cpp b/src/OpenSpaceToolkit/Astrodynamics/Access/Generator.cpp index d33c31cb4..ba2284d89 100644 --- a/src/OpenSpaceToolkit/Astrodynamics/Access/Generator.cpp +++ b/src/OpenSpaceToolkit/Astrodynamics/Access/Generator.cpp @@ -44,149 +44,72 @@ namespace astrodynamics namespace access { -GroundTargetConfiguration::GroundTargetConfiguration( - const Interval& anAzimuthInterval, - const Interval& anElevationInterval, - const Interval& aRangeInterval, - const Position& aPosition -) - : azimuthInterval_(anAzimuthInterval), - elevationInterval_(anElevationInterval), - rangeInterval_(aRangeInterval), - azimuthElevationMask_({}), - position_(aPosition), - lla_(LLA::Cartesian( - aPosition.getCoordinates(), - EarthGravitationalModel::EGM2008.equatorialRadius_, - EarthGravitationalModel::EGM2008.flattening_ - )) +GroundTargetConfiguration::Type GroundTargetConfiguration::getType() const { - if (aPosition.isDefined() && aPosition.accessFrame() != Frame::ITRF()) - { - throw ostk::core::error::RuntimeError("The position frame must be ITRF."); - } - - validateIntervals_(); + return type_; } -GroundTargetConfiguration::GroundTargetConfiguration( - const Interval& anAzimuthInterval, - const Interval& anElevationInterval, - const Interval& aRangeInterval, - const LLA& aLLA -) - : azimuthInterval_(anAzimuthInterval), - elevationInterval_(anElevationInterval), - rangeInterval_(aRangeInterval), - azimuthElevationMask_({}), - position_(Position::Meters( - aLLA.toCartesian( - EarthGravitationalModel::EGM2008.equatorialRadius_, EarthGravitationalModel::EGM2008.flattening_ - ), - Frame::ITRF() - )), - lla_(aLLA) +Constraint GroundTargetConfiguration::getConstraint() const { - if (!aLLA.isDefined()) - { - throw ostk::core::error::runtime::Undefined("LLA"); - } - - validateIntervals_(); + return constraint_; } -GroundTargetConfiguration::GroundTargetConfiguration( - const Map& anAzimuthElevationMask, const Interval& aRangeInterval, const Position& aPosition -) - : azimuthInterval_(Interval::Undefined()), - elevationInterval_(Interval::Undefined()), - rangeInterval_(aRangeInterval), - azimuthElevationMask_(anAzimuthElevationMask), - position_(aPosition), - lla_(LLA::Cartesian( - aPosition.getCoordinates(), - EarthGravitationalModel::EGM2008.equatorialRadius_, - EarthGravitationalModel::EGM2008.flattening_ - )) +Trajectory GroundTargetConfiguration::getTrajectory() const { - if (aPosition.isDefined() && aPosition.accessFrame() != Frame::ITRF()) - { - throw ostk::core::error::RuntimeError("The position frame must be ITRF."); - } - - validateMask_(); + return trajectory_; } -GroundTargetConfiguration::GroundTargetConfiguration( - const Map& anAzimuthElevationMask, const Interval& aRangeInterval, const LLA& aLLA -) - : azimuthInterval_(Interval::Undefined()), - elevationInterval_(Interval::Undefined()), - rangeInterval_(aRangeInterval), - azimuthElevationMask_(anAzimuthElevationMask), - position_(Position::Meters( - aLLA.toCartesian( - EarthGravitationalModel::EGM2008.equatorialRadius_, EarthGravitationalModel::EGM2008.flattening_ - ), - Frame::ITRF() - )), - lla_(aLLA) +Position GroundTargetConfiguration::getPosition() const { - if (!aLLA.isDefined()) + if (type_ != Type::Fixed) { - throw ostk::core::error::runtime::Undefined("LLA"); + throw ostk::core::error::RuntimeError("Position is only defined for fixed targets."); } - validateMask_(); + return trajectory_.getStateAt(Instant::J2000()).inFrame(Frame::ITRF()).getPosition(); } -Trajectory GroundTargetConfiguration::getTrajectory() const +LLA GroundTargetConfiguration::getLLA(const Shared& aCelestialSPtr) const { - return Trajectory::Position(position_); + return LLA::Cartesian( + getPosition().accessCoordinates(), aCelestialSPtr->getEquatorialRadius(), aCelestialSPtr->getFlattening() + ); } -Position GroundTargetConfiguration::getPosition() const -{ - return position_; -} +// Interval GroundTargetConfiguration::getAzimuthInterval() const +// { +// return azimuthInterval_; +// } -LLA GroundTargetConfiguration::getLLA() const -{ - return lla_; -} +// Interval GroundTargetConfiguration::getElevationInterval() const +// { +// return elevationInterval_; +// } -Interval GroundTargetConfiguration::getAzimuthInterval() const -{ - return azimuthInterval_; -} +// Interval GroundTargetConfiguration::getRangeInterval() const +// { +// return rangeInterval_; +// } -Interval GroundTargetConfiguration::getElevationInterval() const -{ - return elevationInterval_; -} +// Map GroundTargetConfiguration::getAzimuthElevationMask() const +// { +// return azimuthElevationMask_; +// } -Interval GroundTargetConfiguration::getRangeInterval() const -{ - return rangeInterval_; -} +// bool GroundTargetConfiguration::isAboveMask(const Real& anAzimuth_rad, const Real& anElevation_rad) const +// { +// return Generator::IsAboveMask(azimuthElevationMask_, anAzimuth_rad, anElevation_rad); +// } -Map GroundTargetConfiguration::getAzimuthElevationMask() const +Matrix3d GroundTargetConfiguration::computeR_SEZ_ECEF(const Shared& aCelestialSPtr) const { - return azimuthElevationMask_; -} + const LLA lla = this->getLLA(aCelestialSPtr); -bool GroundTargetConfiguration::isAboveMask(const Real& anAzimuth_rad, const Real& anElevation_rad) const -{ - return Generator::IsAboveMask(azimuthElevationMask_, anAzimuth_rad, anElevation_rad); -} - -Matrix3d GroundTargetConfiguration::computeR_SEZ_ECEF() const -{ // TBM: Move this to OSTk physics as a utility function - const double sinLat = std::sin(lla_.getLatitude().inRadians()); - const double cosLat = std::cos(lla_.getLatitude().inRadians()); - const double sinLon = std::sin(lla_.getLongitude().inRadians()); - const double cosLon = std::cos(lla_.getLongitude().inRadians()); + const double sinLat = std::sin(lla.getLatitude().inRadians()); + const double cosLat = std::cos(lla.getLatitude().inRadians()); + const double sinLon = std::sin(lla.getLongitude().inRadians()); + const double cosLon = std::cos(lla.getLongitude().inRadians()); Matrix3d SEZRotation; SEZRotation << sinLat * cosLon, sinLat * sinLon, -cosLat, -sinLon, cosLon, 0.0, cosLat * cosLon, cosLat * sinLon, @@ -195,45 +118,87 @@ Matrix3d GroundTargetConfiguration::computeR_SEZ_ECEF() const return SEZRotation; } -void GroundTargetConfiguration::validateIntervals_() const +GroundTargetConfiguration GroundTargetConfiguration::FromLLA( + const Constraint& constraint, const LLA& anLLA, const Shared& aCelestialSPtr +) { - if (!azimuthInterval_.isDefined() || !elevationInterval_.isDefined() || !rangeInterval_.isDefined()) - { - throw ostk::core::error::runtime::Undefined("Interval"); - } + return GroundTargetConfiguration( + GroundTargetConfiguration::Type::Fixed, + constraint, + Trajectory::Position(Position::Meters( + anLLA.toCartesian(aCelestialSPtr->getEquatorialRadius(), aCelestialSPtr->getFlattening()), Frame::ITRF() + )) + ); +} - if (azimuthInterval_.getLowerBound() < 0.0 || azimuthInterval_.getUpperBound() > 360.0) - { - throw ostk::core::error::RuntimeError( - "The azimuth interval [{}, {}] must be in the range [0, 360] deg", - azimuthInterval_.accessLowerBound(), - azimuthInterval_.accessUpperBound() - ); - } +GroundTargetConfiguration GroundTargetConfiguration::FromPosition( + const Constraint& constraint, const Position& aPosition +) +{ + return GroundTargetConfiguration( + GroundTargetConfiguration::Type::Fixed, constraint, Trajectory::Position(aPosition) + ); +} - if (elevationInterval_.getLowerBound() < -90.0 || elevationInterval_.getUpperBound() > 90.0) - { - throw ostk::core::error::RuntimeError( - "The elevation interval [{}, {}] must be in the range [-90, 90] deg.", - elevationInterval_.accessLowerBound(), - elevationInterval_.accessUpperBound() - ); - } +GroundTargetConfiguration GroundTargetConfiguration::FromTrajectory( + const Constraint& constraint, const Trajectory& aTrajectory +) +{ + return GroundTargetConfiguration(GroundTargetConfiguration::Type::Trajectory, constraint, aTrajectory); +} - if (rangeInterval_.getLowerBound() < 0.0) +GroundTargetConfiguration::GroundTargetConfiguration( + const GroundTargetConfiguration::Type& aType, const Constraint& aConstraint, const Trajectory& aTrajectory +) + : type_(aType), + constraint_(aConstraint), + trajectory_(aTrajectory) +{ + if (!aTrajectory.isDefined()) { - throw ostk::core::error::RuntimeError( - "The range interval [{}, {}] must be positive.", - rangeInterval_.accessLowerBound(), - rangeInterval_.accessUpperBound() - ); + throw ostk::core::error::runtime::Undefined("Trajectory"); } } -void GroundTargetConfiguration::validateMask_() -{ - azimuthElevationMask_ = Generator::ConvertAzimuthElevationMask(azimuthElevationMask_); -} +// void GroundTargetConfiguration::validateIntervals_() const +// { +// if (!azimuthInterval_.isDefined() || !elevationInterval_.isDefined() || !rangeInterval_.isDefined()) +// { +// throw ostk::core::error::runtime::Undefined("Interval"); +// } + +// if (azimuthInterval_.getLowerBound() < 0.0 || azimuthInterval_.getUpperBound() > 360.0) +// { +// throw ostk::core::error::RuntimeError( +// "The azimuth interval [{}, {}] must be in the range [0, 360] deg", +// azimuthInterval_.accessLowerBound(), +// azimuthInterval_.accessUpperBound() +// ); +// } + +// if (elevationInterval_.getLowerBound() < -90.0 || elevationInterval_.getUpperBound() > 90.0) +// { +// throw ostk::core::error::RuntimeError( +// "The elevation interval [{}, {}] must be in the range [-90, 90] deg.", +// elevationInterval_.accessLowerBound(), +// elevationInterval_.accessUpperBound() +// ); +// } + +// if (rangeInterval_.getLowerBound() < 0.0) +// { +// throw ostk::core::error::RuntimeError( +// "The range interval [{}, {}] must be positive.", +// rangeInterval_.accessLowerBound(), +// rangeInterval_.accessUpperBound() +// ); +// } +// } + +// void GroundTargetConfiguration::validateMask_() +// { +// azimuthElevationMask_ = Generator::ConvertAzimuthElevationMask(azimuthElevationMask_); +// } Generator::Generator(const Environment& anEnvironment, const Duration& aStep, const Duration& aTolerance) : environment_(anEnvironment), @@ -374,9 +339,11 @@ Array> Generator::computeAccessesWithGroundTargets( const Index targetCount = someGroundTargetConfigurations.getSize(); Eigen::Matrix SEZRotations(3, 3 * targetCount); + const Shared earthSPtr = this->environment_.accessCelestialObjectWithName("Earth"); + for (Index i = 0; i < targetCount; ++i) { - SEZRotations.block<3, 3>(0, 3 * i) = someGroundTargetConfigurations[i].computeR_SEZ_ECEF(); + SEZRotations.block<3, 3>(0, 3 * i) = someGroundTargetConfigurations[i].computeR_SEZ_ECEF(earthSPtr); } // create a stacked matrix of ITRF positions for all ground targets @@ -392,7 +359,7 @@ Array> Generator::computeAccessesWithGroundTargets( someGroundTargetConfigurations.end(), [](const auto& groundTargetConfiguration) { - return !groundTargetConfiguration.getAzimuthElevationMask().empty(); + return groundTargetConfiguration.getConstraint().isMaskBased(); } ); @@ -408,15 +375,16 @@ Array> Generator::computeAccessesWithGroundTargets( for (Index i = 0; i < targetCount; ++i) { - aerLowerBounds(i, 0) = someGroundTargetConfigurations[i].getAzimuthInterval().accessLowerBound() * degToRad; - aerLowerBounds(i, 1) = - someGroundTargetConfigurations[i].getElevationInterval().accessLowerBound() * degToRad; - aerLowerBounds(i, 2) = someGroundTargetConfigurations[i].getRangeInterval().accessLowerBound(); - - aerUpperBounds(i, 0) = someGroundTargetConfigurations[i].getAzimuthInterval().accessUpperBound() * degToRad; - aerUpperBounds(i, 1) = - someGroundTargetConfigurations[i].getElevationInterval().accessUpperBound() * degToRad; - aerUpperBounds(i, 2) = someGroundTargetConfigurations[i].getRangeInterval().accessUpperBound(); + const Constraint::IntervalConstraint intervalConstraint = + someGroundTargetConfigurations[i].getConstraint().getIntervalConstraint().value(); + + aerLowerBounds(i, 0) = intervalConstraint.azimuth.accessLowerBound() * degToRad; + aerLowerBounds(i, 1) = intervalConstraint.elevation.accessLowerBound() * degToRad; + aerLowerBounds(i, 2) = intervalConstraint.range.accessLowerBound(); + + aerUpperBounds(i, 0) = intervalConstraint.azimuth.accessUpperBound() * degToRad; + aerUpperBounds(i, 1) = intervalConstraint.elevation.accessUpperBound() * degToRad; + aerUpperBounds(i, 2) = intervalConstraint.range.accessUpperBound(); } aerFilter = [aerLowerBounds, @@ -437,8 +405,11 @@ Array> Generator::computeAccessesWithGroundTargets( for (Index i = 0; i < targetCount; ++i) { - rangeLowerBounds(i) = someGroundTargetConfigurations[i].getRangeInterval().accessLowerBound(); - rangeUpperBounds(i) = someGroundTargetConfigurations[i].getRangeInterval().accessUpperBound(); + const Constraint::MaskConstraint constraint = + someGroundTargetConfigurations[i].getConstraint().getMaskConstraint().value(); + + rangeLowerBounds(i) = constraint.range.accessLowerBound(); + rangeUpperBounds(i) = constraint.range.accessUpperBound(); } aerFilter = [&someGroundTargetConfigurations, rangeLowerBounds, rangeUpperBounds]( @@ -450,11 +421,16 @@ Array> Generator::computeAccessesWithGroundTargets( for (Index i = 0; i < (Index)mask.rows(); ++i) { - const GroundTargetConfiguration& groundTargetConfiguration = someGroundTargetConfigurations[i]; + const Constraint::MaskConstraint constraint = + someGroundTargetConfigurations[i].getConstraint().getMaskConstraint().value(); + const double& azimuth_rad = azimuths_rad(i); const double& elevation_rad = elevations_rad(i); - mask(i) = mask(i) && groundTargetConfiguration.isAboveMask(azimuth_rad, elevation_rad); + const AER aer = + AER(Angle::Radians(azimuth_rad), Angle::Radians(elevation_rad), Length::Meters(ranges_m(i))); + + mask(i) = mask(i) && constraint.isSatisfied(aer); } return mask; @@ -659,7 +635,9 @@ Array Generator::computePreciseCrossings( { const RootSolver rootSolver = RootSolver(100, this->tolerance_.inSeconds()); - const Matrix3d SEZRotation = aGroundTargetConfiguration.computeR_SEZ_ECEF(); + const Shared earthSPtr = this->environment_.accessCelestialObjectWithName("Earth"); + + const Matrix3d SEZRotation = aGroundTargetConfiguration.computeR_SEZ_ECEF(earthSPtr); std::function condition; @@ -681,16 +659,19 @@ Array Generator::computePreciseCrossings( return {azimuth_rad, elevation_rad, range_m}; }; - if (aGroundTargetConfiguration.getAzimuthElevationMask().empty()) + if (aGroundTargetConfiguration.getConstraint().isIntervalBased()) { - condition = [&computeAER, &aGroundTargetConfiguration](const Instant& instant) -> bool + const Constraint::IntervalConstraint intervalConstraint = + aGroundTargetConfiguration.getConstraint().getIntervalConstraint().value(); + + condition = [&computeAER, intervalConstraint](const Instant& instant) -> bool { - const double& azimuthLowerBound = aGroundTargetConfiguration.getAzimuthInterval().accessLowerBound(); - const double& azimuthUpperBound = aGroundTargetConfiguration.getAzimuthInterval().accessUpperBound(); - const double& elevationLowerBound = aGroundTargetConfiguration.getElevationInterval().accessLowerBound(); - const double& elevationUpperBound = aGroundTargetConfiguration.getElevationInterval().accessUpperBound(); - const double& rangeLowerBound = aGroundTargetConfiguration.getRangeInterval().accessLowerBound(); - const double& rangeUpperBound = aGroundTargetConfiguration.getRangeInterval().accessUpperBound(); + const double& azimuthLowerBound = intervalConstraint.azimuth.accessLowerBound(); + const double& azimuthUpperBound = intervalConstraint.azimuth.accessUpperBound(); + const double& elevationLowerBound = intervalConstraint.elevation.accessLowerBound(); + const double& elevationUpperBound = intervalConstraint.elevation.accessUpperBound(); + const double& rangeLowerBound = intervalConstraint.range.accessLowerBound(); + const double& rangeUpperBound = intervalConstraint.range.accessUpperBound(); const auto [azimuth_rad, elevation_rad, range] = computeAER(instant); @@ -701,16 +682,19 @@ Array Generator::computePreciseCrossings( } else { - condition = [&computeAER, &aGroundTargetConfiguration](const Instant& instant) -> bool + const Constraint::MaskConstraint maskConstraint = + aGroundTargetConfiguration.getConstraint().getMaskConstraint().value(); + + condition = [&computeAER, maskConstraint](const Instant& instant) -> bool { - const double& rangeLowerBound = aGroundTargetConfiguration.getRangeInterval().accessLowerBound(); - const double& rangeUpperBound = aGroundTargetConfiguration.getRangeInterval().accessUpperBound(); + const double& rangeLowerBound = maskConstraint.range.accessLowerBound(); + const double& rangeUpperBound = maskConstraint.range.accessUpperBound(); const auto [azimuth_rad, elevation_rad, range_m] = computeAER(instant); - const bool inMask = aGroundTargetConfiguration.isAboveMask(azimuth_rad, elevation_rad); + const AER aer = AER(Angle::Radians(azimuth_rad), Angle::Radians(elevation_rad), Length::Meters(range_m)); - return inMask && range_m > rangeLowerBound && range_m < rangeUpperBound; + return maskConstraint.isSatisfied(aer) && range_m > rangeLowerBound && range_m < rangeUpperBound; }; } From fe0858d5950087f8c860cb9117a095e1e65554c0 Mon Sep 17 00:00:00 2001 From: vishwa shah Date: Tue, 12 Nov 2024 07:54:07 +0000 Subject: [PATCH 07/22] feat: cleaner interface, wip --- .../Astrodynamics/Access/Constraint.hpp | 4 +- .../Astrodynamics/Access/Generator.hpp | 342 +++++------- .../Astrodynamics/Access/Constraint.cpp | 10 +- .../Astrodynamics/Access/Generator.cpp | 463 ++++++----------- .../Astrodynamics/Access/Generator.test.cpp | 486 ++++++++---------- .../Flight/Profile/Transform.test.cpp | 2 +- 6 files changed, 513 insertions(+), 794 deletions(-) diff --git a/include/OpenSpaceToolkit/Astrodynamics/Access/Constraint.hpp b/include/OpenSpaceToolkit/Astrodynamics/Access/Constraint.hpp index acb663a00..8fb2b7763 100644 --- a/include/OpenSpaceToolkit/Astrodynamics/Access/Constraint.hpp +++ b/include/OpenSpaceToolkit/Astrodynamics/Access/Constraint.hpp @@ -76,9 +76,9 @@ class Constraint private: std::variant constraint_; - explicit Constraint(IntervalConstraint constraint); + explicit Constraint(const IntervalConstraint& constraint); - explicit Constraint(MaskConstraint constraint); + explicit Constraint(const MaskConstraint& constraint); }; } // namespace access diff --git a/include/OpenSpaceToolkit/Astrodynamics/Access/Generator.hpp b/include/OpenSpaceToolkit/Astrodynamics/Access/Generator.hpp index 6c4f38dae..2059275a4 100644 --- a/include/OpenSpaceToolkit/Astrodynamics/Access/Generator.hpp +++ b/include/OpenSpaceToolkit/Astrodynamics/Access/Generator.hpp @@ -58,156 +58,12 @@ using ostk::astrodynamics::trajectory::State; #define DEFAULT_STEP Duration::Minutes(1.0) #define DEFAULT_TOLERANCE Duration::Microseconds(1.0) -class GroundTargetConfiguration; - -class Generator -{ - public: - Generator( - const Environment& anEnvironment, - const Duration& aStep = DEFAULT_STEP, - const Duration& aTolerance = DEFAULT_TOLERANCE - ); - - Generator( - const Environment& anEnvironment, - const std::function& anAerFilter, - const std::function& anAccessFilter = {}, - const std::function& aStateFilter = {}, - const Duration& aStep = DEFAULT_STEP, - const Duration& aTolerance = DEFAULT_TOLERANCE - ); - - bool isDefined() const; - - Duration getStep() const; - - Duration getTolerance() const; - - std::function getAerFilter() const; - - std::function getAccessFilter() const; - - std::function getStateFilter() const; - - std::function getConditionFunction( - const Trajectory& aFromTrajectory, const Trajectory& aToTrajectory - ) const; - - Array computeAccesses( - const physics::time::Interval& anInterval, const Trajectory& aFromTrajectory, const Trajectory& aToTrajectory - ) const; - - Array> computeAccessesWithGroundTargets( - const physics::time::Interval& anInterval, - const Array& someGroundTargetConfigurations, - const Trajectory& aToTrajectory, - const bool& coarse = false - ) const; - - void setStep(const Duration& aStep); - - void setTolerance(const Duration& aTolerance); - - void setAerFilter(const std::function& anAerFilter); - - void setAccessFilter(const std::function& anAccessFilter); - - void setStateFilter(const std::function& aStateFilter); - - static Generator Undefined(); - - /// @brief Construct an access generator with defined AER ranges - /// - /// @param anAzimuthRange An azimuth interval [deg] - /// @param anElevationRange An elevation interval [deg] - /// @param aRangeRange A range interval [m] - /// @param anEnvironment An environment - /// @return An access generator - static Generator AerRanges( - const Interval& anAzimuthRange, - const Interval& anElevationRange, - const Interval& aRangeRange, - const Environment& anEnvironment - ); - - /// @brief Construct an access generator with a defined AER mask - /// - /// @param anAzimuthElevationMask An azimuth-elevation mask [deg] - /// @param aRangeRange A range interval [m] - /// @param anEnvironment An environment - /// @return An access generator - static Generator AerMask( - const Map& anAzimuthElevationMask, - const Interval& aRangeRange, - const Environment& anEnvironment - ); - - private: - Environment environment_; - - Duration step_; - Duration tolerance_; - - std::function aerFilter_; - std::function accessFilter_; - std::function stateFilter_; - - Array generateAccessesFromIntervals( - const Array& someIntervals, - const physics::time::Interval& anInterval, - const Trajectory& aFromTrajectory, - const Trajectory& aToTrajectory - ) const; - - Array computePreciseCrossings( - const Array& accessIntervals, - const physics::time::Interval& anAnalysisInterval, - const Vector3d& fromPositionCoordinate_ITRF, - const Trajectory& aToTrajectory, - const GroundTargetConfiguration& aGroundTargetConfiguration - ) const; - - static Array ComputeIntervals(const VectorXi& inAccess, const Array& instants); - - static Access GenerateAccess( - const physics::time::Interval& anAccessInterval, - const physics::time::Interval& aGlobalInterval, - const Trajectory& aFromTrajectory, - const Trajectory& aToTrajectory, - const Shared anEarthSPtr, - const Duration& aTolerance - ); - - static Instant FindTimeOfClosestApproach( - const physics::time::Interval& anAccessInterval, - const Trajectory& aFromTrajectory, - const Trajectory& aToTrajectory, - const Duration& aTolerance - ); - - static Angle CalculateElevationAt( - const Instant& anInstant, - const Trajectory& aFromTrajectory, - const Trajectory& aToTrajectory, - const Shared anEarthSPtr - ); - - static bool IsAboveMask( - const Map& anAzimuthElevationMask, const Real& anAzimuth_rad, const Real& anElevation_rad - ); - - static Map ConvertAzimuthElevationMask(const Map& anAzimuthElevationMask); - - friend class GroundTargetConfiguration; -}; - /// @brief Represents the configuration for a ground target, including azimuth, elevation, and range intervals, as well /// as position and LLA (Latitude, Longitude, Altitude). /// /// @details This class provides methods to retrieve the trajectory, position, LLA, and intervals for azimuth, /// elevation, and range. It also includes a method to get the SEZ (South-East-Zenith) rotation matrix. -class GroundTargetConfiguration +class AccessTarget { public: enum class Type @@ -218,13 +74,13 @@ class GroundTargetConfiguration /// @brief Constructor /// @param aPosition A position - // GroundTargetConfiguration(const Type& aType, const Constraint& constrant, const Position& aPosition); + // AccessTarget(const Type& aType, const Constraint& constrant, const Position& aPosition); /// @brief Get the type /// /// @code{.cpp} - /// GroundTargetConfiguration groundTargetConfiguration = { ... } ; - /// GroundTargetConfiguration::Type type = groundTargetConfiguration.getType(); + /// AccessTarget accessTarget = { ... } ; + /// AccessTarget::Type type = accessTarget.getType(); /// @endcode /// /// @return The type @@ -233,8 +89,8 @@ class GroundTargetConfiguration /// @brief Get the constraint /// /// @code{.cpp} - /// GroundTargetConfiguration groundTargetConfiguration = { ... } ; - /// Constraint constraint = groundTargetConfiguration.getConstraint(); + /// AccessTarget accessTarget = { ... } ; + /// Constraint constraint = accessTarget.getConstraint(); /// @endcode /// /// @return The constraint @@ -243,8 +99,8 @@ class GroundTargetConfiguration /// @brief Get the trajectory /// /// @code{.cpp} - /// GroundTargetConfiguration groundTargetConfiguration = { ... } ; - /// Trajectory trajectory = groundTargetConfiguration.getTrajectory(); + /// AccessTarget accessTarget = { ... } ; + /// Trajectory trajectory = accessTarget.getTrajectory(); /// @endcode /// /// @return The trajectory @@ -253,8 +109,8 @@ class GroundTargetConfiguration /// @brief Get the position /// /// @code{.cpp} - /// GroundTargetConfiguration groundTargetConfiguration = { ... } ; - /// Position position = groundTargetConfiguration.getPosition(); + /// AccessTarget accessTarget = { ... } ; + /// Position position = accessTarget.getPosition(); /// @endcode /// /// @return The position @@ -263,8 +119,8 @@ class GroundTargetConfiguration /// @brief Get the latitude, longitude, and altitude (LLA) /// /// @code{.cpp} - /// GroundTargetConfiguration groundTargetConfiguration = { ... } ; - /// LLA lla = groundTargetConfiguration.getLLA(); + /// AccessTarget accessTarget = { ... } ; + /// LLA lla = accessTarget.getLLA(); /// @endcode /// /// @param aCelestialSPtr A celestial body @@ -272,64 +128,12 @@ class GroundTargetConfiguration /// @return The latitude, longitude, and altitude (LLA) LLA getLLA(const Shared& aCelestialSPtr) const; - // /// @brief Get the azimuth interval - // /// - // /// @code{.cpp} - // /// GroundTargetConfiguration groundTargetConfiguration = { ... } ; - // /// Interval groundTargetConfiguration = generator.getAzimuthInterval(); - // /// @endcode - // /// - // /// @return The azimuth interval - // Interval getAzimuthInterval() const; - - // /// @brief Get the elevation interval - // /// - // /// @code{.cpp} - // /// GroundTargetConfiguration groundTargetConfiguration = { ... } ; - // /// Interval groundTargetConfiguration = generator.getElevationInterval(); - // /// @endcode - // /// - // /// @return The elevation interval - // Interval getElevationInterval() const; - - // /// @brief Get the range interval - // /// - // /// @code{.cpp} - // /// GroundTargetConfiguration groundTargetConfiguration = { ... } ; - // /// Interval groundTargetConfiguration = generator.getRangeInterval(); - // /// @endcode - // /// - // /// @return The range interval - // Interval getRangeInterval() const; - - // /// @brief Get the azimuth-elevation mask - // /// - // /// @code{.cpp} - // /// GroundTargetConfiguration groundTargetConfiguration = { ... } ; - // /// Map azimuthElevationMask = groundTargetConfiguration.getAzimuthElevationMask(); - // /// @endcode - // /// - // /// @return The azimuth-elevation mask - // Map getAzimuthElevationMask() const; - - /// @brief Check if the elevation-azimuth are within the mask - /// - /// @code{.cpp} - /// GroundTargetConfiguration groundTargetConfiguration = { ... } ; - /// bool isAboveMask = groundTargetConfiguration.isAboveMask(elevation, azimuth); - /// @endcode - /// - /// @param anAzimuth An azimuth [deg] - /// @param anElevation An elevation [deg] - /// @return True if the azimuth-elevation are within the mask, false otherwise - // bool isAboveMask(const Real& anAzimuth, const Real& anElevation) const; - /// @brief Get the rotation matrix (Matrix3d) from ECEF (Earth-Centered-Earth-Fixed) to SEZ (South-East-Zenith) /// frame /// /// @code{.cpp} - /// GroundTargetConfiguration groundTargetConfiguration = { ... } ; - /// Matrix3d sezRotation = groundTargetConfiguration.computeR_SEZ_ECEF(); + /// AccessTarget accessTarget = { ... } ; + /// Matrix3d sezRotation = accessTarget.computeR_SEZ_ECEF(); /// @endcode /// /// @return The SEZ rotation matrix @@ -338,7 +142,7 @@ class GroundTargetConfiguration /// @brief Construct a ground target configuration from an LLA (Latitude, Longitude, Altitude) /// /// @code{.cpp} - /// GroundTargetConfiguration groundTargetConfiguration = GroundTargetConfiguration::LLA( + /// AccessTarget accessTarget = AccessTarget::LLA( /// constraint, lla, aCelestialSPtr /// ); /// @endcode @@ -347,20 +151,126 @@ class GroundTargetConfiguration /// @param anLLA /// @param aCelestialSPtr /// @return Ground target configuration - static GroundTargetConfiguration FromLLA( + static AccessTarget FromLLA( const Constraint& constraint, const LLA& anLLA, const Shared& aCelestialSPtr ); - static GroundTargetConfiguration FromPosition(const Constraint& constraint, const Position& aPosition); + static AccessTarget FromPosition(const Constraint& constraint, const Position& aPosition); - static GroundTargetConfiguration FromTrajectory(const Constraint& constraint, const Trajectory& aTrajectory); + static AccessTarget FromTrajectory(const Constraint& constraint, const Trajectory& aTrajectory); private: Type type_; Constraint constraint_; Trajectory trajectory_; - GroundTargetConfiguration(const Type& aType, const Constraint& constrant, const Trajectory& aTrajectory); + AccessTarget(const Type& aType, const Constraint& constrant, const Trajectory& aTrajectory); +}; + +class Generator +{ + public: + Generator( + const Environment& anEnvironment, + const Duration& aStep = DEFAULT_STEP, + const Duration& aTolerance = DEFAULT_TOLERANCE, + const std::function& anAccessFilter = {}, + const std::function& aStateFilter = {} + ); + + bool isDefined() const; + + Duration getStep() const; + + Duration getTolerance() const; + + std::function getAccessFilter() const; + + std::function getStateFilter() const; + + std::function getConditionFunction( + const AccessTarget& anAccessTarget, const Trajectory& aToTrajectory + ) const; + + Array computeAccesses( + const physics::time::Interval& anInterval, const AccessTarget& anAccessTarget, const Trajectory& aToTrajectory + ) const; + + Array> computeAccesses( + const physics::time::Interval& anInterval, + const Array& someAccessTargets, + const Trajectory& aToTrajectory + ) const; + + void setStep(const Duration& aStep); + + void setTolerance(const Duration& aTolerance); + + void setAccessFilter(const std::function& anAccessFilter); + + void setStateFilter(const std::function& aStateFilter); + + static Generator Undefined(); + + private: + Environment environment_; + + Duration step_; + Duration tolerance_; + + std::function accessFilter_; + std::function stateFilter_; + + Array computeAccessesForTrajectoryTarget( + const physics::time::Interval& anInterval, const AccessTarget& anAccessTarget, const Trajectory& aToTrajectory + ) const; + + Array> computeAccessesForFixedTargets( + const physics::time::Interval& anInterval, + const Array& someAccessTargets, + const Trajectory& aToTrajectory, + const bool& coarse = false + ) const; + + Array generateAccessesFromIntervals( + const Array& someIntervals, + const physics::time::Interval& anInterval, + const Trajectory& aFromTrajectory, + const Trajectory& aToTrajectory + ) const; + + Array computePreciseCrossings( + const Array& accessIntervals, + const physics::time::Interval& anAnalysisInterval, + const Vector3d& fromPositionCoordinate_ITRF, + const Trajectory& aToTrajectory, + const AccessTarget& anAccessTarget + ) const; + + static Array ComputeIntervals(const VectorXi& inAccess, const Array& instants); + + static Access GenerateAccess( + const physics::time::Interval& anAccessInterval, + const physics::time::Interval& aGlobalInterval, + const Trajectory& aFromTrajectory, + const Trajectory& aToTrajectory, + const Shared anEarthSPtr, + const Duration& aTolerance + ); + + static Instant FindTimeOfClosestApproach( + const physics::time::Interval& anAccessInterval, + const Trajectory& aFromTrajectory, + const Trajectory& aToTrajectory, + const Duration& aTolerance + ); + + static Angle CalculateElevationAt( + const Instant& anInstant, + const Trajectory& aFromTrajectory, + const Trajectory& aToTrajectory, + const Shared anEarthSPtr + ); }; class GeneratorContext @@ -373,7 +283,7 @@ class GeneratorContext const Generator& aGenerator ); - bool isAccessActive(const Instant& anInstant); + bool isAccessActive(const Instant& anInstant, const Constraint& aConstraint); static Pair GetStatesAt( const Instant& anInstant, const Trajectory& aFromTrajectory, const Trajectory& aToTrajectory diff --git a/src/OpenSpaceToolkit/Astrodynamics/Access/Constraint.cpp b/src/OpenSpaceToolkit/Astrodynamics/Access/Constraint.cpp index 03287be30..247524105 100644 --- a/src/OpenSpaceToolkit/Astrodynamics/Access/Constraint.cpp +++ b/src/OpenSpaceToolkit/Astrodynamics/Access/Constraint.cpp @@ -166,6 +166,7 @@ std::optional Constraint::getIntervalConstraint( { return *interval; } + return std::nullopt; } @@ -175,6 +176,7 @@ std::optional Constraint::getMaskConstraint() const { return *mask; } + return std::nullopt; } @@ -189,13 +191,13 @@ Interval Constraint::getRangeInterval() const ); } -Constraint::Constraint(IntervalConstraint constraint) - : constraint_(std::move(constraint)) +Constraint::Constraint(const IntervalConstraint& constraint) + : constraint_(constraint) { } -Constraint::Constraint(MaskConstraint constraint) - : constraint_(std::move(constraint)) +Constraint::Constraint(const MaskConstraint& constraint) + : constraint_(constraint) { } diff --git a/src/OpenSpaceToolkit/Astrodynamics/Access/Generator.cpp b/src/OpenSpaceToolkit/Astrodynamics/Access/Generator.cpp index ba2284d89..919d519c4 100644 --- a/src/OpenSpaceToolkit/Astrodynamics/Access/Generator.cpp +++ b/src/OpenSpaceToolkit/Astrodynamics/Access/Generator.cpp @@ -44,22 +44,22 @@ namespace astrodynamics namespace access { -GroundTargetConfiguration::Type GroundTargetConfiguration::getType() const +AccessTarget::Type AccessTarget::getType() const { return type_; } -Constraint GroundTargetConfiguration::getConstraint() const +Constraint AccessTarget::getConstraint() const { return constraint_; } -Trajectory GroundTargetConfiguration::getTrajectory() const +Trajectory AccessTarget::getTrajectory() const { return trajectory_; } -Position GroundTargetConfiguration::getPosition() const +Position AccessTarget::getPosition() const { if (type_ != Type::Fixed) { @@ -69,39 +69,14 @@ Position GroundTargetConfiguration::getPosition() const return trajectory_.getStateAt(Instant::J2000()).inFrame(Frame::ITRF()).getPosition(); } -LLA GroundTargetConfiguration::getLLA(const Shared& aCelestialSPtr) const +LLA AccessTarget::getLLA(const Shared& aCelestialSPtr) const { return LLA::Cartesian( getPosition().accessCoordinates(), aCelestialSPtr->getEquatorialRadius(), aCelestialSPtr->getFlattening() ); } -// Interval GroundTargetConfiguration::getAzimuthInterval() const -// { -// return azimuthInterval_; -// } - -// Interval GroundTargetConfiguration::getElevationInterval() const -// { -// return elevationInterval_; -// } - -// Interval GroundTargetConfiguration::getRangeInterval() const -// { -// return rangeInterval_; -// } - -// Map GroundTargetConfiguration::getAzimuthElevationMask() const -// { -// return azimuthElevationMask_; -// } - -// bool GroundTargetConfiguration::isAboveMask(const Real& anAzimuth_rad, const Real& anElevation_rad) const -// { -// return Generator::IsAboveMask(azimuthElevationMask_, anAzimuth_rad, anElevation_rad); -// } - -Matrix3d GroundTargetConfiguration::computeR_SEZ_ECEF(const Shared& aCelestialSPtr) const +Matrix3d AccessTarget::computeR_SEZ_ECEF(const Shared& aCelestialSPtr) const { const LLA lla = this->getLLA(aCelestialSPtr); @@ -118,12 +93,12 @@ Matrix3d GroundTargetConfiguration::computeR_SEZ_ECEF(const Shared& aCelestialSPtr ) { - return GroundTargetConfiguration( - GroundTargetConfiguration::Type::Fixed, + return AccessTarget( + AccessTarget::Type::Fixed, constraint, Trajectory::Position(Position::Meters( anLLA.toCartesian(aCelestialSPtr->getEquatorialRadius(), aCelestialSPtr->getFlattening()), Frame::ITRF() @@ -131,24 +106,18 @@ GroundTargetConfiguration GroundTargetConfiguration::FromLLA( ); } -GroundTargetConfiguration GroundTargetConfiguration::FromPosition( - const Constraint& constraint, const Position& aPosition -) +AccessTarget AccessTarget::FromPosition(const Constraint& constraint, const Position& aPosition) { - return GroundTargetConfiguration( - GroundTargetConfiguration::Type::Fixed, constraint, Trajectory::Position(aPosition) - ); + return AccessTarget(AccessTarget::Type::Fixed, constraint, Trajectory::Position(aPosition)); } -GroundTargetConfiguration GroundTargetConfiguration::FromTrajectory( - const Constraint& constraint, const Trajectory& aTrajectory -) +AccessTarget AccessTarget::FromTrajectory(const Constraint& constraint, const Trajectory& aTrajectory) { - return GroundTargetConfiguration(GroundTargetConfiguration::Type::Trajectory, constraint, aTrajectory); + return AccessTarget(AccessTarget::Type::Trajectory, constraint, aTrajectory); } -GroundTargetConfiguration::GroundTargetConfiguration( - const GroundTargetConfiguration::Type& aType, const Constraint& aConstraint, const Trajectory& aTrajectory +AccessTarget::AccessTarget( + const AccessTarget::Type& aType, const Constraint& aConstraint, const Trajectory& aTrajectory ) : type_(aType), constraint_(aConstraint), @@ -160,68 +129,16 @@ GroundTargetConfiguration::GroundTargetConfiguration( } } -// void GroundTargetConfiguration::validateIntervals_() const -// { -// if (!azimuthInterval_.isDefined() || !elevationInterval_.isDefined() || !rangeInterval_.isDefined()) -// { -// throw ostk::core::error::runtime::Undefined("Interval"); -// } - -// if (azimuthInterval_.getLowerBound() < 0.0 || azimuthInterval_.getUpperBound() > 360.0) -// { -// throw ostk::core::error::RuntimeError( -// "The azimuth interval [{}, {}] must be in the range [0, 360] deg", -// azimuthInterval_.accessLowerBound(), -// azimuthInterval_.accessUpperBound() -// ); -// } - -// if (elevationInterval_.getLowerBound() < -90.0 || elevationInterval_.getUpperBound() > 90.0) -// { -// throw ostk::core::error::RuntimeError( -// "The elevation interval [{}, {}] must be in the range [-90, 90] deg.", -// elevationInterval_.accessLowerBound(), -// elevationInterval_.accessUpperBound() -// ); -// } - -// if (rangeInterval_.getLowerBound() < 0.0) -// { -// throw ostk::core::error::RuntimeError( -// "The range interval [{}, {}] must be positive.", -// rangeInterval_.accessLowerBound(), -// rangeInterval_.accessUpperBound() -// ); -// } -// } - -// void GroundTargetConfiguration::validateMask_() -// { -// azimuthElevationMask_ = Generator::ConvertAzimuthElevationMask(azimuthElevationMask_); -// } - -Generator::Generator(const Environment& anEnvironment, const Duration& aStep, const Duration& aTolerance) - : environment_(anEnvironment), - step_(aStep), - tolerance_(aTolerance), - aerFilter_({}), - accessFilter_({}), - stateFilter_({}) -{ -} - Generator::Generator( const Environment& anEnvironment, - const std::function& anAerFilter, - const std::function& anAccessFilter, - const std::function& aStateFilter, const Duration& aStep, - const Duration& aTolerance + const Duration& aTolerance, + const std::function& anAccessFilter, + const std::function& aStateFilter ) : environment_(anEnvironment), step_(aStep), tolerance_(aTolerance), - aerFilter_(anAerFilter), accessFilter_(anAccessFilter), stateFilter_(aStateFilter) { @@ -252,16 +169,6 @@ Duration Generator::getTolerance() const return this->tolerance_; } -std::function Generator::getAerFilter() const -{ - if (!this->isDefined()) - { - throw ostk::core::error::runtime::Undefined("Generator"); - } - - return this->aerFilter_; -} - std::function Generator::getAccessFilter() const { if (!this->isDefined()) @@ -283,9 +190,11 @@ std::function Generator::getStateFilter() cons } std::function Generator::getConditionFunction( - const Trajectory& aFromTrajectory, const Trajectory& aToTrajectory + const AccessTarget& anAccessTarget, const Trajectory& aToTrajectory ) const { + const Trajectory aFromTrajectory = anAccessTarget.getTrajectory(); + if (!aFromTrajectory.isDefined()) { throw ostk::core::error::runtime::Undefined("From Trajectory"); @@ -301,16 +210,39 @@ std::function Generator::getConditionFunction( throw ostk::core::error::runtime::Undefined("Generator"); } + const Constraint constraint = anAccessTarget.getConstraint(); + GeneratorContext generatorContext = GeneratorContext(aFromTrajectory, aToTrajectory, environment_, *this); - return [generatorContext](const Instant& anInstant) mutable -> bool + return [generatorContext, constraint](const Instant& anInstant) mutable -> bool { - return generatorContext.isAccessActive(anInstant); + return generatorContext.isAccessActive(anInstant, constraint); }; } Array Generator::computeAccesses( - const physics::time::Interval& anInterval, const Trajectory& aFromTrajectory, const Trajectory& aToTrajectory + const physics::time::Interval& anInterval, const AccessTarget& anAccessTarget, const Trajectory& aToTrajectory +) const +{ + if (!anInterval.isDefined()) + { + throw ostk::core::error::runtime::Undefined("Interval"); + } + + if (anAccessTarget.getType() == AccessTarget::Type::Trajectory) + { + return this->computeAccessesForTrajectoryTarget(anInterval, anAccessTarget, aToTrajectory); + } + else + { + return this->computeAccessesForFixedTargets(anInterval, Array {anAccessTarget}, aToTrajectory)[0]; + } +} + +Array> Generator::computeAccesses( + const physics::time::Interval& anInterval, + const Array& someAccessTargets, + const Trajectory& aToTrajectory ) const { if (!anInterval.isDefined()) @@ -318,32 +250,119 @@ Array Generator::computeAccesses( throw ostk::core::error::runtime::Undefined("Interval"); } + if (someAccessTargets.isEmpty()) + { + throw ostk::core::error::runtime::Undefined("Access targets"); + } + + if (std ::all_of( + someAccessTargets.begin(), + someAccessTargets.end(), + [](const auto& accessTarget) + { + return accessTarget.getType() == AccessTarget::Type::Trajectory; + } + )) + { + Array> accessesPerTarget; + accessesPerTarget.reserve(someAccessTargets.getSize()); + + for (const auto& groundTargetConfiguration : someAccessTargets) + { + accessesPerTarget.add( + this->computeAccessesForTrajectoryTarget(anInterval, groundTargetConfiguration, aToTrajectory) + ); + } + + return accessesPerTarget; + } + else if (std::all_of( + someAccessTargets.begin(), + someAccessTargets.end(), + [](const auto& accessTarget) + { + return accessTarget.getType() == AccessTarget::Type::Fixed; + } + )) + { + return this->computeAccessesForFixedTargets(anInterval, someAccessTargets, aToTrajectory); + } + else + { + throw ostk::core::error::RuntimeError("All targets must be of same type."); + } +} + +void Generator::setStep(const Duration& aStep) +{ + if (!aStep.isDefined()) + { + throw ostk::core::error::runtime::Undefined("Step"); + } + + this->step_ = aStep; +} + +void Generator::setTolerance(const Duration& aTolerance) +{ + if (!aTolerance.isDefined()) + { + throw ostk::core::error::runtime::Undefined("Tolerance"); + } + + this->tolerance_ = aTolerance; +} + +void Generator::setAccessFilter(const std::function& anAccessFilter) +{ + this->accessFilter_ = anAccessFilter; +} + +void Generator::setStateFilter(const std::function& aStateFilter) +{ + this->stateFilter_ = aStateFilter; +} + +Generator Generator::Undefined() +{ + return {Environment::Undefined(), Duration::Undefined(), Duration::Undefined()}; +} + +Array Generator::computeAccessesForTrajectoryTarget( + const physics::time::Interval& anInterval, const AccessTarget& anAccessTarget, const Trajectory& aToTrajectory +) const +{ const TemporalConditionSolver temporalConditionSolver = {this->step_, this->tolerance_}; const Array accessIntervals = - temporalConditionSolver.solve(this->getConditionFunction(aFromTrajectory, aToTrajectory), anInterval); + temporalConditionSolver.solve(this->getConditionFunction(anAccessTarget, aToTrajectory), anInterval); - const Shared earthSPtr = this->environment_.accessCelestialObjectWithName("Earth"); + const Trajectory aFromTrajectory = anAccessTarget.getTrajectory(); return generateAccessesFromIntervals(accessIntervals, anInterval, aFromTrajectory, aToTrajectory); } -Array> Generator::computeAccessesWithGroundTargets( +Array> Generator::computeAccessesForFixedTargets( const physics::time::Interval& anInterval, - const Array& someGroundTargetConfigurations, + const Array& someAccessTargets, const Trajectory& aToTrajectory, const bool& coarse ) const { + if (stateFilter_) + { + throw ostk::core::error::RuntimeError("State filter is not supported for multiple ground targets."); + } + // create a stacked matrix of SEZ rotations for all ground targets - const Index targetCount = someGroundTargetConfigurations.getSize(); + const Index targetCount = someAccessTargets.getSize(); Eigen::Matrix SEZRotations(3, 3 * targetCount); const Shared earthSPtr = this->environment_.accessCelestialObjectWithName("Earth"); for (Index i = 0; i < targetCount; ++i) { - SEZRotations.block<3, 3>(0, 3 * i) = someGroundTargetConfigurations[i].computeR_SEZ_ECEF(earthSPtr); + SEZRotations.block<3, 3>(0, 3 * i) = someAccessTargets[i].computeR_SEZ_ECEF(earthSPtr); } // create a stacked matrix of ITRF positions for all ground targets @@ -351,21 +370,21 @@ Array> Generator::computeAccessesWithGroundTargets( for (Index i = 0; i < targetCount; ++i) { - fromPositionCoordinates_ITRF.col(i) = someGroundTargetConfigurations[i].getPosition().getCoordinates(); + fromPositionCoordinates_ITRF.col(i) = someAccessTargets[i].getPosition().getCoordinates(); } - const bool allGroundTargetsHaveMasks = std::all_of( - someGroundTargetConfigurations.begin(), - someGroundTargetConfigurations.end(), - [](const auto& groundTargetConfiguration) + const bool allAccessTargetsHaveMasks = std::all_of( + someAccessTargets.begin(), + someAccessTargets.end(), + [](const auto& accessTarget) { - return groundTargetConfiguration.getConstraint().isMaskBased(); + return accessTarget.getConstraint().isMaskBased(); } ); std::function aerFilter; - if (!allGroundTargetsHaveMasks) + if (!allAccessTargetsHaveMasks) { // create a stacked matrix of azimuth, elevation, and range bounds for all ground targets MatrixXd aerLowerBounds = MatrixXd::Zero(targetCount, 3); @@ -376,7 +395,7 @@ Array> Generator::computeAccessesWithGroundTargets( for (Index i = 0; i < targetCount; ++i) { const Constraint::IntervalConstraint intervalConstraint = - someGroundTargetConfigurations[i].getConstraint().getIntervalConstraint().value(); + someAccessTargets[i].getConstraint().getIntervalConstraint().value(); aerLowerBounds(i, 0) = intervalConstraint.azimuth.accessLowerBound() * degToRad; aerLowerBounds(i, 1) = intervalConstraint.elevation.accessLowerBound() * degToRad; @@ -406,23 +425,23 @@ Array> Generator::computeAccessesWithGroundTargets( for (Index i = 0; i < targetCount; ++i) { const Constraint::MaskConstraint constraint = - someGroundTargetConfigurations[i].getConstraint().getMaskConstraint().value(); + someAccessTargets[i].getConstraint().getMaskConstraint().value(); rangeLowerBounds(i) = constraint.range.accessLowerBound(); rangeUpperBounds(i) = constraint.range.accessUpperBound(); } - aerFilter = [&someGroundTargetConfigurations, rangeLowerBounds, rangeUpperBounds]( + aerFilter = [&someAccessTargets, rangeLowerBounds, rangeUpperBounds]( const VectorXd& azimuths_rad, const VectorXd& elevations_rad, const VectorXd& ranges_m ) { ArrayXb mask(azimuths_rad.rows()); mask = ranges_m.array() > rangeLowerBounds.array() && ranges_m.array() < rangeUpperBounds.array(); - for (Index i = 0; i < (Index)mask.rows(); ++i) + for (Eigen::Index i = 0; i < mask.rows(); ++i) { const Constraint::MaskConstraint constraint = - someGroundTargetConfigurations[i].getConstraint().getMaskConstraint().value(); + someAccessTargets[i].getConstraint().getMaskConstraint().value(); const double& azimuth_rad = azimuths_rad(i); const double& elevation_rad = elevations_rad(i); @@ -453,7 +472,7 @@ Array> Generator::computeAccessesWithGroundTargets( // calculate target to satellite vector in SEZ MatrixXd dx_SEZ = MatrixXd::Zero(3, dx.cols()); - for (Index i = 0; i < (Index)dx.cols(); ++i) + for (Eigen::Index i = 0; i < dx.cols(); ++i) { dx_SEZ.col(i) = SEZRotations.block<3, 3>(0, 3 * i) * dx.col(i); } @@ -498,7 +517,7 @@ Array> Generator::computeAccessesWithGroundTargets( anInterval, fromPositionCoordinates_ITRF.col(i), aToTrajectory, - someGroundTargetConfigurations[i] + someAccessTargets[i] ); } } @@ -506,7 +525,7 @@ Array> Generator::computeAccessesWithGroundTargets( Array> accesses = Array>(targetCount, Array::Empty()); for (Index i = 0; i < accessIntervalsPerTarget.getSize(); ++i) { - const Trajectory fromTrajectory = someGroundTargetConfigurations[i].getTrajectory(); + const Trajectory fromTrajectory = someAccessTargets[i].getTrajectory(); accesses[i] = this->generateAccessesFromIntervals(accessIntervalsPerTarget[i], anInterval, fromTrajectory, aToTrajectory); } @@ -514,89 +533,6 @@ Array> Generator::computeAccessesWithGroundTargets( return accesses; } -void Generator::setStep(const Duration& aStep) -{ - if (!aStep.isDefined()) - { - throw ostk::core::error::runtime::Undefined("Step"); - } - - this->step_ = aStep; -} - -void Generator::setTolerance(const Duration& aTolerance) -{ - if (!aTolerance.isDefined()) - { - throw ostk::core::error::runtime::Undefined("Tolerance"); - } - - this->tolerance_ = aTolerance; -} - -void Generator::setAerFilter(const std::function& anAerFilter) -{ - this->aerFilter_ = anAerFilter; -} - -void Generator::setAccessFilter(const std::function& anAccessFilter) -{ - this->accessFilter_ = anAccessFilter; -} - -void Generator::setStateFilter(const std::function& aStateFilter) -{ - this->stateFilter_ = aStateFilter; -} - -Generator Generator::Undefined() -{ - return {Environment::Undefined()}; -} - -Generator Generator::AerRanges( - const Interval& anAzimuthRange, - const Interval& anElevationRange, - const Interval& aRangeRange, - const Environment& anEnvironment -) -{ - const Interval azimuthRange_deg = anAzimuthRange; - const Interval elevationRange_deg = anElevationRange; - const Interval rangeRange_m = aRangeRange; - - const std::function aerFilter = - [azimuthRange_deg, elevationRange_deg, rangeRange_m](const AER& anAER) -> bool - { - return ((!azimuthRange_deg.isDefined()) || azimuthRange_deg.contains(anAER.getAzimuth().inDegrees(0.0, +360.0)) - ) && - ((!elevationRange_deg.isDefined()) || - elevationRange_deg.contains(anAER.getElevation().inDegrees(-180.0, +180.0))) && - ((!rangeRange_m.isDefined()) || rangeRange_m.contains(anAER.getRange().inMeters())); - }; - - return {anEnvironment, aerFilter}; -} - -Generator Generator::AerMask( - const Map& anAzimuthElevationMask, const Interval& aRangeRange, const Environment& anEnvironment -) -{ - const Map anAzimuthElevationMask_rad = ConvertAzimuthElevationMask(anAzimuthElevationMask); - - const std::function aerFilter = [anAzimuthElevationMask_rad, - aRangeRange](const AER& anAER) -> bool - { - const Real azimuth = anAER.getAzimuth().inRadians(0.0, Real::TwoPi()); - const Real elevation = anAER.getElevation().inRadians(-Real::Pi(), Real::Pi()); - - return Generator::IsAboveMask(anAzimuthElevationMask_rad, azimuth, elevation) && - ((!aRangeRange.isDefined()) || aRangeRange.contains(anAER.getRange().inMeters())); - }; - - return {anEnvironment, aerFilter}; -} - Array Generator::generateAccessesFromIntervals( const Array& someIntervals, const physics::time::Interval& anInterval, @@ -630,14 +566,14 @@ Array Generator::computePreciseCrossings( const physics::time::Interval& anAnalysisInterval, const Vector3d& fromPositionCoordinate_ITRF, const Trajectory& aToTrajectory, - const GroundTargetConfiguration& aGroundTargetConfiguration + const AccessTarget& anAccessTarget ) const { const RootSolver rootSolver = RootSolver(100, this->tolerance_.inSeconds()); const Shared earthSPtr = this->environment_.accessCelestialObjectWithName("Earth"); - const Matrix3d SEZRotation = aGroundTargetConfiguration.computeR_SEZ_ECEF(earthSPtr); + const Matrix3d SEZRotation = anAccessTarget.computeR_SEZ_ECEF(earthSPtr); std::function condition; @@ -659,10 +595,10 @@ Array Generator::computePreciseCrossings( return {azimuth_rad, elevation_rad, range_m}; }; - if (aGroundTargetConfiguration.getConstraint().isIntervalBased()) + if (anAccessTarget.getConstraint().isIntervalBased()) { const Constraint::IntervalConstraint intervalConstraint = - aGroundTargetConfiguration.getConstraint().getIntervalConstraint().value(); + anAccessTarget.getConstraint().getIntervalConstraint().value(); condition = [&computeAER, intervalConstraint](const Instant& instant) -> bool { @@ -682,8 +618,7 @@ Array Generator::computePreciseCrossings( } else { - const Constraint::MaskConstraint maskConstraint = - aGroundTargetConfiguration.getConstraint().getMaskConstraint().value(); + const Constraint::MaskConstraint maskConstraint = anAccessTarget.getConstraint().getMaskConstraint().value(); condition = [&computeAER, maskConstraint](const Instant& instant) -> bool { @@ -761,7 +696,7 @@ Array Generator::ComputeIntervals(const VectorXi& inAcc Instant startInstant = Instant::Undefined(); Instant endInstant = Instant::Undefined(); - for (Index j = 0; j < (Index)diff.size() - 1; ++j) + for (Eigen::Index j = 0; j < diff.size() - 1; ++j) { if (!diff[j]) { @@ -933,66 +868,6 @@ Angle Generator::CalculateElevationAt( return aer.getElevation(); } -bool Generator::IsAboveMask( - const Map& anAzimuthElevationMask, const Real& anAzimuth_rad, const Real& anElevation_rad -) -{ - auto itLow = anAzimuthElevationMask.lower_bound(anAzimuth_rad); - itLow--; - auto itUp = anAzimuthElevationMask.upper_bound(anAzimuth_rad); - - // Vector between the two successive mask data points with bounding azimuth values - - const Vector2d lowToUpVector = {itUp->first - itLow->first, itUp->second - itLow->second}; - - // Vector from data point with azimuth lower bound to tested point - - const Vector2d lowToPointVector = {anAzimuth_rad - itLow->first, anElevation_rad - itLow->second}; - - // If the determinant of these two vectors is positive, the tested point lies above the function defined by the - // mask - - return ((lowToUpVector[0] * lowToPointVector[1] - lowToUpVector[1] * lowToPointVector[0]) >= 0.0); -} - -Map Generator::ConvertAzimuthElevationMask(const Map& anAzimuthElevationMask) -{ - if ((anAzimuthElevationMask.empty()) || (anAzimuthElevationMask.begin()->first < 0.0) || - (anAzimuthElevationMask.rbegin()->first > 360.0)) - { - throw ostk::core::error::runtime::Wrong("Azimuth-Elevation Mask"); - } - - for (const auto& azimuthElevationPair : anAzimuthElevationMask) - { - if ((azimuthElevationPair.second).abs() > 90.0) - { - throw ostk::core::error::runtime::Wrong("Azimuth-Elevation Mask"); - } - } - - Map anAzimuthElevationMask_deg = anAzimuthElevationMask; - - if (anAzimuthElevationMask_deg.begin()->first != 0.0) - { - anAzimuthElevationMask_deg.insert({0.0, anAzimuthElevationMask_deg.begin()->second}); - } - - if (anAzimuthElevationMask_deg.rbegin()->first != 360.0) - { - anAzimuthElevationMask_deg.insert({360.0, anAzimuthElevationMask_deg.begin()->second}); - } - - Map anAzimuthElevationMask_rad; - - for (auto it = anAzimuthElevationMask_deg.begin(); it != anAzimuthElevationMask_deg.end(); ++it) - { - anAzimuthElevationMask_rad.insert({it->first * M_PI / 180.0, it->second * M_PI / 180.0}); - } - - return anAzimuthElevationMask_rad; -} - GeneratorContext::GeneratorContext( const Trajectory& aFromTrajectory, const Trajectory& aToTrajectory, @@ -1007,7 +882,7 @@ GeneratorContext::GeneratorContext( { } -bool GeneratorContext::isAccessActive(const Instant& anInstant) +bool GeneratorContext::isAccessActive(const Instant& anInstant, const Constraint& aConstraint) { this->environment_.setInstant(anInstant); @@ -1043,19 +918,9 @@ bool GeneratorContext::isAccessActive(const Instant& anInstant) } } - // AER filtering - - if (this->generator_.getAerFilter()) - { - const AER aer = GeneratorContext::CalculateAer(anInstant, fromPosition, toPosition, this->earthSPtr_); - - if (!this->generator_.getAerFilter()(aer)) - { - return false; - } - } + const AER aer = GeneratorContext::CalculateAer(anInstant, fromPosition, toPosition, earthSPtr_); - return true; + return aConstraint.isSatisfied(aer); } Pair GeneratorContext::GetStatesAt( diff --git a/test/OpenSpaceToolkit/Astrodynamics/Access/Generator.test.cpp b/test/OpenSpaceToolkit/Astrodynamics/Access/Generator.test.cpp index eeccef096..0ca62f862 100644 --- a/test/OpenSpaceToolkit/Astrodynamics/Access/Generator.test.cpp +++ b/test/OpenSpaceToolkit/Astrodynamics/Access/Generator.test.cpp @@ -31,6 +31,7 @@ using ostk::core::filesystem::File; using ostk::core::filesystem::Path; using ostk::core::type::Index; using ostk::core::type::Real; +using ostk::core::type::Shared; using ostk::core::type::String; using ostk::mathematics::object::Matrix3d; @@ -43,6 +44,7 @@ using ostk::physics::coordinate::spherical::LLA; using ostk::physics::coordinate::Velocity; using ostk::physics::Environment; using ostk::physics::environment::gravitational::Earth; +using ostk::physics::environment::object::Celestial; using ostk::physics::time::DateTime; using ostk::physics::time::Duration; using ostk::physics::time::Instant; @@ -53,8 +55,9 @@ using ostk::physics::unit::Derived; using ostk::physics::unit::Length; using ostk::astrodynamics::Access; +using ostk::astrodynamics::access::AccessTarget; +using ostk::astrodynamics::access::Constraint; using ostk::astrodynamics::access::Generator; -using ostk::astrodynamics::access::GroundTargetConfiguration; using ostk::astrodynamics::Trajectory; using ostk::astrodynamics::trajectory::Orbit; using ostk::astrodynamics::trajectory::orbit::model::Kepler; @@ -63,38 +66,32 @@ using ostk::astrodynamics::trajectory::orbit::model::SGP4; using ostk::astrodynamics::trajectory::orbit::model::sgp4::TLE; using ostk::astrodynamics::trajectory::State; -TEST(OpenSpaceToolkit_Astrodynamics_Access_Generator, Constructor) +class OpenSpaceToolkit_Astrodynamics_Access_Generator : public ::testing::Test +{ + protected: + Environment defaultEnvironment_ = Environment::Default(); + const Shared defaultEarthSPtr_ = defaultEnvironment_.accessCelestialObjectWithName("Earth"); + Duration defaultStep_ = Duration::Minutes(1.0); + Duration defaultTolerance_ = Duration::Microseconds(1.0); + Generator defaultGenerator_ = {this->defaultEnvironment_, this->defaultStep_, this->defaultTolerance_}; +}; + +TEST_F(OpenSpaceToolkit_Astrodynamics_Access_Generator, Constructor) { { - const Environment environment = Environment::Default(); - - EXPECT_NO_THROW(Generator generator(environment);); + EXPECT_NO_THROW(Generator generator(defaultEnvironment_);); } { - const Environment environment = Environment::Default(); - - const auto aerFilter = [](const AER&) -> bool - { - return true; - }; - const auto accessFilter = [](const Access&) -> bool { return true; }; - EXPECT_NO_THROW(Generator generator(environment, aerFilter, accessFilter);); + EXPECT_NO_THROW(Generator generator(defaultEnvironment_, defaultStep_, defaultTolerance_, accessFilter);); } { - const Environment environment = Environment::Default(); - - const auto aerFilter = [](const AER&) -> bool - { - return true; - }; - const auto accessFilter = [](const Access&) -> bool { return true; @@ -105,25 +102,23 @@ TEST(OpenSpaceToolkit_Astrodynamics_Access_Generator, Constructor) return true; }; - EXPECT_NO_THROW(Generator generator(environment, aerFilter, accessFilter, stateFilter);); + EXPECT_NO_THROW( + Generator generator(defaultEnvironment_, defaultStep_, defaultTolerance_, accessFilter, stateFilter); + ); } } -TEST(OpenSpaceToolkit_Astrodynamics_Access_Generator, IsDefined) +TEST_F(OpenSpaceToolkit_Astrodynamics_Access_Generator, IsDefined) { { - const Environment environment = Environment::Default(); - - const Generator generator = {environment}; - - EXPECT_TRUE(generator.isDefined()); + EXPECT_TRUE(defaultGenerator_.isDefined()); } { - const Environment environment = Environment::Default(); - - const auto aerFilter = [](const AER&) -> bool + const auto stateFilter = [](const State& aFirstState, const State& aSecondState) -> bool { + (void)aFirstState; + (void)aSecondState; return true; }; @@ -132,7 +127,7 @@ TEST(OpenSpaceToolkit_Astrodynamics_Access_Generator, IsDefined) return true; }; - const Generator generator = {environment, aerFilter, accessFilter}; + const Generator generator = {defaultEnvironment_, defaultStep_, defaultTolerance_, accessFilter, stateFilter}; EXPECT_TRUE(generator.isDefined()); } @@ -142,12 +137,10 @@ TEST(OpenSpaceToolkit_Astrodynamics_Access_Generator, IsDefined) } } -TEST(OpenSpaceToolkit_Astrodynamics_Access_Generator, GetStep) +TEST_F(OpenSpaceToolkit_Astrodynamics_Access_Generator, GetStep) { { - const Generator generator = {Environment::Default()}; - - EXPECT_EQ(Duration::Minutes(1.0), generator.getStep()); + EXPECT_EQ(defaultStep_, defaultGenerator_.getStep()); } { @@ -155,12 +148,10 @@ TEST(OpenSpaceToolkit_Astrodynamics_Access_Generator, GetStep) } } -TEST(OpenSpaceToolkit_Astrodynamics_Access_Generator, GetTolerance) +TEST_F(OpenSpaceToolkit_Astrodynamics_Access_Generator, GetTolerance) { { - const Generator generator = {Environment::Default()}; - - EXPECT_EQ(Duration::Microseconds(1.0), generator.getTolerance()); + EXPECT_EQ(defaultTolerance_, defaultGenerator_.getTolerance()); } { @@ -168,25 +159,10 @@ TEST(OpenSpaceToolkit_Astrodynamics_Access_Generator, GetTolerance) } } -TEST(OpenSpaceToolkit_Astrodynamics_Access_Generator, GetAerFilter) +TEST_F(OpenSpaceToolkit_Astrodynamics_Access_Generator, GetAccessFilter) { { - const Generator generator = {Environment::Default()}; - - EXPECT_EQ(nullptr, generator.getAerFilter()); - } - - { - EXPECT_ANY_THROW(Generator::Undefined().getAerFilter()); - } -} - -TEST(OpenSpaceToolkit_Astrodynamics_Access_Generator, GetAccessFilter) -{ - { - const Generator generator = {Environment::Default()}; - - EXPECT_EQ(nullptr, generator.getAccessFilter()); + EXPECT_EQ(nullptr, defaultGenerator_.getAccessFilter()); } { @@ -194,12 +170,10 @@ TEST(OpenSpaceToolkit_Astrodynamics_Access_Generator, GetAccessFilter) } } -TEST(OpenSpaceToolkit_Astrodynamics_Access_Generator, GetStateFilter) +TEST_F(OpenSpaceToolkit_Astrodynamics_Access_Generator, GetStateFilter) { { - const Generator generator = {Environment::Default()}; - - EXPECT_EQ(nullptr, generator.getStateFilter()); + EXPECT_EQ(nullptr, defaultGenerator_.getStateFilter()); } { @@ -207,13 +181,11 @@ TEST(OpenSpaceToolkit_Astrodynamics_Access_Generator, GetStateFilter) } } -TEST(OpenSpaceToolkit_Astrodynamics_Access_Generator, GetConditionFunction) +TEST_F(OpenSpaceToolkit_Astrodynamics_Access_Generator, GetConditionFunction) { - const Environment environment = Environment::Default(); - const Instant epoch = Instant::DateTime(DateTime(2018, 1, 1, 0, 0, 0), Scale::UTC); - const auto generateFirstOrbit = [&environment, &epoch]() -> Orbit + const auto generateFirstOrbit = [this, &epoch]() -> Orbit { const Length semiMajorAxis = Length::Kilometers(7000.0); const Real eccentricity = 0.0; @@ -233,12 +205,12 @@ TEST(OpenSpaceToolkit_Astrodynamics_Access_Generator, GetConditionFunction) coe, epoch, gravitationalParameter, equatorialRadius, J2, J4, Kepler::PerturbationType::None }; - const Orbit orbit = {keplerianModel, environment.accessCelestialObjectWithName("Earth")}; + const Orbit orbit = {keplerianModel, defaultEnvironment_.accessCelestialObjectWithName("Earth")}; return orbit; }; - const auto generateSecondOrbit = [&environment, &epoch]() -> Orbit + const auto generateSecondOrbit = [this, &epoch]() -> Orbit { const Length semiMajorAxis = Length::Kilometers(7000.0); const Real eccentricity = 0.0; @@ -258,7 +230,7 @@ TEST(OpenSpaceToolkit_Astrodynamics_Access_Generator, GetConditionFunction) coe, epoch, gravitationalParameter, equatorialRadius, J2, J4, Kepler::PerturbationType::None }; - const Orbit orbit = {keplerianModel, environment.accessCelestialObjectWithName("Earth")}; + const Orbit orbit = {keplerianModel, defaultEnvironment_.accessCelestialObjectWithName("Earth")}; return orbit; }; @@ -266,32 +238,33 @@ TEST(OpenSpaceToolkit_Astrodynamics_Access_Generator, GetConditionFunction) const Orbit fromOrbit = generateFirstOrbit(); const Orbit toOrbit = generateSecondOrbit(); - { - const Generator generator = {environment}; + const Constraint constraint = Constraint::FromIntervals( + ostk::mathematics::object::Interval::Closed(0.0, 360.0), + ostk::mathematics::object::Interval::Closed(-90.0, 90.0), + ostk::mathematics::object::Interval::Closed(0.0, 1.0e10) + ); + const AccessTarget accessTarget = AccessTarget::FromTrajectory(constraint, fromOrbit); - const auto conditionFunction = generator.getConditionFunction(fromOrbit, toOrbit); + { + const auto conditionFunction = defaultGenerator_.getConditionFunction(accessTarget, toOrbit); EXPECT_NE(nullptr, conditionFunction); EXPECT_TRUE(conditionFunction(epoch)); } { - EXPECT_ANY_THROW(Generator::Undefined().getConditionFunction(fromOrbit, toOrbit)); + EXPECT_ANY_THROW(Generator::Undefined().getConditionFunction(accessTarget, toOrbit)); } } -TEST(OpenSpaceToolkit_Astrodynamics_Access_Generator, ComputeAccesses_1) +TEST_F(OpenSpaceToolkit_Astrodynamics_Access_Generator, ComputeAccesses_1) { - const Environment environment = Environment::Default(); - - const Generator generator = {environment}; - const Instant startInstant = Instant::DateTime(DateTime(2018, 1, 1, 0, 0, 0), Scale::UTC); const Instant endInstant = Instant::DateTime(DateTime(2018, 1, 2, 0, 0, 0), Scale::UTC); const Interval interval = Interval::Closed(startInstant, endInstant); - const auto generateFirstOrbit = [&environment, &startInstant]() -> Orbit + const auto generateFirstOrbit = [this, &startInstant]() -> Orbit { const Length semiMajorAxis = Length::Kilometers(7000.0); const Real eccentricity = 0.0; @@ -312,12 +285,12 @@ TEST(OpenSpaceToolkit_Astrodynamics_Access_Generator, ComputeAccesses_1) coe, epoch, gravitationalParameter, equatorialRadius, J2, J4, Kepler::PerturbationType::None }; - const Orbit orbit = {keplerianModel, environment.accessCelestialObjectWithName("Earth")}; + const Orbit orbit = {keplerianModel, defaultEnvironment_.accessCelestialObjectWithName("Earth")}; return orbit; }; - const auto generateSecondOrbit = [&environment, &startInstant]() -> Orbit + const auto generateSecondOrbit = [this, &startInstant]() -> Orbit { const Length semiMajorAxis = Length::Kilometers(7000.0); const Real eccentricity = 0.0; @@ -338,7 +311,7 @@ TEST(OpenSpaceToolkit_Astrodynamics_Access_Generator, ComputeAccesses_1) coe, epoch, gravitationalParameter, equatorialRadius, J2, J4, Kepler::PerturbationType::None }; - const Orbit orbit = {keplerianModel, environment.accessCelestialObjectWithName("Earth")}; + const Orbit orbit = {keplerianModel, defaultEnvironment_.accessCelestialObjectWithName("Earth")}; return orbit; }; @@ -346,7 +319,15 @@ TEST(OpenSpaceToolkit_Astrodynamics_Access_Generator, ComputeAccesses_1) const Orbit fromOrbit = generateFirstOrbit(); const Orbit toOrbit = generateSecondOrbit(); - const Array accesses = generator.computeAccesses(interval, fromOrbit, toOrbit); + const Constraint constraint = Constraint::FromIntervals( + ostk::mathematics::object::Interval::Closed(0.0, 360.0), + ostk::mathematics::object::Interval::Closed(-90.0, 90.0), + ostk::mathematics::object::Interval::Closed(0.0, 1.0e10) + ); + + const AccessTarget accessTarget = AccessTarget::FromTrajectory(constraint, fromOrbit); + + const Array accesses = defaultGenerator_.computeAccesses(interval, accessTarget, toOrbit); // Reference data setup @@ -390,29 +371,16 @@ TEST(OpenSpaceToolkit_Astrodynamics_Access_Generator, ComputeAccesses_1) } } -TEST(OpenSpaceToolkit_Astrodynamics_Access_Generator, ComputeAccesses_2) +TEST_F(OpenSpaceToolkit_Astrodynamics_Access_Generator, ComputeAccesses_2) { - const Environment environment = Environment::Default(); - - const Generator generator = {environment}; - const Instant startInstant = Instant::DateTime(DateTime(2018, 1, 1, 0, 0, 0), Scale::UTC); const Instant endInstant = Instant::DateTime(DateTime(2018, 1, 3, 0, 0, 0), Scale::UTC); const Interval interval = Interval::Closed(startInstant, endInstant); - const auto generateGroundStationTrajectory = []() -> Trajectory - { - const LLA groundStationLla = {Angle::Degrees(0.0), Angle::Degrees(0.0), Length::Meters(20.0)}; - - const Position groundStationPosition = Position::Meters( - groundStationLla.toCartesian(Earth::EGM2008.equatorialRadius_, Earth::EGM2008.flattening_), Frame::ITRF() - ); + const LLA groundStationLla = {Angle::Degrees(0.0), Angle::Degrees(0.0), Length::Meters(20.0)}; - return Trajectory::Position(groundStationPosition); - }; - - const auto generateSatelliteOrbit = [&environment, &startInstant]() -> Orbit + const auto generateSatelliteOrbit = [this, &startInstant]() -> Orbit { const Length semiMajorAxis = Length::Kilometers(7000.0); const Real eccentricity = 0.0; @@ -433,15 +401,21 @@ TEST(OpenSpaceToolkit_Astrodynamics_Access_Generator, ComputeAccesses_2) coe, epoch, gravitationalParameter, equatorialRadius, J2, J4, Kepler::PerturbationType::None }; - const Orbit orbit = {keplerianModel, environment.accessCelestialObjectWithName("Earth")}; + const Orbit orbit = {keplerianModel, defaultEnvironment_.accessCelestialObjectWithName("Earth")}; return orbit; }; - const Trajectory groundStationTrajectory = generateGroundStationTrajectory(); + const Constraint constraint = Constraint::FromIntervals( + ostk::mathematics::object::Interval::Closed(0.0, 360.0), + ostk::mathematics::object::Interval::Closed(-90.0, 90.0), + ostk::mathematics::object::Interval::Closed(0.0, 1.0e10) + ); + const AccessTarget groundStationTarget = AccessTarget::FromLLA(constraint, groundStationLla, defaultEarthSPtr_); + const Orbit satelliteOrbit = generateSatelliteOrbit(); - const Array accesses = generator.computeAccesses(interval, groundStationTrajectory, satelliteOrbit); + const Array accesses = defaultGenerator_.computeAccesses(interval, groundStationTarget, satelliteOrbit); // Reference data setup @@ -485,29 +459,16 @@ TEST(OpenSpaceToolkit_Astrodynamics_Access_Generator, ComputeAccesses_2) } } -TEST(OpenSpaceToolkit_Astrodynamics_Access_Generator, ComputeAccesses_3) +TEST_F(OpenSpaceToolkit_Astrodynamics_Access_Generator, ComputeAccesses_3) { - const Environment environment = Environment::Default(); - - const Generator generator = {environment}; - const Instant startInstant = Instant::DateTime(DateTime(2018, 9, 6, 0, 0, 0), Scale::UTC); const Instant endInstant = Instant::DateTime(DateTime(2018, 9, 7, 0, 0, 0), Scale::UTC); const Interval interval = Interval::Closed(startInstant, endInstant); - const auto generateGroundStationTrajectory = []() -> Trajectory - { - const LLA groundStationLla = {Angle::Degrees(-45.0), Angle::Degrees(-170.0), Length::Meters(5.0)}; - - const Position groundStationPosition = Position::Meters( - groundStationLla.toCartesian(Earth::EGM2008.equatorialRadius_, Earth::EGM2008.flattening_), Frame::ITRF() - ); + const LLA groundStationLla = {Angle::Degrees(-45.0), Angle::Degrees(-170.0), Length::Meters(5.0)}; - return Trajectory::Position(groundStationPosition); - }; - - const auto generateSatelliteOrbit = [&environment, &startInstant]() -> Orbit + const auto generateSatelliteOrbit = [this, &startInstant]() -> Orbit { const TLE tle = { "1 39419U 13066D 18248.44969859 -.00000394 00000-0 -31796-4 0 9997", @@ -516,15 +477,22 @@ TEST(OpenSpaceToolkit_Astrodynamics_Access_Generator, ComputeAccesses_3) const SGP4 orbitalModel = {tle}; - const Orbit orbit = {orbitalModel, environment.accessCelestialObjectWithName("Earth")}; + const Orbit orbit = {orbitalModel, defaultEnvironment_.accessCelestialObjectWithName("Earth")}; return orbit; }; - const Trajectory groundStationTrajectory = generateGroundStationTrajectory(); + // TBI: Convert to LOS trajectory + const Constraint constraint = Constraint::FromIntervals( + ostk::mathematics::object::Interval::Closed(0.0, 360.0), + ostk::mathematics::object::Interval::Closed(-90.0, 90.0), + ostk::mathematics::object::Interval::Closed(0.0, 1.0e10) + ); + + const AccessTarget groundStationTarget = AccessTarget::FromLLA(constraint, groundStationLla, defaultEarthSPtr_); const Orbit satelliteOrbit = generateSatelliteOrbit(); - const Array accesses = generator.computeAccesses(interval, groundStationTrajectory, satelliteOrbit); + const Array accesses = defaultGenerator_.computeAccesses(interval, groundStationTarget, satelliteOrbit); // Reference data setup @@ -568,7 +536,7 @@ TEST(OpenSpaceToolkit_Astrodynamics_Access_Generator, ComputeAccesses_3) } } -TEST(OpenSpaceToolkit_Astrodynamics_Access_Generator, ComputeAccesses_4) +TEST_F(OpenSpaceToolkit_Astrodynamics_Access_Generator, ComputeAccesses_4) { const Instant startInstant = Instant::DateTime(DateTime(2020, 1, 1, 0, 0, 0), Scale::UTC); const Instant endInstant = Instant::DateTime(DateTime(2020, 1, 1, 0, 10, 0), Scale::UTC); @@ -603,6 +571,15 @@ TEST(OpenSpaceToolkit_Astrodynamics_Access_Generator, ComputeAccesses_4) Position::Meters({0.0, 0.0, 0.0}, Frame::GCRF()), Velocity::MetersPerSecond({1.0, 0.0, 0.0}, Frame::GCRF()) ); + const AccessTarget accessTarget = AccessTarget::FromTrajectory( + Constraint::FromIntervals( + ostk::mathematics::object::Interval::Closed(0.0, 360.0), + ostk::mathematics::object::Interval::Closed(-90.0, 90.0), + ostk::mathematics::object::Interval::Closed(0.0, 1.0e10) + ), + firstTrajectory + ); + const Trajectory secondTrajectory = generateTrajectory( Position::Meters({0.0, 0.0, 0.0}, Frame::GCRF()), Velocity::MetersPerSecond({0.0, 0.0, 1.0}, Frame::GCRF()) ); @@ -615,9 +592,9 @@ TEST(OpenSpaceToolkit_Astrodynamics_Access_Generator, ComputeAccesses_4) return true; }; - const Generator generator = {Environment::Default(), {}, {}, stateFilter}; + const Generator generator = {defaultEnvironment_, defaultStep_, defaultTolerance_, {}, stateFilter}; - const Array accesses = generator.computeAccesses(interval, firstTrajectory, secondTrajectory); + const Array accesses = generator.computeAccesses(interval, accessTarget, secondTrajectory); EXPECT_EQ(1, accesses.getSize()); @@ -635,9 +612,9 @@ TEST(OpenSpaceToolkit_Astrodynamics_Access_Generator, ComputeAccesses_4) .getAbsolute() >= Duration::Minutes(2.0); }; - const Generator generator = {Environment::Default(), {}, {}, stateFilter}; + const Generator generator = {defaultEnvironment_, defaultStep_, defaultTolerance_, {}, stateFilter}; - const Array accesses = generator.computeAccesses(interval, firstTrajectory, secondTrajectory); + const Array accesses = generator.computeAccesses(interval, accessTarget, secondTrajectory); EXPECT_EQ(2, accesses.getSize()); @@ -655,7 +632,7 @@ TEST(OpenSpaceToolkit_Astrodynamics_Access_Generator, ComputeAccesses_4) } } -TEST(OpenSpaceToolkit_Astrodynamics_Access_Generator, GroundTargetConfiguration) +TEST_F(OpenSpaceToolkit_Astrodynamics_Access_Generator, AccessTarget) { const ostk::mathematics::object::Interval azimuthInterval = ostk::mathematics::object::Interval::Closed(0.0, 360.0); @@ -668,21 +645,21 @@ TEST(OpenSpaceToolkit_Astrodynamics_Access_Generator, GroundTargetConfiguration) // Constructor { { - EXPECT_NO_THROW(GroundTargetConfiguration(azimuthInterval, elevationInterval, rangeInterval, lla)); + const Constraint constraint = Constraint::FromIntervals(azimuthInterval, elevationInterval, rangeInterval); + + EXPECT_NO_THROW(AccessTarget::FromLLA(constraint, lla, defaultEarthSPtr_)); } { + const Constraint constraint = Constraint::FromIntervals(azimuthInterval, elevationInterval, rangeInterval); { const Position position = Position::Meters({0.0, 0.0, 0.0}, Frame::ITRF()); - EXPECT_NO_THROW(GroundTargetConfiguration(azimuthInterval, elevationInterval, rangeInterval, position)); + EXPECT_NO_THROW(AccessTarget::FromPosition(constraint, position)); } { const Position position = Position::Meters({0.0, 0.0, 0.0}, Frame::GCRF()); - EXPECT_THROW( - GroundTargetConfiguration(azimuthInterval, elevationInterval, rangeInterval, position), - ostk::core::error::RuntimeError - ); + EXPECT_THROW(AccessTarget::FromPosition(constraint, position), ostk::core::error::RuntimeError); } } @@ -691,25 +668,38 @@ TEST(OpenSpaceToolkit_Astrodynamics_Access_Generator, GroundTargetConfiguration) const Position position = Position::Meters({0.0, 0.0, 0.0}, Frame::ITRF()); EXPECT_THROW( - GroundTargetConfiguration( - ostk::mathematics::object::Interval::Undefined(), elevationInterval, rangeInterval, position + AccessTarget::FromPosition( + Constraint::FromIntervals( + ostk::mathematics::object::Interval::Undefined(), elevationInterval, rangeInterval + ), + position ), ostk::core::error::runtime::Undefined ); EXPECT_THROW( - GroundTargetConfiguration( - azimuthInterval, ostk::mathematics::object::Interval::Undefined(), rangeInterval, position + AccessTarget::FromPosition( + Constraint::FromIntervals( + azimuthInterval, ostk::mathematics::object::Interval::Undefined(), rangeInterval + ), + position ), ostk::core::error::runtime::Undefined ); EXPECT_THROW( - GroundTargetConfiguration( - azimuthInterval, elevationInterval, ostk::mathematics::object::Interval::Undefined(), position + AccessTarget::FromPosition( + Constraint::FromIntervals( + azimuthInterval, elevationInterval, ostk::mathematics::object::Interval::Undefined() + ), + position ), ostk::core::error::runtime::Undefined ); EXPECT_THROW( - GroundTargetConfiguration(azimuthInterval, elevationInterval, rangeInterval, LLA::Undefined()), + AccessTarget::FromLLA( + Constraint::FromIntervals(azimuthInterval, elevationInterval, rangeInterval), + LLA::Undefined(), + defaultEarthSPtr_ + ), ostk::core::error::runtime::Undefined ); } @@ -718,51 +708,66 @@ TEST(OpenSpaceToolkit_Astrodynamics_Access_Generator, GroundTargetConfiguration) { { EXPECT_THROW( - GroundTargetConfiguration( - ostk::mathematics::object::Interval::Closed(-1.0, 350.0), - elevationInterval, - rangeInterval, - lla + AccessTarget::FromLLA( + Constraint::FromIntervals( + ostk::mathematics::object::Interval::Closed(-1.0, 350.0), + elevationInterval, + rangeInterval + ), + lla, + defaultEarthSPtr_ ), ostk::core::error::RuntimeError ); EXPECT_THROW( - GroundTargetConfiguration( - ostk::mathematics::object::Interval::Closed(0.0, 360.1), - elevationInterval, - rangeInterval, - lla + AccessTarget::FromLLA( + Constraint::FromIntervals( + ostk::mathematics::object::Interval::Closed(0.0, 360.1), + elevationInterval, + rangeInterval + ), + lla, + defaultEarthSPtr_ ), ostk::core::error::RuntimeError ); } { EXPECT_THROW( - GroundTargetConfiguration( - azimuthInterval, - ostk::mathematics::object::Interval::Closed(-91.0, 0.0), - rangeInterval, - lla + AccessTarget::FromLLA( + Constraint::FromIntervals( + azimuthInterval, + ostk::mathematics::object::Interval::Closed(-91.0, 0.0), + rangeInterval + ), + lla, + defaultEarthSPtr_ ), ostk::core::error::RuntimeError ); EXPECT_THROW( - GroundTargetConfiguration( - azimuthInterval, - ostk::mathematics::object::Interval::Closed(-45.0, 91.0), - rangeInterval, - lla + AccessTarget::FromLLA( + Constraint::FromIntervals( + azimuthInterval, + ostk::mathematics::object::Interval::Closed(-45.0, 91.0), + rangeInterval + ), + lla, + defaultEarthSPtr_ ), ostk::core::error::RuntimeError ); } { EXPECT_THROW( - GroundTargetConfiguration( - azimuthInterval, - elevationInterval, - ostk::mathematics::object::Interval::Closed(-1.0, 5.0), - lla + AccessTarget::FromLLA( + Constraint::FromIntervals( + azimuthInterval, + elevationInterval, + ostk::mathematics::object::Interval::Closed(-1.0, 5.0) + ), + lla, + defaultEarthSPtr_ ), ostk::core::error::RuntimeError ); @@ -772,40 +777,36 @@ TEST(OpenSpaceToolkit_Astrodynamics_Access_Generator, GroundTargetConfiguration) // Getters { - const GroundTargetConfiguration groundTargetConfiguration = - GroundTargetConfiguration(azimuthInterval, elevationInterval, rangeInterval, lla); - EXPECT_EQ(groundTargetConfiguration.getAzimuthInterval(), azimuthInterval); - EXPECT_EQ(groundTargetConfiguration.getElevationInterval(), elevationInterval); - EXPECT_EQ(groundTargetConfiguration.getRangeInterval(), rangeInterval); - EXPECT_EQ(groundTargetConfiguration.getLLA(), lla); + const Constraint constraint = Constraint::FromIntervals(azimuthInterval, elevationInterval, rangeInterval); + const AccessTarget accessTarget = AccessTarget::FromLLA(constraint, lla, defaultEarthSPtr_); + // TBI: Fix the checks below + EXPECT_EQ(accessTarget.getLLA(defaultEarthSPtr_), lla); EXPECT_EQ( - groundTargetConfiguration.getPosition(), + accessTarget.getPosition(), Position::Meters( lla.toCartesian(Earth::EGM2008.equatorialRadius_, Earth::EGM2008.flattening_), Frame::ITRF() ) ); - EXPECT_NO_THROW(groundTargetConfiguration.getTrajectory()); + EXPECT_NO_THROW(accessTarget.getTrajectory()); Matrix3d r_SEZ_ECEF; r_SEZ_ECEF.row(0) = Vector3d {0.0, 0.0, -1.0}; r_SEZ_ECEF.row(1) = Vector3d {0.0, 1.0, 0.0}; r_SEZ_ECEF.row(2) = Vector3d {1.0, 0.0, 0.0}; - EXPECT_EQ(groundTargetConfiguration.computeR_SEZ_ECEF(), r_SEZ_ECEF); + EXPECT_EQ(accessTarget.computeR_SEZ_ECEF(defaultEarthSPtr_), r_SEZ_ECEF); } } -TEST(OpenSpaceToolkit_Astrodynamics_Access_Generator, ComputeAccessesWithGroundTargets) +TEST_F(OpenSpaceToolkit_Astrodynamics_Access_Generator, ComputeAccesses) { { - const Environment environment = Environment::Default(); - const TLE tle = { "1 60504U 24149AN 24293.10070306 .00000000 00000-0 58313-3 0 08", "2 60504 97.4383 7.6998 0003154 274.9510 182.9597 15.19652001 9607", }; const SGP4 sgp4 = SGP4(tle); - const Orbit aToTrajectory = Orbit(sgp4, environment.accessCelestialObjectWithName("Earth")); + const Orbit aToTrajectory = Orbit(sgp4, defaultEnvironment_.accessCelestialObjectWithName("Earth")); const Instant startInstant = Instant::Parse("2024-10-19 02:25:00.744.384", Scale::UTC); const Instant endInstant = startInstant + Duration::Days(1.0); @@ -835,28 +836,27 @@ TEST(OpenSpaceToolkit_Astrodynamics_Access_Generator, ComputeAccessesWithGroundT const ostk::mathematics::object::Interval rangeInterval = ostk::mathematics::object::Interval::Closed(0.0, 1.0e10); - Array groundTargets = LLAs.map( - [&azimuthInterval, &elevationInterval, &rangeInterval](const LLA& lla) -> GroundTargetConfiguration + const Constraint constraint = Constraint::FromIntervals(azimuthInterval, elevationInterval, rangeInterval); + + Array accessTargets = LLAs.map( + [&constraint, this](const LLA& lla) -> AccessTarget { - return GroundTargetConfiguration(azimuthInterval, elevationInterval, rangeInterval, lla); + return AccessTarget::FromLLA(constraint, lla, defaultEarthSPtr_); } ); - const Generator generator = - Generator::AerRanges(azimuthInterval, elevationInterval, rangeInterval, environment); - const Array> accessesPerTarget = - generator.computeAccessesWithGroundTargets(interval, groundTargets, aToTrajectory); + defaultGenerator_.computeAccesses(interval, accessTargets, aToTrajectory); - ASSERT_EQ(accessesPerTarget.getSize(), groundTargets.getSize()); + ASSERT_EQ(accessesPerTarget.getSize(), accessTargets.getSize()); for (Index i = 0; i < accessesPerTarget.getSize(); ++i) { const Array accesses = accessesPerTarget.at(i); - const GroundTargetConfiguration groundTarget = groundTargets.at(i); + const AccessTarget groundTarget = accessTargets.at(i); const Array expectedAccesses = - generator.computeAccesses(interval, groundTarget.getTrajectory(), aToTrajectory); + defaultGenerator_.computeAccesses(interval, groundTarget, aToTrajectory); ASSERT_EQ(accesses.getSize(), expectedAccesses.getSize()); @@ -884,91 +884,57 @@ TEST(OpenSpaceToolkit_Astrodynamics_Access_Generator, ComputeAccessesWithGroundT } } -TEST(OpenSpaceToolkit_Astrodynamics_Access_Generator, SetStep) +TEST_F(OpenSpaceToolkit_Astrodynamics_Access_Generator, SetStep) { { - const Environment environment = Environment::Default(); - - Generator generator = {environment}; - const Duration step = Duration::Seconds(1.0); - EXPECT_NO_THROW(generator.setStep(step)); + EXPECT_NO_THROW(defaultGenerator_.setStep(step)); - EXPECT_ANY_THROW(generator.setStep(Duration::Undefined())); + EXPECT_ANY_THROW(defaultGenerator_.setStep(Duration::Undefined())); } } -TEST(OpenSpaceToolkit_Astrodynamics_Access_Generator, SetTolerance) +TEST_F(OpenSpaceToolkit_Astrodynamics_Access_Generator, SetTolerance) { { - const Environment environment = Environment::Default(); - - Generator generator = {environment}; - const Duration tolerance = Duration::Seconds(1.0); - EXPECT_NO_THROW(generator.setTolerance(tolerance)); + EXPECT_NO_THROW(defaultGenerator_.setTolerance(tolerance)); - EXPECT_ANY_THROW(generator.setTolerance(Duration::Undefined())); + EXPECT_ANY_THROW(defaultGenerator_.setTolerance(Duration::Undefined())); } } -TEST(OpenSpaceToolkit_Astrodynamics_Access_Generator, SetAerFilter) +TEST_F(OpenSpaceToolkit_Astrodynamics_Access_Generator, SetAccessFilter) { { - const Environment environment = Environment::Default(); - - Generator generator = {environment}; - - const auto aerFilter = [](const AER&) -> bool - { - return true; - }; - - EXPECT_NO_THROW(generator.setAerFilter(aerFilter)); - - EXPECT_NO_THROW(generator.setAerFilter({})); - } -} - -TEST(OpenSpaceToolkit_Astrodynamics_Access_Generator, SetAccessFilter) -{ - { - const Environment environment = Environment::Default(); - - Generator generator = {environment}; - const auto accessFilter = [](const Access&) -> bool { return true; }; - EXPECT_NO_THROW(generator.setAccessFilter(accessFilter)); + EXPECT_NO_THROW(defaultGenerator_.setAccessFilter(accessFilter)); - EXPECT_NO_THROW(generator.setAccessFilter({})); + EXPECT_NO_THROW(defaultGenerator_.setAccessFilter({})); } } -TEST(OpenSpaceToolkit_Astrodynamics_Access_Generator, SetStateFilter) +TEST_F(OpenSpaceToolkit_Astrodynamics_Access_Generator, SetStateFilter) { { - const Environment environment = Environment::Default(); - - Generator generator = {environment}; - const auto stateFilter = [](const State&, const State&) -> bool { return true; }; - EXPECT_NO_THROW(generator.setStateFilter(stateFilter)); + EXPECT_NO_THROW(defaultGenerator_.setStateFilter(stateFilter)); - EXPECT_NO_THROW(generator.setStateFilter({})); + EXPECT_NO_THROW(defaultGenerator_.setStateFilter({})); } } -TEST(OpenSpaceToolkit_Astrodynamics_Access_Generator, Undefined) +TEST_F(OpenSpaceToolkit_Astrodynamics_Access_Generator, Undefined) { { EXPECT_NO_THROW(Generator::Undefined()); @@ -977,13 +943,11 @@ TEST(OpenSpaceToolkit_Astrodynamics_Access_Generator, Undefined) } } -TEST(OpenSpaceToolkit_Astrodynamics_Access_Generator, AerRanges) +TEST_F(OpenSpaceToolkit_Astrodynamics_Access_Generator, AerRanges_1) { { // Access computation - const Environment environment = Environment::Default(); - const ostk::mathematics::object::Interval azimuthRange = ostk::mathematics::object::Interval::Closed(0.0, 360.0); const ostk::mathematics::object::Interval elevationRange = @@ -991,26 +955,16 @@ TEST(OpenSpaceToolkit_Astrodynamics_Access_Generator, AerRanges) const ostk::mathematics::object::Interval rangeRange = ostk::mathematics::object::Interval::Closed(0.0, 10000e3); - const Generator generator = Generator::AerRanges(azimuthRange, elevationRange, rangeRange, environment); + const Constraint constraint = Constraint::FromIntervals(azimuthRange, elevationRange, rangeRange); const Instant startInstant = Instant::DateTime(DateTime(2018, 1, 1, 0, 0, 0), Scale::UTC); const Instant endInstant = Instant::DateTime(DateTime(2018, 1, 10, 0, 0, 0), Scale::UTC); const Interval interval = Interval::Closed(startInstant, endInstant); - const auto generateGroundStationTrajectory = []() -> Trajectory - { - const LLA groundStationLla = {Angle::Degrees(47.8864), Angle::Degrees(106.906), Length::Meters(10.0)}; - - const Position groundStationPosition = Position::Meters( - groundStationLla.toCartesian(Earth::EGM2008.equatorialRadius_, Earth::EGM2008.flattening_), - Frame::ITRF() - ); + const LLA groundStationLla = {Angle::Degrees(47.8864), Angle::Degrees(106.906), Length::Meters(10.0)}; - return Trajectory::Position(groundStationPosition); - }; - - const auto generateSatelliteOrbit = [&environment, &startInstant]() -> Orbit + const auto generateSatelliteOrbit = [this, &startInstant]() -> Orbit { const Length semiMajorAxis = Length::Kilometers(6878.14); const Real eccentricity = 0.0; @@ -1031,16 +985,15 @@ TEST(OpenSpaceToolkit_Astrodynamics_Access_Generator, AerRanges) coe, epoch, gravitationalParameter, equatorialRadius, J2, J4, Kepler::PerturbationType::J2 }; - const Orbit orbit = {keplerianModel, environment.accessCelestialObjectWithName("Earth")}; + const Orbit orbit = {keplerianModel, defaultEnvironment_.accessCelestialObjectWithName("Earth")}; return orbit; }; - const Trajectory groundStationTrajectory = generateGroundStationTrajectory(); + const AccessTarget groundStationTarget = AccessTarget::FromLLA(constraint, groundStationLla, defaultEarthSPtr_); const Orbit satelliteOrbit = generateSatelliteOrbit(); - const Array accesses = generator.computeAccesses(interval, groundStationTrajectory, satelliteOrbit); - // std::cout << accesses << std::endl; + const Array accesses = defaultGenerator_.computeAccesses(interval, groundStationTarget, satelliteOrbit); // Reference data setup @@ -1085,39 +1038,27 @@ TEST(OpenSpaceToolkit_Astrodynamics_Access_Generator, AerRanges) } } -TEST(OpenSpaceToolkit_Astrodynamics_Access_Generator, AerMask) +TEST_F(OpenSpaceToolkit_Astrodynamics_Access_Generator, AerMask_1) { { // Access computation - const Environment environment = Environment::Default(); - const ostk::core::container::Map azimuthElevationMask = { {0.0, 30.0}, {90.0, 60.0}, {180.0, 60.0}, {270.0, 30.0}, {359.0, 30.0} }; const ostk::mathematics::object::Interval rangeRange = ostk::mathematics::object::Interval::Closed(0.0, 10000e3); - const Generator generator = Generator::AerMask(azimuthElevationMask, rangeRange, environment); + const Constraint constraint = Constraint::FromMask(azimuthElevationMask, rangeRange); const Instant startInstant = Instant::DateTime(DateTime(2018, 1, 1, 0, 0, 0), Scale::UTC); const Instant endInstant = Instant::DateTime(DateTime(2018, 1, 5, 0, 0, 0), Scale::UTC); const Interval interval = Interval::Closed(startInstant, endInstant); - const auto generateGroundStationTrajectory = []() -> Trajectory - { - const LLA groundStationLla = {Angle::Degrees(47.8864), Angle::Degrees(106.906), Length::Meters(10.0)}; - - const Position groundStationPosition = Position::Meters( - groundStationLla.toCartesian(Earth::EGM2008.equatorialRadius_, Earth::EGM2008.flattening_), - Frame::ITRF() - ); + const LLA groundStationLla = {Angle::Degrees(47.8864), Angle::Degrees(106.906), Length::Meters(10.0)}; - return Trajectory::Position(groundStationPosition); - }; - - const auto generateSatelliteOrbit = [&environment, &startInstant]() -> Orbit + const auto generateSatelliteOrbit = [this, &startInstant]() -> Orbit { const Length semiMajorAxis = Length::Kilometers(6878.14); const Real eccentricity = 0.0; @@ -1138,15 +1079,16 @@ TEST(OpenSpaceToolkit_Astrodynamics_Access_Generator, AerMask) coe, epoch, gravitationalParameter, equatorialRadius, J2, J4, Kepler::PerturbationType::J2 }; - const Orbit orbit = {keplerianModel, environment.accessCelestialObjectWithName("Earth")}; + const Orbit orbit = {keplerianModel, defaultEnvironment_.accessCelestialObjectWithName("Earth")}; return orbit; }; - const Trajectory groundStationTrajectory = generateGroundStationTrajectory(); + const AccessTarget groundStationTarget = AccessTarget::FromLLA(constraint, groundStationLla, defaultEarthSPtr_); + const Orbit satelliteOrbit = generateSatelliteOrbit(); - const Array accesses = generator.computeAccesses(interval, groundStationTrajectory, satelliteOrbit); + const Array accesses = defaultGenerator_.computeAccesses(interval, groundStationTarget, satelliteOrbit); // Reference data setup diff --git a/test/OpenSpaceToolkit/Astrodynamics/Flight/Profile/Transform.test.cpp b/test/OpenSpaceToolkit/Astrodynamics/Flight/Profile/Transform.test.cpp index d4e9128f2..c3a909e55 100644 --- a/test/OpenSpaceToolkit/Astrodynamics/Flight/Profile/Transform.test.cpp +++ b/test/OpenSpaceToolkit/Astrodynamics/Flight/Profile/Transform.test.cpp @@ -101,7 +101,7 @@ TEST_F(OpenSpaceToolkit_Astrodynamics_Flight_Profile_Models_Transform, Construct ); }; - EXPECT_NO_THROW(Transform transform_(DynamicProvider(dynamicProviderGenerator), Frame::GCRF())); + EXPECT_NO_THROW(Transform transform(DynamicProvider(dynamicProviderGenerator), Frame::GCRF())); } TEST_F(OpenSpaceToolkit_Astrodynamics_Flight_Profile_Models_Transform, StreamOperator) From 876d6dd5d16157528f701814d94e624fffe2d2b1 Mon Sep 17 00:00:00 2001 From: vishwa shah Date: Tue, 12 Nov 2024 16:40:34 +0000 Subject: [PATCH 08/22] wip: --- .../Astrodynamics/Access/Constraint.hpp | 25 +++++- .../Astrodynamics/Access/Constraint.cpp | 82 ++++++++++++++----- .../Astrodynamics/Access/Generator.cpp | 68 +++++++++------ 3 files changed, 130 insertions(+), 45 deletions(-) diff --git a/include/OpenSpaceToolkit/Astrodynamics/Access/Constraint.hpp b/include/OpenSpaceToolkit/Astrodynamics/Access/Constraint.hpp index 8fb2b7763..145093413 100644 --- a/include/OpenSpaceToolkit/Astrodynamics/Access/Constraint.hpp +++ b/include/OpenSpaceToolkit/Astrodynamics/Access/Constraint.hpp @@ -12,7 +12,10 @@ #include #include +#include #include +#include +#include namespace ostk { @@ -27,7 +30,10 @@ using ostk::core::type::Real; using ostk::mathematics::object::Interval; using ostk::mathematics::object::Vector2d; +using ostk::physics::coordinate::Position; using ostk::physics::coordinate::spherical::AER; +using ostk::physics::Environment; +using ostk::physics::time::Instant; class Constraint { @@ -55,30 +61,43 @@ class Constraint bool isSatisfied(const AER& anAer) const; }; + struct LineOfSightConstraint + { + mutable Environment environment; + + LineOfSightConstraint(const Environment& anEnvironment); + + bool isSatisfied(const Instant& anInstant, const Position& aFromPosition, const Position& aToPosition) const; + }; + static Constraint FromIntervals( const Interval& azimuth, const Interval& elevation, const Interval& range ); static Constraint FromMask(const Map& azimuthElevationMask, const Interval& range); - bool isSatisfied(const AER& aer) const; + static Constraint FromLineOfSight(const Environment& environment); bool isMaskBased() const; bool isIntervalBased() const; + bool isLineOfSightBased() const; + std::optional getIntervalConstraint() const; std::optional getMaskConstraint() const; - Interval getRangeInterval() const; + std::optional getLineOfSightConstraint() const; private: - std::variant constraint_; + std::variant constraint_; explicit Constraint(const IntervalConstraint& constraint); explicit Constraint(const MaskConstraint& constraint); + + explicit Constraint(const LineOfSightConstraint& constraint); }; } // namespace access diff --git a/src/OpenSpaceToolkit/Astrodynamics/Access/Constraint.cpp b/src/OpenSpaceToolkit/Astrodynamics/Access/Constraint.cpp index 247524105..f5bb08419 100644 --- a/src/OpenSpaceToolkit/Astrodynamics/Access/Constraint.cpp +++ b/src/OpenSpaceToolkit/Astrodynamics/Access/Constraint.cpp @@ -1,5 +1,10 @@ /// Apache License 2.0 +#include +#include + +#include + #include namespace ostk @@ -9,6 +14,14 @@ namespace astrodynamics namespace access { +using ostk::core::type::Shared; + +using ostk::mathematics::geometry::d3::object::Point; +using ostk::mathematics::geometry::d3::object::Segment; + +using ostk::physics::coordinate::Frame; +using ostk::physics::environment::Object; + Constraint::IntervalConstraint::IntervalConstraint( const Interval& anAzimuthInterval, const Interval& anElevationInterval, @@ -101,7 +114,7 @@ Constraint::MaskConstraint::MaskConstraint( anAzimuthElevationMask_rad.insert({it->first * M_PI / 180.0, it->second * M_PI / 180.0}); } - azimuthElevationMask = anAzimuthElevationMask_rad; + this->azimuthElevationMask = anAzimuthElevationMask_rad; } bool Constraint::MaskConstraint::isSatisfied(const AER& anAer) const @@ -109,9 +122,9 @@ bool Constraint::MaskConstraint::isSatisfied(const AER& anAer) const const Real azimuth_rad = anAer.getAzimuth().inRadians(0.0, Real::TwoPi()); const Real elevation_rad = anAer.getElevation().inRadians(-Real::Pi(), Real::Pi()); - auto itLow = azimuthElevationMask.lower_bound(azimuth_rad); + auto itLow = this->azimuthElevationMask.lower_bound(azimuth_rad); itLow--; - auto itUp = azimuthElevationMask.upper_bound(azimuth_rad); + auto itUp = this->azimuthElevationMask.upper_bound(azimuth_rad); // Vector between the two successive mask data points with bounding azimuth values @@ -127,6 +140,34 @@ bool Constraint::MaskConstraint::isSatisfied(const AER& anAer) const return ((lowToUpVector[0] * lowToPointVector[1] - lowToUpVector[1] * lowToPointVector[0]) >= 0.0); } +Constraint::LineOfSightConstraint::LineOfSightConstraint(const Environment& anEnvironment) + : environment(anEnvironment) +{ +} + +bool Constraint::LineOfSightConstraint::isSatisfied( + const Instant& anInstant, const Position& aFromPosition, const Position& aToPosition +) const +{ + static const Shared commonFrameSPtr = Frame::GCRF(); + + this->environment.setInstant(anInstant); + + const Point fromPositionCoordinates = Point::Vector(aFromPosition.accessCoordinates()); + const Point toPositionCoordinates = Point::Vector(aToPosition.accessCoordinates()); + + if (fromPositionCoordinates == toPositionCoordinates) + { + return true; + } + + const Segment fromToSegment = {fromPositionCoordinates, toPositionCoordinates}; + + const Object::Geometry fromToSegmentGeometry = {fromToSegment, commonFrameSPtr}; + + return !this->environment.intersects(fromToSegmentGeometry); +} + Constraint Constraint::FromIntervals( const Interval& azimuth, const Interval& elevation, const Interval& range ) @@ -139,15 +180,9 @@ Constraint Constraint::FromMask(const Map& azimuthElevationMask, con return Constraint(MaskConstraint {azimuthElevationMask, range}); } -bool Constraint::isSatisfied(const AER& aer) const +Constraint Constraint::FromLineOfSight(const Environment& environment) { - return std::visit( - [&aer](const auto& constraint) - { - return constraint.isSatisfied(aer); - }, - constraint_ - ); + return Constraint(LineOfSightConstraint {environment}); } bool Constraint::isMaskBased() const @@ -160,6 +195,11 @@ bool Constraint::isIntervalBased() const return std::holds_alternative(constraint_); } +bool Constraint::isLineOfSightBased() const +{ + return std::holds_alternative(constraint_); +} + std::optional Constraint::getIntervalConstraint() const { if (const auto* interval = std::get_if(&constraint_)) @@ -180,15 +220,14 @@ std::optional Constraint::getMaskConstraint() const return std::nullopt; } -Interval Constraint::getRangeInterval() const +std::optional Constraint::getLineOfSightConstraint() const { - return std::visit( - [](const auto& constraint) -> Interval - { - return constraint.range; - }, - constraint_ - ); + if (const auto* lineOfSight = std::get_if(&constraint_)) + { + return *lineOfSight; + } + + return std::nullopt; } Constraint::Constraint(const IntervalConstraint& constraint) @@ -201,6 +240,11 @@ Constraint::Constraint(const MaskConstraint& constraint) { } +Constraint::Constraint(const LineOfSightConstraint& constraint) + : constraint_(constraint) +{ +} + } // namespace access } // namespace astrodynamics } // namespace ostk diff --git a/src/OpenSpaceToolkit/Astrodynamics/Access/Generator.cpp b/src/OpenSpaceToolkit/Astrodynamics/Access/Generator.cpp index 919d519c4..18b05fda3 100644 --- a/src/OpenSpaceToolkit/Astrodynamics/Access/Generator.cpp +++ b/src/OpenSpaceToolkit/Astrodynamics/Access/Generator.cpp @@ -229,14 +229,17 @@ Array Generator::computeAccesses( throw ostk::core::error::runtime::Undefined("Interval"); } - if (anAccessTarget.getType() == AccessTarget::Type::Trajectory) + if (!aToTrajectory.isDefined()) { - return this->computeAccessesForTrajectoryTarget(anInterval, anAccessTarget, aToTrajectory); + throw ostk::core::error::runtime::Undefined("To Trajectory"); } - else + + if (anAccessTarget.getType() == AccessTarget::Type::Trajectory) { - return this->computeAccessesForFixedTargets(anInterval, Array {anAccessTarget}, aToTrajectory)[0]; + return this->computeAccessesForTrajectoryTarget(anInterval, anAccessTarget, aToTrajectory); } + + return this->computeAccessesForFixedTargets(anInterval, Array {anAccessTarget}, aToTrajectory)[0]; } Array> Generator::computeAccesses( @@ -255,6 +258,11 @@ Array> Generator::computeAccesses( throw ostk::core::error::runtime::Undefined("Access targets"); } + if (!aToTrajectory.isDefined()) + { + throw ostk::core::error::runtime::Undefined("To Trajectory"); + } + if (std ::all_of( someAccessTargets.begin(), someAccessTargets.end(), @@ -287,10 +295,10 @@ Array> Generator::computeAccesses( { return this->computeAccessesForFixedTargets(anInterval, someAccessTargets, aToTrajectory); } - else - { - throw ostk::core::error::RuntimeError("All targets must be of same type."); - } + + throw ostk::core::error::RuntimeError("All targets must be of same type."); + + return {}; } void Generator::setStep(const Duration& aStep) @@ -349,6 +357,7 @@ Array> Generator::computeAccessesForFixedTargets( const bool& coarse ) const { + std::cout << "Computing accesses for fixed targets" << std::endl; if (stateFilter_) { throw ostk::core::error::RuntimeError("State filter is not supported for multiple ground targets."); @@ -884,8 +893,6 @@ GeneratorContext::GeneratorContext( bool GeneratorContext::isAccessActive(const Instant& anInstant, const Constraint& aConstraint) { - this->environment_.setInstant(anInstant); - const auto [fromState, toState] = GeneratorContext::GetStatesAt(anInstant, this->fromTrajectory_, this->toTrajectory_); @@ -899,28 +906,43 @@ bool GeneratorContext::isAccessActive(const Instant& anInstant, const Constraint // Line of sight // TBI: Remove this check as it is redundant - static const Shared commonFrameSPtr = Frame::GCRF(); + // static const Shared commonFrameSPtr = Frame::GCRF(); - const Point fromPositionCoordinates = Point::Vector(fromPosition.accessCoordinates()); - const Point toPositionCoordinates = Point::Vector(toPosition.accessCoordinates()); + // const Point fromPositionCoordinates = Point::Vector(fromPosition.accessCoordinates()); + // const Point toPositionCoordinates = Point::Vector(toPosition.accessCoordinates()); - if (fromPositionCoordinates != toPositionCoordinates) - { - const Segment fromToSegment = {fromPositionCoordinates, toPositionCoordinates}; + // if (fromPositionCoordinates != toPositionCoordinates) + // { + // const Segment fromToSegment = {fromPositionCoordinates, toPositionCoordinates}; - const Object::Geometry fromToSegmentGeometry = {fromToSegment, commonFrameSPtr}; + // const Object::Geometry fromToSegmentGeometry = {fromToSegment, commonFrameSPtr}; - const bool lineOfSight = !this->environment_.intersects(fromToSegmentGeometry); + // const bool lineOfSight = !this->environment_.intersects(fromToSegmentGeometry); - if (!lineOfSight) - { - return false; - } + // if (!lineOfSight) + // { + // return false; + // } + // } + + if (aConstraint.isLineOfSightBased()) + { + return aConstraint.getLineOfSightConstraint().value().isSatisfied(anInstant, fromPosition, toPosition); } const AER aer = GeneratorContext::CalculateAer(anInstant, fromPosition, toPosition, earthSPtr_); - return aConstraint.isSatisfied(aer); + if (aConstraint.isMaskBased()) + { + return aConstraint.getMaskConstraint().value().isSatisfied(aer); + } + + if (aConstraint.isIntervalBased()) + { + return aConstraint.getIntervalConstraint().value().isSatisfied(aer); + } + + return false; } Pair GeneratorContext::GetStatesAt( From 2a14e4405ee7e52ae7a178150943babd6505922f Mon Sep 17 00:00:00 2001 From: vishwa shah Date: Tue, 19 Nov 2024 03:49:40 +0000 Subject: [PATCH 09/22] wip: --- .../Astrodynamics/Access/Constraint.hpp | 7 +- .../Astrodynamics/Access/Constraint.cpp | 8 +- .../Astrodynamics/Access/Generator.cpp | 205 +++++++++++++----- 3 files changed, 154 insertions(+), 66 deletions(-) diff --git a/include/OpenSpaceToolkit/Astrodynamics/Access/Constraint.hpp b/include/OpenSpaceToolkit/Astrodynamics/Access/Constraint.hpp index 145093413..d877ee60b 100644 --- a/include/OpenSpaceToolkit/Astrodynamics/Access/Constraint.hpp +++ b/include/OpenSpaceToolkit/Astrodynamics/Access/Constraint.hpp @@ -28,9 +28,8 @@ using ostk::core::container::Map; using ostk::core::type::Real; using ostk::mathematics::object::Interval; -using ostk::mathematics::object::Vector2d; +using ostk::mathematics::object::Vector3d; -using ostk::physics::coordinate::Position; using ostk::physics::coordinate::spherical::AER; using ostk::physics::Environment; using ostk::physics::time::Instant; @@ -67,7 +66,9 @@ class Constraint LineOfSightConstraint(const Environment& anEnvironment); - bool isSatisfied(const Instant& anInstant, const Position& aFromPosition, const Position& aToPosition) const; + bool isSatisfied( + const Instant& anInstant, const Vector3d& aFromPositionCoordinates, const Vector3d& aToPositionCoordinates + ) const; }; static Constraint FromIntervals( diff --git a/src/OpenSpaceToolkit/Astrodynamics/Access/Constraint.cpp b/src/OpenSpaceToolkit/Astrodynamics/Access/Constraint.cpp index f5bb08419..fca25f811 100644 --- a/src/OpenSpaceToolkit/Astrodynamics/Access/Constraint.cpp +++ b/src/OpenSpaceToolkit/Astrodynamics/Access/Constraint.cpp @@ -16,6 +16,8 @@ namespace access using ostk::core::type::Shared; +using ostk::mathematics::object::Vector2d; + using ostk::mathematics::geometry::d3::object::Point; using ostk::mathematics::geometry::d3::object::Segment; @@ -146,15 +148,15 @@ Constraint::LineOfSightConstraint::LineOfSightConstraint(const Environment& anEn } bool Constraint::LineOfSightConstraint::isSatisfied( - const Instant& anInstant, const Position& aFromPosition, const Position& aToPosition + const Instant& anInstant, const Vector3d& aFromPositionCoordinates, const Vector3d& aToPositionCoordinates ) const { static const Shared commonFrameSPtr = Frame::GCRF(); this->environment.setInstant(anInstant); - const Point fromPositionCoordinates = Point::Vector(aFromPosition.accessCoordinates()); - const Point toPositionCoordinates = Point::Vector(aToPosition.accessCoordinates()); + const Point fromPositionCoordinates = Point::Vector(aFromPositionCoordinates); + const Point toPositionCoordinates = Point::Vector(aToPositionCoordinates); if (fromPositionCoordinates == toPositionCoordinates) { diff --git a/src/OpenSpaceToolkit/Astrodynamics/Access/Generator.cpp b/src/OpenSpaceToolkit/Astrodynamics/Access/Generator.cpp index 18b05fda3..133cfd723 100644 --- a/src/OpenSpaceToolkit/Astrodynamics/Access/Generator.cpp +++ b/src/OpenSpaceToolkit/Astrodynamics/Access/Generator.cpp @@ -375,6 +375,7 @@ Array> Generator::computeAccessesForFixedTargets( } // create a stacked matrix of ITRF positions for all ground targets + MatrixXd fromPositionCoordinates_ITRF = MatrixXd::Zero(3, targetCount); for (Index i = 0; i < targetCount; ++i) @@ -390,10 +391,59 @@ Array> Generator::computeAccessesForFixedTargets( return accessTarget.getConstraint().isMaskBased(); } ); + const bool allAccessTargetsHaveIntervals = std::all_of( + someAccessTargets.begin(), + someAccessTargets.end(), + [](const auto& accessTarget) + { + return accessTarget.getConstraint().isIntervalBased(); + } + ); + const bool allAccessTargetsHaveLineOfSight = std::all_of( + someAccessTargets.begin(), + someAccessTargets.end(), + [](const auto& accessTarget) + { + return accessTarget.getConstraint().isLineOfSightBased(); + } + ); + + const auto computeAer = [&SEZRotations, &fromPositionCoordinates_ITRF](const Vector3d& aToPositionCoordinates_ITRF + ) -> Triple + { + const MatrixXd dx = (-fromPositionCoordinates_ITRF).colwise() + aToPositionCoordinates_ITRF; + + // TBI: See if this can be vectorized + MatrixXd dx_SEZ = MatrixXd::Zero(3, dx.cols()); + for (Eigen::Index i = 0; i < dx.cols(); ++i) + { + dx_SEZ.col(i) = SEZRotations.block<3, 3>(0, 3 * i) * dx.col(i); + } + + // calculate azimuth, elevation, and range + const VectorXd range_m = dx_SEZ.colwise().norm(); + const VectorXd elevation_rad = (dx_SEZ.row(2).transpose().array() / range_m.array()).asin(); + VectorXd azimuth_rad = dx_SEZ.row(0).array().binaryExpr( + dx_SEZ.row(1).array(), + [](double x, double y) + { + return std::atan2(y, -x); + } + ); + + azimuth_rad = azimuth_rad.unaryExpr( + [](double x) + { + return x < 0.0 ? x + 2.0 * M_PI : x; + } + ); + + return {azimuth_rad, elevation_rad, range_m}; + }; - std::function aerFilter; + std::function constraintFilter; - if (!allAccessTargetsHaveMasks) + if (allAccessTargetsHaveIntervals) { // create a stacked matrix of azimuth, elevation, and range bounds for all ground targets MatrixXd aerLowerBounds = MatrixXd::Zero(targetCount, 3); @@ -415,9 +465,17 @@ Array> Generator::computeAccessesForFixedTargets( aerUpperBounds(i, 2) = intervalConstraint.range.accessUpperBound(); } - aerFilter = [aerLowerBounds, - aerUpperBounds](const VectorXd& azimuths, const VectorXd& elevations, const VectorXd& ranges) + constraintFilter = [aerLowerBounds, aerUpperBounds, &computeAer]( + const MatrixXd& aFromPositionCoordinates_ITRF, + const Vector3d& aToPositionCoordinates_ITRF, + const Instant& anInstant + ) { + (void)anInstant; + (void)aFromPositionCoordinates_ITRF; + + const auto [azimuths, elevations, ranges] = computeAer(aToPositionCoordinates_ITRF); + return (azimuths.array() > aerLowerBounds.col(0).array() && azimuths.array() < aerUpperBounds.col(0).array() && elevations.array() > aerLowerBounds.col(1).array() && @@ -426,7 +484,7 @@ Array> Generator::computeAccessesForFixedTargets( .eval(); }; } - else + else if (allAccessTargetsHaveMasks) { VectorXd rangeLowerBounds = VectorXd::Zero(targetCount, 1); VectorXd rangeUpperBounds = VectorXd::Zero(targetCount, 1); @@ -440,10 +498,16 @@ Array> Generator::computeAccessesForFixedTargets( rangeUpperBounds(i) = constraint.range.accessUpperBound(); } - aerFilter = [&someAccessTargets, rangeLowerBounds, rangeUpperBounds]( - const VectorXd& azimuths_rad, const VectorXd& elevations_rad, const VectorXd& ranges_m - ) + constraintFilter = [&someAccessTargets, &computeAer, rangeLowerBounds, rangeUpperBounds]( + const MatrixXd& aFromPositionCoordinates_ITRF, + const Vector3d& aToPositionCoordinates_ITRF, + const Instant& anInstant + ) { + (void)anInstant; + (void)aFromPositionCoordinates_ITRF; + + const auto [azimuths_rad, elevations_rad, ranges_m] = computeAer(aToPositionCoordinates_ITRF); ArrayXb mask(azimuths_rad.rows()); mask = ranges_m.array() > rangeLowerBounds.array() && ranges_m.array() < rangeUpperBounds.array(); @@ -464,11 +528,39 @@ Array> Generator::computeAccessesForFixedTargets( return mask; }; } + else if (allAccessTargetsHaveLineOfSight) + { + constraintFilter = [&someAccessTargets]( + const MatrixXd& aFromPositionCoordinates_ITRF, + const Vector3d& aToPositionCoordinates_ITRF, + const Instant& anInstant + ) + { + ArrayXb mask(aFromPositionCoordinates_ITRF.cols()); + + for (Eigen::Index i = 0; i < mask.rows(); ++i) + { + const Constraint::LineOfSightConstraint constraint = + someAccessTargets[i].getConstraint().getLineOfSightConstraint().value(); + + const Vector3d& fromPositionCoordinate_ITRF = aFromPositionCoordinates_ITRF.col(i); + + mask(i) = constraint.isSatisfied(anInstant, fromPositionCoordinate_ITRF, aToPositionCoordinates_ITRF); + } + + return mask; + }; + } + else + { + throw ostk::core::error::RuntimeError("All access targets must have the same type of constraint."); + } const Array instants = anInterval.generateGrid(this->step_); MatrixXi inAccessPerTarget = MatrixXi::Zero(instants.getSize(), targetCount); + std::chrono::steady_clock::time_point start = std::chrono::steady_clock::now(); for (Index index = 0; index < instants.getSize(); ++index) { const Instant& instant = instants[index]; @@ -477,38 +569,18 @@ Array> Generator::computeAccessesForFixedTargets( const Vector3d toPositionCoordinates_ITRF = aToTrajectory.getStateAt(instant).inFrame(Frame::ITRF()).getPosition().getCoordinates(); - const MatrixXd dx = (-fromPositionCoordinates_ITRF).colwise() + toPositionCoordinates_ITRF; - - // calculate target to satellite vector in SEZ - MatrixXd dx_SEZ = MatrixXd::Zero(3, dx.cols()); - for (Eigen::Index i = 0; i < dx.cols(); ++i) - { - dx_SEZ.col(i) = SEZRotations.block<3, 3>(0, 3 * i) * dx.col(i); - } - - // calculate azimuth, elevation, and range - const VectorXd range_m = dx_SEZ.colwise().norm(); - const VectorXd elevation_rad = (dx_SEZ.row(2).transpose().array() / range_m.array()).asin(); - VectorXd azimuth_rad = dx_SEZ.row(0).array().binaryExpr( - dx_SEZ.row(1).array(), - [](double x, double y) - { - return std::atan2(y, -x); - } - ); - - azimuth_rad = azimuth_rad.unaryExpr( - [](double x) - { - return x < 0.0 ? x + 2.0 * M_PI : x; - } - ); - // check if satellite is in access - const auto inAccess = aerFilter(azimuth_rad, elevation_rad, range_m); + const auto inAccess = constraintFilter(fromPositionCoordinates_ITRF, toPositionCoordinates_ITRF, instant); + inAccessPerTarget.row(index) = inAccess.cast().transpose(); } + std::chrono::steady_clock::time_point end = std::chrono::steady_clock::now(); + + std::cout << "Access checks took " << std::chrono::duration_cast(end - start).count() + << "ms" << std::endl; + + start = std::chrono::steady_clock::now(); Array> accessIntervalsPerTarget = Array>(targetCount, Array::Empty()); @@ -517,6 +589,12 @@ Array> Generator::computeAccessesForFixedTargets( accessIntervalsPerTarget[i] = ComputeIntervals(inAccessPerTarget.col(i), instants); } + end = std::chrono::steady_clock::now(); + + std::cout << "Computing intervals took " + << std::chrono::duration_cast(end - start).count() << "ms" << std::endl; + + start = std::chrono::steady_clock::now(); if (!coarse) { for (Index i = 0; i < accessIntervalsPerTarget.getSize(); ++i) @@ -530,7 +608,12 @@ Array> Generator::computeAccessesForFixedTargets( ); } } + end = std::chrono::steady_clock::now(); + std::cout << "Computing precise crossings took " + << std::chrono::duration_cast(end - start).count() << "ms" << std::endl; + + start = std::chrono::steady_clock::now(); Array> accesses = Array>(targetCount, Array::Empty()); for (Index i = 0; i < accessIntervalsPerTarget.getSize(); ++i) { @@ -538,6 +621,10 @@ Array> Generator::computeAccessesForFixedTargets( accesses[i] = this->generateAccessesFromIntervals(accessIntervalsPerTarget[i], anInterval, fromTrajectory, aToTrajectory); } + end = std::chrono::steady_clock::now(); + + std::cout << "Generating accesses took " + << std::chrono::duration_cast(end - start).count() << "ms" << std::endl; return accesses; } @@ -625,7 +712,7 @@ Array Generator::computePreciseCrossings( range > rangeLowerBound && range < rangeUpperBound; }; } - else + else if (anAccessTarget.getConstraint().isMaskBased()) { const Constraint::MaskConstraint maskConstraint = anAccessTarget.getConstraint().getMaskConstraint().value(); @@ -641,6 +728,24 @@ Array Generator::computePreciseCrossings( return maskConstraint.isSatisfied(aer) && range_m > rangeLowerBound && range_m < rangeUpperBound; }; } + else if (anAccessTarget.getConstraint().isLineOfSightBased()) + { + const Constraint::LineOfSightConstraint lineOfSightConstraint = + anAccessTarget.getConstraint().getLineOfSightConstraint().value(); + + condition = [&fromPositionCoordinate_ITRF, &aToTrajectory, lineOfSightConstraint](const Instant& instant + ) -> bool + { + const Vector3d toPositionCoordinates_ITRF = + aToTrajectory.getStateAt(instant).inFrame(Frame::ITRF()).getPosition().getCoordinates(); + + return lineOfSightConstraint.isSatisfied(instant, fromPositionCoordinate_ITRF, toPositionCoordinates_ITRF); + }; + } + else + { + throw ostk::core::error::RuntimeError("Constraint type not supported."); + } Array preciseAccessIntervals = accessIntervals; @@ -903,31 +1008,11 @@ bool GeneratorContext::isAccessActive(const Instant& anInstant, const Constraint const auto [fromPosition, toPosition] = GeneratorContext::GetPositionsFromStates(fromState, toState); - // Line of sight - // TBI: Remove this check as it is redundant - - // static const Shared commonFrameSPtr = Frame::GCRF(); - - // const Point fromPositionCoordinates = Point::Vector(fromPosition.accessCoordinates()); - // const Point toPositionCoordinates = Point::Vector(toPosition.accessCoordinates()); - - // if (fromPositionCoordinates != toPositionCoordinates) - // { - // const Segment fromToSegment = {fromPositionCoordinates, toPositionCoordinates}; - - // const Object::Geometry fromToSegmentGeometry = {fromToSegment, commonFrameSPtr}; - - // const bool lineOfSight = !this->environment_.intersects(fromToSegmentGeometry); - - // if (!lineOfSight) - // { - // return false; - // } - // } - if (aConstraint.isLineOfSightBased()) { - return aConstraint.getLineOfSightConstraint().value().isSatisfied(anInstant, fromPosition, toPosition); + return aConstraint.getLineOfSightConstraint().value().isSatisfied( + anInstant, fromPosition.accessCoordinates(), toPosition.accessCoordinates() + ); } const AER aer = GeneratorContext::CalculateAer(anInstant, fromPosition, toPosition, earthSPtr_); From 770972631b63b522f26c71142fcb500082705cb5 Mon Sep 17 00:00:00 2001 From: vishwa shah Date: Sun, 24 Nov 2024 20:19:05 +0000 Subject: [PATCH 10/22] wip --- .../Astrodynamics/Access/Constraint.hpp | 18 ++++----- .../Astrodynamics/Access/Constraint.cpp | 18 ++++----- .../Astrodynamics/Access/Generator.cpp | 16 ++++---- .../Astrodynamics/Access/Generator.test.cpp | 40 ++++++++++--------- 4 files changed, 47 insertions(+), 45 deletions(-) diff --git a/include/OpenSpaceToolkit/Astrodynamics/Access/Constraint.hpp b/include/OpenSpaceToolkit/Astrodynamics/Access/Constraint.hpp index d877ee60b..b97c32f42 100644 --- a/include/OpenSpaceToolkit/Astrodynamics/Access/Constraint.hpp +++ b/include/OpenSpaceToolkit/Astrodynamics/Access/Constraint.hpp @@ -37,13 +37,13 @@ using ostk::physics::time::Instant; class Constraint { public: - struct IntervalConstraint + struct AERIntervalConstraint { Interval azimuth; // degrees Interval elevation; // degrees Interval range; // meters - IntervalConstraint( + AERIntervalConstraint( const Interval& anAzimuth, const Interval& anElevation, const Interval& aRange ); @@ -71,7 +71,7 @@ class Constraint ) const; }; - static Constraint FromIntervals( + static Constraint FromAERIntervals( const Interval& azimuth, const Interval& elevation, const Interval& range ); @@ -81,24 +81,24 @@ class Constraint bool isMaskBased() const; - bool isIntervalBased() const; + bool isAERIntervalBased() const; bool isLineOfSightBased() const; - std::optional getIntervalConstraint() const; + std::optional getAERIntervalConstraint() const; std::optional getMaskConstraint() const; std::optional getLineOfSightConstraint() const; private: - std::variant constraint_; + std::variant constraint_; - explicit Constraint(const IntervalConstraint& constraint); + explicit Constraint(const AERIntervalConstraint& aConstraint); - explicit Constraint(const MaskConstraint& constraint); + explicit Constraint(const MaskConstraint& aConstraint); - explicit Constraint(const LineOfSightConstraint& constraint); + explicit Constraint(const LineOfSightConstraint& aConstraint); }; } // namespace access diff --git a/src/OpenSpaceToolkit/Astrodynamics/Access/Constraint.cpp b/src/OpenSpaceToolkit/Astrodynamics/Access/Constraint.cpp index fca25f811..01bd2c405 100644 --- a/src/OpenSpaceToolkit/Astrodynamics/Access/Constraint.cpp +++ b/src/OpenSpaceToolkit/Astrodynamics/Access/Constraint.cpp @@ -24,7 +24,7 @@ using ostk::mathematics::geometry::d3::object::Segment; using ostk::physics::coordinate::Frame; using ostk::physics::environment::Object; -Constraint::IntervalConstraint::IntervalConstraint( +Constraint::AERIntervalConstraint::AERIntervalConstraint( const Interval& anAzimuthInterval, const Interval& anElevationInterval, const Interval& aRangeInterval @@ -64,7 +64,7 @@ Constraint::IntervalConstraint::IntervalConstraint( } } -bool Constraint::IntervalConstraint::isSatisfied(const AER& anAer) const +bool Constraint::AERIntervalConstraint::isSatisfied(const AER& anAer) const { return azimuth.contains(anAer.getAzimuth().inDegrees()) && elevation.contains(anAer.getElevation().inDegrees()) && range.contains(anAer.getRange().inMeters()); @@ -170,11 +170,11 @@ bool Constraint::LineOfSightConstraint::isSatisfied( return !this->environment.intersects(fromToSegmentGeometry); } -Constraint Constraint::FromIntervals( +Constraint Constraint::FromAERIntervals( const Interval& azimuth, const Interval& elevation, const Interval& range ) { - return Constraint(IntervalConstraint {azimuth, elevation, range}); + return Constraint(AERIntervalConstraint {azimuth, elevation, range}); } Constraint Constraint::FromMask(const Map& azimuthElevationMask, const Interval& range) @@ -192,9 +192,9 @@ bool Constraint::isMaskBased() const return std::holds_alternative(constraint_); } -bool Constraint::isIntervalBased() const +bool Constraint::isAERIntervalBased() const { - return std::holds_alternative(constraint_); + return std::holds_alternative(constraint_); } bool Constraint::isLineOfSightBased() const @@ -202,9 +202,9 @@ bool Constraint::isLineOfSightBased() const return std::holds_alternative(constraint_); } -std::optional Constraint::getIntervalConstraint() const +std::optional Constraint::getAERIntervalConstraint() const { - if (const auto* interval = std::get_if(&constraint_)) + if (const auto* interval = std::get_if(&constraint_)) { return *interval; } @@ -232,7 +232,7 @@ std::optional Constraint::getLineOfSightConst return std::nullopt; } -Constraint::Constraint(const IntervalConstraint& constraint) +Constraint::Constraint(const AERIntervalConstraint& constraint) : constraint_(constraint) { } diff --git a/src/OpenSpaceToolkit/Astrodynamics/Access/Generator.cpp b/src/OpenSpaceToolkit/Astrodynamics/Access/Generator.cpp index 133cfd723..bb87f654a 100644 --- a/src/OpenSpaceToolkit/Astrodynamics/Access/Generator.cpp +++ b/src/OpenSpaceToolkit/Astrodynamics/Access/Generator.cpp @@ -396,7 +396,7 @@ Array> Generator::computeAccessesForFixedTargets( someAccessTargets.end(), [](const auto& accessTarget) { - return accessTarget.getConstraint().isIntervalBased(); + return accessTarget.getConstraint().isAERIntervalBased(); } ); const bool allAccessTargetsHaveLineOfSight = std::all_of( @@ -453,8 +453,8 @@ Array> Generator::computeAccessesForFixedTargets( for (Index i = 0; i < targetCount; ++i) { - const Constraint::IntervalConstraint intervalConstraint = - someAccessTargets[i].getConstraint().getIntervalConstraint().value(); + const Constraint::AERIntervalConstraint intervalConstraint = + someAccessTargets[i].getConstraint().getAERIntervalConstraint().value(); aerLowerBounds(i, 0) = intervalConstraint.azimuth.accessLowerBound() * degToRad; aerLowerBounds(i, 1) = intervalConstraint.elevation.accessLowerBound() * degToRad; @@ -691,10 +691,10 @@ Array Generator::computePreciseCrossings( return {azimuth_rad, elevation_rad, range_m}; }; - if (anAccessTarget.getConstraint().isIntervalBased()) + if (anAccessTarget.getConstraint().isAERIntervalBased()) { - const Constraint::IntervalConstraint intervalConstraint = - anAccessTarget.getConstraint().getIntervalConstraint().value(); + const Constraint::AERIntervalConstraint intervalConstraint = + anAccessTarget.getConstraint().getAERIntervalConstraint().value(); condition = [&computeAER, intervalConstraint](const Instant& instant) -> bool { @@ -1022,9 +1022,9 @@ bool GeneratorContext::isAccessActive(const Instant& anInstant, const Constraint return aConstraint.getMaskConstraint().value().isSatisfied(aer); } - if (aConstraint.isIntervalBased()) + if (aConstraint.isAERIntervalBased()) { - return aConstraint.getIntervalConstraint().value().isSatisfied(aer); + return aConstraint.getAERIntervalConstraint().value().isSatisfied(aer); } return false; diff --git a/test/OpenSpaceToolkit/Astrodynamics/Access/Generator.test.cpp b/test/OpenSpaceToolkit/Astrodynamics/Access/Generator.test.cpp index 0ca62f862..be3def2af 100644 --- a/test/OpenSpaceToolkit/Astrodynamics/Access/Generator.test.cpp +++ b/test/OpenSpaceToolkit/Astrodynamics/Access/Generator.test.cpp @@ -238,7 +238,7 @@ TEST_F(OpenSpaceToolkit_Astrodynamics_Access_Generator, GetConditionFunction) const Orbit fromOrbit = generateFirstOrbit(); const Orbit toOrbit = generateSecondOrbit(); - const Constraint constraint = Constraint::FromIntervals( + const Constraint constraint = Constraint::FromAERIntervals( ostk::mathematics::object::Interval::Closed(0.0, 360.0), ostk::mathematics::object::Interval::Closed(-90.0, 90.0), ostk::mathematics::object::Interval::Closed(0.0, 1.0e10) @@ -319,7 +319,7 @@ TEST_F(OpenSpaceToolkit_Astrodynamics_Access_Generator, ComputeAccesses_1) const Orbit fromOrbit = generateFirstOrbit(); const Orbit toOrbit = generateSecondOrbit(); - const Constraint constraint = Constraint::FromIntervals( + const Constraint constraint = Constraint::FromAERIntervals( ostk::mathematics::object::Interval::Closed(0.0, 360.0), ostk::mathematics::object::Interval::Closed(-90.0, 90.0), ostk::mathematics::object::Interval::Closed(0.0, 1.0e10) @@ -406,7 +406,7 @@ TEST_F(OpenSpaceToolkit_Astrodynamics_Access_Generator, ComputeAccesses_2) return orbit; }; - const Constraint constraint = Constraint::FromIntervals( + const Constraint constraint = Constraint::FromAERIntervals( ostk::mathematics::object::Interval::Closed(0.0, 360.0), ostk::mathematics::object::Interval::Closed(-90.0, 90.0), ostk::mathematics::object::Interval::Closed(0.0, 1.0e10) @@ -483,7 +483,7 @@ TEST_F(OpenSpaceToolkit_Astrodynamics_Access_Generator, ComputeAccesses_3) }; // TBI: Convert to LOS trajectory - const Constraint constraint = Constraint::FromIntervals( + const Constraint constraint = Constraint::FromAERIntervals( ostk::mathematics::object::Interval::Closed(0.0, 360.0), ostk::mathematics::object::Interval::Closed(-90.0, 90.0), ostk::mathematics::object::Interval::Closed(0.0, 1.0e10) @@ -572,7 +572,7 @@ TEST_F(OpenSpaceToolkit_Astrodynamics_Access_Generator, ComputeAccesses_4) ); const AccessTarget accessTarget = AccessTarget::FromTrajectory( - Constraint::FromIntervals( + Constraint::FromAERIntervals( ostk::mathematics::object::Interval::Closed(0.0, 360.0), ostk::mathematics::object::Interval::Closed(-90.0, 90.0), ostk::mathematics::object::Interval::Closed(0.0, 1.0e10) @@ -645,13 +645,15 @@ TEST_F(OpenSpaceToolkit_Astrodynamics_Access_Generator, AccessTarget) // Constructor { { - const Constraint constraint = Constraint::FromIntervals(azimuthInterval, elevationInterval, rangeInterval); + const Constraint constraint = + Constraint::FromAERIntervals(azimuthInterval, elevationInterval, rangeInterval); EXPECT_NO_THROW(AccessTarget::FromLLA(constraint, lla, defaultEarthSPtr_)); } { - const Constraint constraint = Constraint::FromIntervals(azimuthInterval, elevationInterval, rangeInterval); + const Constraint constraint = + Constraint::FromAERIntervals(azimuthInterval, elevationInterval, rangeInterval); { const Position position = Position::Meters({0.0, 0.0, 0.0}, Frame::ITRF()); EXPECT_NO_THROW(AccessTarget::FromPosition(constraint, position)); @@ -669,7 +671,7 @@ TEST_F(OpenSpaceToolkit_Astrodynamics_Access_Generator, AccessTarget) EXPECT_THROW( AccessTarget::FromPosition( - Constraint::FromIntervals( + Constraint::FromAERIntervals( ostk::mathematics::object::Interval::Undefined(), elevationInterval, rangeInterval ), position @@ -678,7 +680,7 @@ TEST_F(OpenSpaceToolkit_Astrodynamics_Access_Generator, AccessTarget) ); EXPECT_THROW( AccessTarget::FromPosition( - Constraint::FromIntervals( + Constraint::FromAERIntervals( azimuthInterval, ostk::mathematics::object::Interval::Undefined(), rangeInterval ), position @@ -687,7 +689,7 @@ TEST_F(OpenSpaceToolkit_Astrodynamics_Access_Generator, AccessTarget) ); EXPECT_THROW( AccessTarget::FromPosition( - Constraint::FromIntervals( + Constraint::FromAERIntervals( azimuthInterval, elevationInterval, ostk::mathematics::object::Interval::Undefined() ), position @@ -696,7 +698,7 @@ TEST_F(OpenSpaceToolkit_Astrodynamics_Access_Generator, AccessTarget) ); EXPECT_THROW( AccessTarget::FromLLA( - Constraint::FromIntervals(azimuthInterval, elevationInterval, rangeInterval), + Constraint::FromAERIntervals(azimuthInterval, elevationInterval, rangeInterval), LLA::Undefined(), defaultEarthSPtr_ ), @@ -709,7 +711,7 @@ TEST_F(OpenSpaceToolkit_Astrodynamics_Access_Generator, AccessTarget) { EXPECT_THROW( AccessTarget::FromLLA( - Constraint::FromIntervals( + Constraint::FromAERIntervals( ostk::mathematics::object::Interval::Closed(-1.0, 350.0), elevationInterval, rangeInterval @@ -721,7 +723,7 @@ TEST_F(OpenSpaceToolkit_Astrodynamics_Access_Generator, AccessTarget) ); EXPECT_THROW( AccessTarget::FromLLA( - Constraint::FromIntervals( + Constraint::FromAERIntervals( ostk::mathematics::object::Interval::Closed(0.0, 360.1), elevationInterval, rangeInterval @@ -735,7 +737,7 @@ TEST_F(OpenSpaceToolkit_Astrodynamics_Access_Generator, AccessTarget) { EXPECT_THROW( AccessTarget::FromLLA( - Constraint::FromIntervals( + Constraint::FromAERIntervals( azimuthInterval, ostk::mathematics::object::Interval::Closed(-91.0, 0.0), rangeInterval @@ -747,7 +749,7 @@ TEST_F(OpenSpaceToolkit_Astrodynamics_Access_Generator, AccessTarget) ); EXPECT_THROW( AccessTarget::FromLLA( - Constraint::FromIntervals( + Constraint::FromAERIntervals( azimuthInterval, ostk::mathematics::object::Interval::Closed(-45.0, 91.0), rangeInterval @@ -761,7 +763,7 @@ TEST_F(OpenSpaceToolkit_Astrodynamics_Access_Generator, AccessTarget) { EXPECT_THROW( AccessTarget::FromLLA( - Constraint::FromIntervals( + Constraint::FromAERIntervals( azimuthInterval, elevationInterval, ostk::mathematics::object::Interval::Closed(-1.0, 5.0) @@ -777,7 +779,7 @@ TEST_F(OpenSpaceToolkit_Astrodynamics_Access_Generator, AccessTarget) // Getters { - const Constraint constraint = Constraint::FromIntervals(azimuthInterval, elevationInterval, rangeInterval); + const Constraint constraint = Constraint::FromAERIntervals(azimuthInterval, elevationInterval, rangeInterval); const AccessTarget accessTarget = AccessTarget::FromLLA(constraint, lla, defaultEarthSPtr_); // TBI: Fix the checks below EXPECT_EQ(accessTarget.getLLA(defaultEarthSPtr_), lla); @@ -836,7 +838,7 @@ TEST_F(OpenSpaceToolkit_Astrodynamics_Access_Generator, ComputeAccesses) const ostk::mathematics::object::Interval rangeInterval = ostk::mathematics::object::Interval::Closed(0.0, 1.0e10); - const Constraint constraint = Constraint::FromIntervals(azimuthInterval, elevationInterval, rangeInterval); + const Constraint constraint = Constraint::FromAERIntervals(azimuthInterval, elevationInterval, rangeInterval); Array accessTargets = LLAs.map( [&constraint, this](const LLA& lla) -> AccessTarget @@ -955,7 +957,7 @@ TEST_F(OpenSpaceToolkit_Astrodynamics_Access_Generator, AerRanges_1) const ostk::mathematics::object::Interval rangeRange = ostk::mathematics::object::Interval::Closed(0.0, 10000e3); - const Constraint constraint = Constraint::FromIntervals(azimuthRange, elevationRange, rangeRange); + const Constraint constraint = Constraint::FromAERIntervals(azimuthRange, elevationRange, rangeRange); const Instant startInstant = Instant::DateTime(DateTime(2018, 1, 1, 0, 0, 0), Scale::UTC); const Instant endInstant = Instant::DateTime(DateTime(2018, 1, 10, 0, 0, 0), Scale::UTC); From 18f42d6e9b08a4673969e06d900ccb009386e321 Mon Sep 17 00:00:00 2001 From: vishwa shah Date: Sun, 24 Nov 2024 20:19:05 +0000 Subject: [PATCH 11/22] wip: add elevation only constraint --- .../Astrodynamics/Access/Constraint.hpp | 40 +++++++--- .../Astrodynamics/Access/Generator.hpp | 2 +- .../Astrodynamics/Access/Constraint.cpp | 79 ++++++++++++++----- .../Astrodynamics/Access/Generator.cpp | 77 +++++++++++++++--- 4 files changed, 156 insertions(+), 42 deletions(-) diff --git a/include/OpenSpaceToolkit/Astrodynamics/Access/Constraint.hpp b/include/OpenSpaceToolkit/Astrodynamics/Access/Constraint.hpp index b97c32f42..d86b67c0d 100644 --- a/include/OpenSpaceToolkit/Astrodynamics/Access/Constraint.hpp +++ b/include/OpenSpaceToolkit/Astrodynamics/Access/Constraint.hpp @@ -39,8 +39,8 @@ class Constraint public: struct AERIntervalConstraint { - Interval azimuth; // degrees - Interval elevation; // degrees + Interval azimuth; // radians + Interval elevation; // radians Interval range; // meters AERIntervalConstraint( @@ -48,16 +48,20 @@ class Constraint ); bool isSatisfied(const AER& anAer) const; + + bool isSatisifed(const Real& anAzimuthRadians, const Real& anElevationRadians, const Real& aRangeMeters) const; }; struct MaskConstraint { - Map azimuthElevationMask; // degrees, degrees + Map azimuthElevationMask; // radians, radians Interval range; // meters MaskConstraint(const Map& anAzimuthElevationMask, const Interval& aRange); bool isSatisfied(const AER& anAer) const; + + bool isSatisfied(const Real& anAzimuthRadians, const Real& anElevationRadians, const Real& aRangeMeters) const; }; struct LineOfSightConstraint @@ -71,34 +75,46 @@ class Constraint ) const; }; + struct ElevationIntervalConstraint + { + Interval elevation; // radians + + ElevationIntervalConstraint(const Interval& anElevationInterval); + + bool isSatisfied(const Real& anElevation) const; + }; + static Constraint FromAERIntervals( - const Interval& azimuth, const Interval& elevation, const Interval& range + const Interval& anAzimuthInterval, const Interval& anElevationInterval, const Interval& aRangeInterval ); - static Constraint FromMask(const Map& azimuthElevationMask, const Interval& range); + static Constraint FromMask(const Map& anAzimuthElevationMask, const Interval& aRangeInterval); - static Constraint FromLineOfSight(const Environment& environment); + static Constraint FromLineOfSight(const Environment& anEnvironment); - bool isMaskBased() const; + static Constraint FromElevationInterval(const Interval& anElevationInterval); bool isAERIntervalBased() const; + bool isMaskBased() const; + bool isLineOfSightBased() const; + bool isElevationIntervalBased() const; + std::optional getAERIntervalConstraint() const; std::optional getMaskConstraint() const; std::optional getLineOfSightConstraint() const; + std::optional getElevationIntervalConstraint() const; + private: std::variant constraint_; - explicit Constraint(const AERIntervalConstraint& aConstraint); - - explicit Constraint(const MaskConstraint& aConstraint); - - explicit Constraint(const LineOfSightConstraint& aConstraint); + template + explicit Constraint(const T& aConstraint); }; } // namespace access diff --git a/include/OpenSpaceToolkit/Astrodynamics/Access/Generator.hpp b/include/OpenSpaceToolkit/Astrodynamics/Access/Generator.hpp index 2059275a4..46f9a614b 100644 --- a/include/OpenSpaceToolkit/Astrodynamics/Access/Generator.hpp +++ b/include/OpenSpaceToolkit/Astrodynamics/Access/Generator.hpp @@ -58,7 +58,7 @@ using ostk::astrodynamics::trajectory::State; #define DEFAULT_STEP Duration::Minutes(1.0) #define DEFAULT_TOLERANCE Duration::Microseconds(1.0) -/// @brief Represents the configuration for a ground target, including azimuth, elevation, and range intervals, as well +/// @brief Represents the configuration for an Access target, including azimuth, elevation, and range intervals, as well /// as position and LLA (Latitude, Longitude, Altitude). /// /// @details This class provides methods to retrieve the trajectory, position, LLA, and intervals for azimuth, diff --git a/src/OpenSpaceToolkit/Astrodynamics/Access/Constraint.cpp b/src/OpenSpaceToolkit/Astrodynamics/Access/Constraint.cpp index 01bd2c405..aef6c92ec 100644 --- a/src/OpenSpaceToolkit/Astrodynamics/Access/Constraint.cpp +++ b/src/OpenSpaceToolkit/Astrodynamics/Access/Constraint.cpp @@ -62,12 +62,20 @@ Constraint::AERIntervalConstraint::AERIntervalConstraint( "The range interval [{}, {}] must be positive.", range.accessLowerBound(), range.accessUpperBound() ); } + + azimuth = Interval(anAzimuthInterval.getLowerBound() * M_PI/180.0, anAzimuthInterval.getUpperBound() * M_PI/180.0, anAzimuthInterval.getType()); + elevation = Interval(anElevationInterval.getLowerBound() * M_PI/180.0, anElevationInterval.getUpperBound() * M_PI/180.0, anAzimuthInterval.getType()); } bool Constraint::AERIntervalConstraint::isSatisfied(const AER& anAer) const { - return azimuth.contains(anAer.getAzimuth().inDegrees()) && elevation.contains(anAer.getElevation().inDegrees()) && - range.contains(anAer.getRange().inMeters()); + return isSatisfied(anAer.getAzimuth().inRadians(), anAer.getElevation().inRadians(), anAer.getRange().inMeters()); +} + +bool Constraint::AERIntervalConstraint::isSatisfied(const Real& anAzimuthRadians, const Real& anElevationRadians, const Real& aRangeMeters) const +{ + return azimuth.contains(anAzimuthRadians) && elevation.contains(anElevationRadians) && + range.contains(aRangeMeters); } Constraint::MaskConstraint::MaskConstraint( @@ -121,12 +129,14 @@ Constraint::MaskConstraint::MaskConstraint( bool Constraint::MaskConstraint::isSatisfied(const AER& anAer) const { - const Real azimuth_rad = anAer.getAzimuth().inRadians(0.0, Real::TwoPi()); - const Real elevation_rad = anAer.getElevation().inRadians(-Real::Pi(), Real::Pi()); + return isSatisfied(anAer.getAzimuth().inRadians(0.0, Real::TwoPi()), anAer.getElevation().inRadians(-Real::Pi(), Real::Pi()), anAer.getRange().inMeters()); +} - auto itLow = this->azimuthElevationMask.lower_bound(azimuth_rad); +bool Constraint::MaskConstraint::isSatisfied(const Real anAzimuthRadians&, const Real& anElevationRadians, const Real& aRangeMeters) const +{ + auto itLow = this->azimuthElevationMask.lower_bound(anAzimuthRadians); itLow--; - auto itUp = this->azimuthElevationMask.upper_bound(azimuth_rad); + auto itUp = this->azimuthElevationMask.upper_bound(anAzimuthRadians); // Vector between the two successive mask data points with bounding azimuth values @@ -134,12 +144,12 @@ bool Constraint::MaskConstraint::isSatisfied(const AER& anAer) const // Vector from data point with azimuth lower bound to tested point - const Vector2d lowToPointVector = {azimuth_rad - itLow->first, elevation_rad - itLow->second}; + const Vector2d lowToPointVector = {anAzimuthRadians - itLow->first, anElevationRadians - itLow->second}; // If the determinant of these two vectors is positive, the tested point lies above the function defined by the // mask - return ((lowToUpVector[0] * lowToPointVector[1] - lowToUpVector[1] * lowToPointVector[0]) >= 0.0); + return ((lowToUpVector[0] * lowToPointVector[1] - lowToUpVector[1] * lowToPointVector[0]) >= 0.0) && aRange.contains(aRangeMeters); } Constraint::LineOfSightConstraint::LineOfSightConstraint(const Environment& anEnvironment) @@ -170,6 +180,26 @@ bool Constraint::LineOfSightConstraint::isSatisfied( return !this->environment.intersects(fromToSegmentGeometry); } +Constraint::ElevationIntervalConstraint::ElevationIntervalConstraint(const Interval& anElevationInterval) + : elevation(anElevationInterval) +{ + if (elevation.getLowerBound() < -90.0 || elevation.getUpperBound() > 90.0) + { + throw ostk::core::error::RuntimeError( + "The elevation interval [{}, {}] must be in the range [-90, 90] deg.", + elevation.accessLowerBound(), + elevation.accessUpperBound() + ); + } + + elevation = Interval(anElevationInterval.getLowerBound() * M_PI/180.0, anElevationInterval.getUpperBound() * M_PI/180.0, anAzimuthInterval.getType()); +} + +bool Constraint::ElevationIntervalConstraint::isSatisfied(const Real& anElevation) const +{ + return elevation.contains(anElevation); +} + Constraint Constraint::FromAERIntervals( const Interval& azimuth, const Interval& elevation, const Interval& range ) @@ -187,9 +217,9 @@ Constraint Constraint::FromLineOfSight(const Environment& environment) return Constraint(LineOfSightConstraint {environment}); } -bool Constraint::isMaskBased() const +Constraint Constraint::FromElevationInterval(const Interval& anElevationInterval) { - return std::holds_alternative(constraint_); + return Constraint(ElevationIntervalConstraint {anElevationInterval}); } bool Constraint::isAERIntervalBased() const @@ -197,16 +227,26 @@ bool Constraint::isAERIntervalBased() const return std::holds_alternative(constraint_); } +bool Constraint::isMaskBased() const +{ + return std::holds_alternative(constraint_); +} + bool Constraint::isLineOfSightBased() const { return std::holds_alternative(constraint_); } +bool Constraint::isElevationIntervalBased() const +{ + return std::holds_alternative(constraint_); +} + std::optional Constraint::getAERIntervalConstraint() const { - if (const auto* interval = std::get_if(&constraint_)) + if (const auto* aerInterval = std::get_if(&constraint_)) { - return *interval; + return *aerInterval; } return std::nullopt; @@ -232,17 +272,18 @@ std::optional Constraint::getLineOfSightConst return std::nullopt; } -Constraint::Constraint(const AERIntervalConstraint& constraint) - : constraint_(constraint) +std::optional Constraint::getElevationIntervalConstraint() const { -} + if (const auto* elevationInterval = std::get_if(&constraint_)) + { + return *elevationInterval; + } -Constraint::Constraint(const MaskConstraint& constraint) - : constraint_(constraint) -{ + return std::nullopt; } -Constraint::Constraint(const LineOfSightConstraint& constraint) +template +Constraint::Constraint(const T& constraint) : constraint_(constraint) { } diff --git a/src/OpenSpaceToolkit/Astrodynamics/Access/Generator.cpp b/src/OpenSpaceToolkit/Astrodynamics/Access/Generator.cpp index bb87f654a..bd2b8181d 100644 --- a/src/OpenSpaceToolkit/Astrodynamics/Access/Generator.cpp +++ b/src/OpenSpaceToolkit/Astrodynamics/Access/Generator.cpp @@ -284,7 +284,8 @@ Array> Generator::computeAccesses( return accessesPerTarget; } - else if (std::all_of( + + if (std::all_of( someAccessTargets.begin(), someAccessTargets.end(), [](const auto& accessTarget) @@ -358,20 +359,24 @@ Array> Generator::computeAccessesForFixedTargets( ) const { std::cout << "Computing accesses for fixed targets" << std::endl; + if (stateFilter_) { throw ostk::core::error::RuntimeError("State filter is not supported for multiple ground targets."); } // create a stacked matrix of SEZ rotations for all ground targets + const Index targetCount = someAccessTargets.getSize(); Eigen::Matrix SEZRotations(3, 3 * targetCount); + Eigen::Matrix zenithRotations(3, targetCount); const Shared earthSPtr = this->environment_.accessCelestialObjectWithName("Earth"); for (Index i = 0; i < targetCount; ++i) { SEZRotations.block<3, 3>(0, 3 * i) = someAccessTargets[i].computeR_SEZ_ECEF(earthSPtr); + zenithRotations(i) = SEZRotations<1, 3>(2, 3 * i); } // create a stacked matrix of ITRF positions for all ground targets @@ -391,7 +396,7 @@ Array> Generator::computeAccessesForFixedTargets( return accessTarget.getConstraint().isMaskBased(); } ); - const bool allAccessTargetsHaveIntervals = std::all_of( + const bool allAccessTargetsHaveAERIntervals = std::all_of( someAccessTargets.begin(), someAccessTargets.end(), [](const auto& accessTarget) @@ -441,9 +446,16 @@ Array> Generator::computeAccessesForFixedTargets( return {azimuth_rad, elevation_rad, range_m}; }; + const auto computeElevations = [&zenithRotations, &fromPositionCoordinates_ITRF](const Vector3d& aToPositionCoordinates_ITRF) -> VectorXd + { + const MatrixXd dx = (-fromPositionCoordinates_ITRF).colwise() + aToPositionCoordinates_ITRF; + const MatrxXd dx_Z = zenithRotations * dx; + return (dx_Z.transpose().array() / dx.colwise().norm().array()).asin(); + } + std::function constraintFilter; - if (allAccessTargetsHaveIntervals) + if (allAccessTargetsHaveAERIntervals) { // create a stacked matrix of azimuth, elevation, and range bounds for all ground targets MatrixXd aerLowerBounds = MatrixXd::Zero(targetCount, 3); @@ -551,6 +563,37 @@ Array> Generator::computeAccessesForFixedTargets( return mask; }; } + else if (allAccessTargetsHaveElevationIntervals) + { + // create a stacked matrix of azimuth, elevation, and range bounds for all ground targets + VectorXd elevationLowerBounds = VectorXd::Zero(targetCount); + VectorXd elevationUpperBounds = VectorXd::Zero(targetCount); + + const Real degToRad = Real::Pi() / 180.0; + + for (Index i = 0; i < targetCount; ++i) + { + const Constraint::ElevationIntervalConstraint intervalConstraint = + someAccessTargets[i].getConstraint().getElevationIntervalConstraint().value(); + + elevationLowerBounds(i) = intervalConstraint.elevation.accessLowerBound() * degToRad; + elevationUpperBounds(i) = intervalConstraint.elevation.accessUpperBound() * degToRad; + } + + constraintFilter = [elevationLowerBounds, elevationUpperBounds, &computeElevations]( + const MatrixXd& aFromPositionCoordinates_ITRF, + const Vector3d& aToPositionCoordinates_ITRF, + const Instant& anInstant + ) + { + (void)anInstant; + (void)aFromPositionCoordinates_ITRF; + + const VectorXd elevations = computeElevations(aToPositionCoordinates_ITRF); + + return (elevations.array() > elevationLowerBounds.array() && elevations.array() < elevationUpperBounds.array()).eval(); + }; + } else { throw ostk::core::error::RuntimeError("All access targets must have the same type of constraint."); @@ -705,11 +748,9 @@ Array Generator::computePreciseCrossings( const double& rangeLowerBound = intervalConstraint.range.accessLowerBound(); const double& rangeUpperBound = intervalConstraint.range.accessUpperBound(); - const auto [azimuth_rad, elevation_rad, range] = computeAER(instant); + const auto [azimuth_rad, elevation_rad, range_m] = computeAER(instant); - return azimuth_rad > azimuthLowerBound && azimuth_rad < azimuthUpperBound && - elevation_rad > elevationLowerBound && elevation_rad < elevationUpperBound && - range > rangeLowerBound && range < rangeUpperBound; + return intervalConstraint.isSatisfied(azimuth_rad, elevation_rad, range_m); }; } else if (anAccessTarget.getConstraint().isMaskBased()) @@ -723,9 +764,7 @@ Array Generator::computePreciseCrossings( const auto [azimuth_rad, elevation_rad, range_m] = computeAER(instant); - const AER aer = AER(Angle::Radians(azimuth_rad), Angle::Radians(elevation_rad), Length::Meters(range_m)); - - return maskConstraint.isSatisfied(aer) && range_m > rangeLowerBound && range_m < rangeUpperBound; + return maskConstraint.isSatisfied(azimuth_rad, elevation_rad, range_m); }; } else if (anAccessTarget.getConstraint().isLineOfSightBased()) @@ -742,6 +781,24 @@ Array Generator::computePreciseCrossings( return lineOfSightConstraint.isSatisfied(instant, fromPositionCoordinate_ITRF, toPositionCoordinates_ITRF); }; } + else (anAccessTarget.getConstraint().isElevationIntervalBased()) + { + const Constraiont::ElevationIntervalConstraint elevationIntervalConstraint = anAccessTarget.getConstraint().getElevationIntervalConstraint().value(); + + condition = [&computeAER, &fromPositionCoordinate_ITRF, elevationIntervalConstraint](const Instant& instant) -> bool + { + const Vector3d toPositionCoordinates_ITRF = + aToTrajectory.getStateAt(instant).inFrame(Frame::ITRF()).getPosition().getCoordinates(); + + const Vector3d dx = toPositionCoordinates_ITRF - fromPositionCoordinate_ITRF; + + const Real dx_Z = SEZRotation.row(3) * dx; + + const double elevation_rad = std::asin(dx_Z / dx.norm()); + + return elevationIntervalConstraint.isSatisfied(elevation_rad); + }; + } else { throw ostk::core::error::RuntimeError("Constraint type not supported."); From e414d24e517a6037f57a9baa7fe39f8fa131057a Mon Sep 17 00:00:00 2001 From: vishwa shah Date: Thu, 28 Nov 2024 00:17:33 +0000 Subject: [PATCH 12/22] feat: clean up the naming --- .../Astrodynamics/Access/Generator.hpp | 56 ++-- ...{Constraint.hpp => VisibilityCriteria.hpp} | 76 ++--- .../Astrodynamics/Access/Constraint.cpp | 293 ------------------ .../Astrodynamics/Access/Generator.cpp | 261 ++++++++-------- .../Access/VisibilityCriteria.cpp | 38 +++ .../Access/VisibilityCriteria/AERInterval.cpp | 80 +++++ .../Access/VisibilityCriteria/AERMask.cpp | 97 ++++++ .../VisibilityCriteria/ElevationInterval.cpp | 38 +++ .../Access/VisibilityCriteria/LineOfSight.cpp | 55 ++++ test/Global.test.hpp | 46 +++ .../Astrodynamics/Access/Generator.test.cpp | 113 ++++--- 11 files changed, 611 insertions(+), 542 deletions(-) rename include/OpenSpaceToolkit/Astrodynamics/Access/{Constraint.hpp => VisibilityCriteria.hpp} (51%) delete mode 100644 src/OpenSpaceToolkit/Astrodynamics/Access/Constraint.cpp create mode 100644 src/OpenSpaceToolkit/Astrodynamics/Access/VisibilityCriteria.cpp create mode 100644 src/OpenSpaceToolkit/Astrodynamics/Access/VisibilityCriteria/AERInterval.cpp create mode 100644 src/OpenSpaceToolkit/Astrodynamics/Access/VisibilityCriteria/AERMask.cpp create mode 100644 src/OpenSpaceToolkit/Astrodynamics/Access/VisibilityCriteria/ElevationInterval.cpp create mode 100644 src/OpenSpaceToolkit/Astrodynamics/Access/VisibilityCriteria/LineOfSight.cpp diff --git a/include/OpenSpaceToolkit/Astrodynamics/Access/Generator.hpp b/include/OpenSpaceToolkit/Astrodynamics/Access/Generator.hpp index 46f9a614b..2d2cdb85c 100644 --- a/include/OpenSpaceToolkit/Astrodynamics/Access/Generator.hpp +++ b/include/OpenSpaceToolkit/Astrodynamics/Access/Generator.hpp @@ -19,7 +19,7 @@ #include #include -#include +#include #include namespace ostk @@ -51,7 +51,7 @@ using ostk::physics::unit::Length; using EarthGravitationalModel = ostk::physics::environment::gravitational::Earth; using ostk::astrodynamics::Access; -using ostk::astrodynamics::access::Constraint; +using ostk::astrodynamics::access::VisibilityCriteria; using ostk::astrodynamics::Trajectory; using ostk::astrodynamics::trajectory::State; @@ -72,10 +72,6 @@ class AccessTarget Trajectory }; - /// @brief Constructor - /// @param aPosition A position - // AccessTarget(const Type& aType, const Constraint& constrant, const Position& aPosition); - /// @brief Get the type /// /// @code{.cpp} @@ -86,15 +82,15 @@ class AccessTarget /// @return The type Type getType() const; - /// @brief Get the constraint + /// @brief Get the visibility criteria /// /// @code{.cpp} /// AccessTarget accessTarget = { ... } ; - /// Constraint constraint = accessTarget.getConstraint(); + /// VisibilityCriteria visibilityCriteria = accessTarget.getVisibilityCriteria(); /// @endcode /// - /// @return The constraint - Constraint getConstraint() const; + /// @return The visibility criteria + VisibilityCriteria getVisibilityCriteria() const; /// @brief Get the trajectory /// @@ -139,32 +135,54 @@ class AccessTarget /// @return The SEZ rotation matrix Matrix3d computeR_SEZ_ECEF(const Shared& aCelestialSPtr) const; - /// @brief Construct a ground target configuration from an LLA (Latitude, Longitude, Altitude) + /// @brief Construct an Access Target from an LLA (Latitude, Longitude, Altitude) /// /// @code{.cpp} /// AccessTarget accessTarget = AccessTarget::LLA( - /// constraint, lla, aCelestialSPtr + /// visibilityCriteria, lla, celestialSPtr /// ); /// @endcode /// /// @param constraint /// @param anLLA /// @param aCelestialSPtr - /// @return Ground target configuration + /// @return Access target static AccessTarget FromLLA( - const Constraint& constraint, const LLA& anLLA, const Shared& aCelestialSPtr + const VisibilityCriteria& aVisibilityCriteria, const LLA& anLLA, const Shared& aCelestialSPtr ); - static AccessTarget FromPosition(const Constraint& constraint, const Position& aPosition); + /// @brief Construct an Access Target from a position + /// + /// @code{.cpp} + /// AccessTarget accessTarget = AccessTarget::Position( + /// visibilityCriteria, position + /// ); + /// @endcode + /// + /// @param constraint + /// @param aPosition + /// @return Access target + static AccessTarget FromPosition(const VisibilityCriteria& aVisibilityCriteria, const Position& aPosition); - static AccessTarget FromTrajectory(const Constraint& constraint, const Trajectory& aTrajectory); + /// @brief Construct an Access Target from a trajectory + /// + /// @code{.cpp} + /// AccessTarget accessTarget = AccessTarget::FromTrajectory( + /// visibilityCriteria, trajectory + /// ); + /// @endcode + /// + /// @param constraint + /// @param aTrajectory + /// @return Access target + static AccessTarget FromTrajectory(const VisibilityCriteria& aVisibilityCriteria, const Trajectory& aTrajectory); private: Type type_; - Constraint constraint_; + VisibilityCriteria visibilityCriteria_; Trajectory trajectory_; - AccessTarget(const Type& aType, const Constraint& constrant, const Trajectory& aTrajectory); + AccessTarget(const Type& aType, const VisibilityCriteria& constrant, const Trajectory& aTrajectory); }; class Generator @@ -283,7 +301,7 @@ class GeneratorContext const Generator& aGenerator ); - bool isAccessActive(const Instant& anInstant, const Constraint& aConstraint); + bool isAccessActive(const Instant& anInstant, const VisibilityCriteria& aVisibilityCriteria); static Pair GetStatesAt( const Instant& anInstant, const Trajectory& aFromTrajectory, const Trajectory& aToTrajectory diff --git a/include/OpenSpaceToolkit/Astrodynamics/Access/Constraint.hpp b/include/OpenSpaceToolkit/Astrodynamics/Access/VisibilityCriteria.hpp similarity index 51% rename from include/OpenSpaceToolkit/Astrodynamics/Access/Constraint.hpp rename to include/OpenSpaceToolkit/Astrodynamics/Access/VisibilityCriteria.hpp index d86b67c0d..ffc5ae8df 100644 --- a/include/OpenSpaceToolkit/Astrodynamics/Access/Constraint.hpp +++ b/include/OpenSpaceToolkit/Astrodynamics/Access/VisibilityCriteria.hpp @@ -1,7 +1,7 @@ /// Apache License 2.0 -#ifndef __OpenSpaceToolkit_Astrodynamics_Access_Constraint__ -#define __OpenSpaceToolkit_Astrodynamics_Access_Constraint__ +#ifndef __OpenSpaceToolkit_Astrodynamics_Access_VisibilityCriteria__ +#define __OpenSpaceToolkit_Astrodynamics_Access_VisibilityCriteria__ #include #include @@ -34,87 +34,93 @@ using ostk::physics::coordinate::spherical::AER; using ostk::physics::Environment; using ostk::physics::time::Instant; -class Constraint +class VisibilityCriteria { public: - struct AERIntervalConstraint + struct AERInterval { Interval azimuth; // radians Interval elevation; // radians Interval range; // meters - AERIntervalConstraint( - const Interval& anAzimuth, const Interval& anElevation, const Interval& aRange - ); + AERInterval(const Interval& anAzimuth, const Interval& anElevation, const Interval& aRange); bool isSatisfied(const AER& anAer) const; - bool isSatisifed(const Real& anAzimuthRadians, const Real& anElevationRadians, const Real& aRangeMeters) const; + bool isSatisfied(const Real& anAzimuthRadians, const Real& anElevationRadians, const Real& aRangeMeters) const; }; - struct MaskConstraint + struct AERMask { Map azimuthElevationMask; // radians, radians Interval range; // meters - MaskConstraint(const Map& anAzimuthElevationMask, const Interval& aRange); + AERMask(const Map& anAzimuthElevationMask, const Interval& aRange); bool isSatisfied(const AER& anAer) const; bool isSatisfied(const Real& anAzimuthRadians, const Real& anElevationRadians, const Real& aRangeMeters) const; }; - struct LineOfSightConstraint + struct LineOfSight { mutable Environment environment; - LineOfSightConstraint(const Environment& anEnvironment); + LineOfSight(const Environment& anEnvironment); bool isSatisfied( const Instant& anInstant, const Vector3d& aFromPositionCoordinates, const Vector3d& aToPositionCoordinates ) const; }; - struct ElevationIntervalConstraint + struct ElevationInterval { Interval elevation; // radians - - ElevationIntervalConstraint(const Interval& anElevationInterval); + + ElevationInterval(const Interval& anElevationInterval); bool isSatisfied(const Real& anElevation) const; }; - static Constraint FromAERIntervals( - const Interval& anAzimuthInterval, const Interval& anElevationInterval, const Interval& aRangeInterval + static VisibilityCriteria FromAERInterval( + const Interval& anAzimuthInterval, + const Interval& anElevationInterval, + const Interval& aRangeInterval ); - static Constraint FromMask(const Map& anAzimuthElevationMask, const Interval& aRangeInterval); - - static Constraint FromLineOfSight(const Environment& anEnvironment); - - static Constraint FromElevationInterval(const Interval& anElevationInterval); - - bool isAERIntervalBased() const; - - bool isMaskBased() const; - - bool isLineOfSightBased() const; + static VisibilityCriteria FromAERMask( + const Map& anAzimuthElevationMask, const Interval& aRangeInterval + ); - bool isElevationIntervalBased() const; + static VisibilityCriteria FromLineOfSight(const Environment& anEnvironment); - std::optional getAERIntervalConstraint() const; + static VisibilityCriteria FromElevationInterval(const Interval& anElevationInterval); - std::optional getMaskConstraint() const; + template + bool is() const + { + return std::holds_alternative(visibilityCriteria_); + } - std::optional getLineOfSightConstraint() const; + template + std::optional as() const + { + if (const auto* constraintPtr = std::get_if(&visibilityCriteria_)) + { + return *constraintPtr; + } - std::optional getElevationIntervalConstraint() const; + return std::nullopt; + } private: - std::variant constraint_; + std::variant visibilityCriteria_; template - explicit Constraint(const T& aConstraint); + explicit VisibilityCriteria(const T& aVisibilityCriteria) + : visibilityCriteria_(aVisibilityCriteria) + { + } }; } // namespace access diff --git a/src/OpenSpaceToolkit/Astrodynamics/Access/Constraint.cpp b/src/OpenSpaceToolkit/Astrodynamics/Access/Constraint.cpp deleted file mode 100644 index aef6c92ec..000000000 --- a/src/OpenSpaceToolkit/Astrodynamics/Access/Constraint.cpp +++ /dev/null @@ -1,293 +0,0 @@ -/// Apache License 2.0 - -#include -#include - -#include - -#include - -namespace ostk -{ -namespace astrodynamics -{ -namespace access -{ - -using ostk::core::type::Shared; - -using ostk::mathematics::object::Vector2d; - -using ostk::mathematics::geometry::d3::object::Point; -using ostk::mathematics::geometry::d3::object::Segment; - -using ostk::physics::coordinate::Frame; -using ostk::physics::environment::Object; - -Constraint::AERIntervalConstraint::AERIntervalConstraint( - const Interval& anAzimuthInterval, - const Interval& anElevationInterval, - const Interval& aRangeInterval -) - : azimuth(anAzimuthInterval), - elevation(anElevationInterval), - range(aRangeInterval) -{ - if (!azimuth.isDefined() || !elevation.isDefined() || !range.isDefined()) - { - throw ostk::core::error::runtime::Undefined("Interval"); - } - - if (azimuth.getLowerBound() < 0.0 || azimuth.getUpperBound() > 360.0) - { - throw ostk::core::error::RuntimeError( - "The azimuth interval [{}, {}] must be in the range [0, 360] deg", - azimuth.accessLowerBound(), - azimuth.accessUpperBound() - ); - } - - if (elevation.getLowerBound() < -90.0 || elevation.getUpperBound() > 90.0) - { - throw ostk::core::error::RuntimeError( - "The elevation interval [{}, {}] must be in the range [-90, 90] deg.", - elevation.accessLowerBound(), - elevation.accessUpperBound() - ); - } - - if (range.getLowerBound() < 0.0) - { - throw ostk::core::error::RuntimeError( - "The range interval [{}, {}] must be positive.", range.accessLowerBound(), range.accessUpperBound() - ); - } - - azimuth = Interval(anAzimuthInterval.getLowerBound() * M_PI/180.0, anAzimuthInterval.getUpperBound() * M_PI/180.0, anAzimuthInterval.getType()); - elevation = Interval(anElevationInterval.getLowerBound() * M_PI/180.0, anElevationInterval.getUpperBound() * M_PI/180.0, anAzimuthInterval.getType()); -} - -bool Constraint::AERIntervalConstraint::isSatisfied(const AER& anAer) const -{ - return isSatisfied(anAer.getAzimuth().inRadians(), anAer.getElevation().inRadians(), anAer.getRange().inMeters()); -} - -bool Constraint::AERIntervalConstraint::isSatisfied(const Real& anAzimuthRadians, const Real& anElevationRadians, const Real& aRangeMeters) const -{ - return azimuth.contains(anAzimuthRadians) && elevation.contains(anElevationRadians) && - range.contains(aRangeMeters); -} - -Constraint::MaskConstraint::MaskConstraint( - const Map& anAzimuthElevationMask, const Interval& aRangeInterval -) - : azimuthElevationMask(anAzimuthElevationMask), - range(aRangeInterval) -{ - if (range.getLowerBound() < 0.0) - { - throw ostk::core::error::RuntimeError( - "The range interval [{}, {}] must be positive.", range.accessLowerBound(), range.accessUpperBound() - ); - } - - if ((anAzimuthElevationMask.empty()) || (anAzimuthElevationMask.begin()->first < 0.0) || - (anAzimuthElevationMask.rbegin()->first > 360.0)) - { - throw ostk::core::error::runtime::Wrong("Azimuth-Elevation Mask"); - } - - for (const auto& azimuthElevationPair : anAzimuthElevationMask) - { - if ((azimuthElevationPair.second).abs() > 90.0) - { - throw ostk::core::error::runtime::Wrong("Azimuth-Elevation Mask"); - } - } - - Map anAzimuthElevationMask_deg = anAzimuthElevationMask; - - if (anAzimuthElevationMask_deg.begin()->first != 0.0) - { - anAzimuthElevationMask_deg.insert({0.0, anAzimuthElevationMask_deg.begin()->second}); - } - - if (anAzimuthElevationMask_deg.rbegin()->first != 360.0) - { - anAzimuthElevationMask_deg.insert({360.0, anAzimuthElevationMask_deg.begin()->second}); - } - - Map anAzimuthElevationMask_rad; - - for (auto it = anAzimuthElevationMask_deg.begin(); it != anAzimuthElevationMask_deg.end(); ++it) - { - anAzimuthElevationMask_rad.insert({it->first * M_PI / 180.0, it->second * M_PI / 180.0}); - } - - this->azimuthElevationMask = anAzimuthElevationMask_rad; -} - -bool Constraint::MaskConstraint::isSatisfied(const AER& anAer) const -{ - return isSatisfied(anAer.getAzimuth().inRadians(0.0, Real::TwoPi()), anAer.getElevation().inRadians(-Real::Pi(), Real::Pi()), anAer.getRange().inMeters()); -} - -bool Constraint::MaskConstraint::isSatisfied(const Real anAzimuthRadians&, const Real& anElevationRadians, const Real& aRangeMeters) const -{ - auto itLow = this->azimuthElevationMask.lower_bound(anAzimuthRadians); - itLow--; - auto itUp = this->azimuthElevationMask.upper_bound(anAzimuthRadians); - - // Vector between the two successive mask data points with bounding azimuth values - - const Vector2d lowToUpVector = {itUp->first - itLow->first, itUp->second - itLow->second}; - - // Vector from data point with azimuth lower bound to tested point - - const Vector2d lowToPointVector = {anAzimuthRadians - itLow->first, anElevationRadians - itLow->second}; - - // If the determinant of these two vectors is positive, the tested point lies above the function defined by the - // mask - - return ((lowToUpVector[0] * lowToPointVector[1] - lowToUpVector[1] * lowToPointVector[0]) >= 0.0) && aRange.contains(aRangeMeters); -} - -Constraint::LineOfSightConstraint::LineOfSightConstraint(const Environment& anEnvironment) - : environment(anEnvironment) -{ -} - -bool Constraint::LineOfSightConstraint::isSatisfied( - const Instant& anInstant, const Vector3d& aFromPositionCoordinates, const Vector3d& aToPositionCoordinates -) const -{ - static const Shared commonFrameSPtr = Frame::GCRF(); - - this->environment.setInstant(anInstant); - - const Point fromPositionCoordinates = Point::Vector(aFromPositionCoordinates); - const Point toPositionCoordinates = Point::Vector(aToPositionCoordinates); - - if (fromPositionCoordinates == toPositionCoordinates) - { - return true; - } - - const Segment fromToSegment = {fromPositionCoordinates, toPositionCoordinates}; - - const Object::Geometry fromToSegmentGeometry = {fromToSegment, commonFrameSPtr}; - - return !this->environment.intersects(fromToSegmentGeometry); -} - -Constraint::ElevationIntervalConstraint::ElevationIntervalConstraint(const Interval& anElevationInterval) - : elevation(anElevationInterval) -{ - if (elevation.getLowerBound() < -90.0 || elevation.getUpperBound() > 90.0) - { - throw ostk::core::error::RuntimeError( - "The elevation interval [{}, {}] must be in the range [-90, 90] deg.", - elevation.accessLowerBound(), - elevation.accessUpperBound() - ); - } - - elevation = Interval(anElevationInterval.getLowerBound() * M_PI/180.0, anElevationInterval.getUpperBound() * M_PI/180.0, anAzimuthInterval.getType()); -} - -bool Constraint::ElevationIntervalConstraint::isSatisfied(const Real& anElevation) const -{ - return elevation.contains(anElevation); -} - -Constraint Constraint::FromAERIntervals( - const Interval& azimuth, const Interval& elevation, const Interval& range -) -{ - return Constraint(AERIntervalConstraint {azimuth, elevation, range}); -} - -Constraint Constraint::FromMask(const Map& azimuthElevationMask, const Interval& range) -{ - return Constraint(MaskConstraint {azimuthElevationMask, range}); -} - -Constraint Constraint::FromLineOfSight(const Environment& environment) -{ - return Constraint(LineOfSightConstraint {environment}); -} - -Constraint Constraint::FromElevationInterval(const Interval& anElevationInterval) -{ - return Constraint(ElevationIntervalConstraint {anElevationInterval}); -} - -bool Constraint::isAERIntervalBased() const -{ - return std::holds_alternative(constraint_); -} - -bool Constraint::isMaskBased() const -{ - return std::holds_alternative(constraint_); -} - -bool Constraint::isLineOfSightBased() const -{ - return std::holds_alternative(constraint_); -} - -bool Constraint::isElevationIntervalBased() const -{ - return std::holds_alternative(constraint_); -} - -std::optional Constraint::getAERIntervalConstraint() const -{ - if (const auto* aerInterval = std::get_if(&constraint_)) - { - return *aerInterval; - } - - return std::nullopt; -} - -std::optional Constraint::getMaskConstraint() const -{ - if (const auto* mask = std::get_if(&constraint_)) - { - return *mask; - } - - return std::nullopt; -} - -std::optional Constraint::getLineOfSightConstraint() const -{ - if (const auto* lineOfSight = std::get_if(&constraint_)) - { - return *lineOfSight; - } - - return std::nullopt; -} - -std::optional Constraint::getElevationIntervalConstraint() const -{ - if (const auto* elevationInterval = std::get_if(&constraint_)) - { - return *elevationInterval; - } - - return std::nullopt; -} - -template -Constraint::Constraint(const T& constraint) - : constraint_(constraint) -{ -} - -} // namespace access -} // namespace astrodynamics -} // namespace ostk diff --git a/src/OpenSpaceToolkit/Astrodynamics/Access/Generator.cpp b/src/OpenSpaceToolkit/Astrodynamics/Access/Generator.cpp index bd2b8181d..74c4de004 100644 --- a/src/OpenSpaceToolkit/Astrodynamics/Access/Generator.cpp +++ b/src/OpenSpaceToolkit/Astrodynamics/Access/Generator.cpp @@ -49,9 +49,9 @@ AccessTarget::Type AccessTarget::getType() const return type_; } -Constraint AccessTarget::getConstraint() const +VisibilityCriteria AccessTarget::getVisibilityCriteria() const { - return constraint_; + return visibilityCriteria_; } Trajectory AccessTarget::getTrajectory() const @@ -94,33 +94,33 @@ Matrix3d AccessTarget::computeR_SEZ_ECEF(const Shared& aCelesti } AccessTarget AccessTarget::FromLLA( - const Constraint& constraint, const LLA& anLLA, const Shared& aCelestialSPtr + const VisibilityCriteria& aVisibilityCriteria, const LLA& anLLA, const Shared& aCelestialSPtr ) { return AccessTarget( AccessTarget::Type::Fixed, - constraint, + aVisibilityCriteria, Trajectory::Position(Position::Meters( anLLA.toCartesian(aCelestialSPtr->getEquatorialRadius(), aCelestialSPtr->getFlattening()), Frame::ITRF() )) ); } -AccessTarget AccessTarget::FromPosition(const Constraint& constraint, const Position& aPosition) +AccessTarget AccessTarget::FromPosition(const VisibilityCriteria& aVisibilityCriteria, const Position& aPosition) { - return AccessTarget(AccessTarget::Type::Fixed, constraint, Trajectory::Position(aPosition)); + return AccessTarget(AccessTarget::Type::Fixed, aVisibilityCriteria, Trajectory::Position(aPosition)); } -AccessTarget AccessTarget::FromTrajectory(const Constraint& constraint, const Trajectory& aTrajectory) +AccessTarget AccessTarget::FromTrajectory(const VisibilityCriteria& aVisibilityCriteria, const Trajectory& aTrajectory) { - return AccessTarget(AccessTarget::Type::Trajectory, constraint, aTrajectory); + return AccessTarget(AccessTarget::Type::Trajectory, aVisibilityCriteria, aTrajectory); } AccessTarget::AccessTarget( - const AccessTarget::Type& aType, const Constraint& aConstraint, const Trajectory& aTrajectory + const AccessTarget::Type& aType, const VisibilityCriteria& aVisibilityCriteria, const Trajectory& aTrajectory ) : type_(aType), - constraint_(aConstraint), + visibilityCriteria_(aVisibilityCriteria), trajectory_(aTrajectory) { if (!aTrajectory.isDefined()) @@ -210,13 +210,13 @@ std::function Generator::getConditionFunction( throw ostk::core::error::runtime::Undefined("Generator"); } - const Constraint constraint = anAccessTarget.getConstraint(); + const VisibilityCriteria visibilityCriteria = anAccessTarget.getVisibilityCriteria(); GeneratorContext generatorContext = GeneratorContext(aFromTrajectory, aToTrajectory, environment_, *this); - return [generatorContext, constraint](const Instant& anInstant) mutable -> bool + return [generatorContext, visibilityCriteria](const Instant& anInstant) mutable -> bool { - return generatorContext.isAccessActive(anInstant, constraint); + return generatorContext.isAccessActive(anInstant, visibilityCriteria); }; } @@ -286,13 +286,13 @@ Array> Generator::computeAccesses( } if (std::all_of( - someAccessTargets.begin(), - someAccessTargets.end(), - [](const auto& accessTarget) - { - return accessTarget.getType() == AccessTarget::Type::Fixed; - } - )) + someAccessTargets.begin(), + someAccessTargets.end(), + [](const auto& accessTarget) + { + return accessTarget.getType() == AccessTarget::Type::Fixed; + } + )) { return this->computeAccessesForFixedTargets(anInterval, someAccessTargets, aToTrajectory); } @@ -362,24 +362,24 @@ Array> Generator::computeAccessesForFixedTargets( if (stateFilter_) { - throw ostk::core::error::RuntimeError("State filter is not supported for multiple ground targets."); + throw ostk::core::error::RuntimeError("State filter is not supported for multiple access targets."); } - // create a stacked matrix of SEZ rotations for all ground targets + // create a stacked matrix of SEZ rotations for all access targets const Index targetCount = someAccessTargets.getSize(); Eigen::Matrix SEZRotations(3, 3 * targetCount); - Eigen::Matrix zenithRotations(3, targetCount); + Eigen::Matrix zenithRotations(targetCount, 3); const Shared earthSPtr = this->environment_.accessCelestialObjectWithName("Earth"); for (Index i = 0; i < targetCount; ++i) { SEZRotations.block<3, 3>(0, 3 * i) = someAccessTargets[i].computeR_SEZ_ECEF(earthSPtr); - zenithRotations(i) = SEZRotations<1, 3>(2, 3 * i); + zenithRotations.row(i) = SEZRotations.block<1, 3>(2, 3 * i); } - // create a stacked matrix of ITRF positions for all ground targets + // create a stacked matrix of ITRF positions for all access targets MatrixXd fromPositionCoordinates_ITRF = MatrixXd::Zero(3, targetCount); @@ -393,7 +393,7 @@ Array> Generator::computeAccessesForFixedTargets( someAccessTargets.end(), [](const auto& accessTarget) { - return accessTarget.getConstraint().isMaskBased(); + return accessTarget.getVisibilityCriteria().template is(); } ); const bool allAccessTargetsHaveAERIntervals = std::all_of( @@ -401,7 +401,7 @@ Array> Generator::computeAccessesForFixedTargets( someAccessTargets.end(), [](const auto& accessTarget) { - return accessTarget.getConstraint().isAERIntervalBased(); + return accessTarget.getVisibilityCriteria().template is(); } ); const bool allAccessTargetsHaveLineOfSight = std::all_of( @@ -409,7 +409,15 @@ Array> Generator::computeAccessesForFixedTargets( someAccessTargets.end(), [](const auto& accessTarget) { - return accessTarget.getConstraint().isLineOfSightBased(); + return accessTarget.getVisibilityCriteria().template is(); + } + ); + const bool allAccessTargetsHaveElevationIntervals = std::all_of( + someAccessTargets.begin(), + someAccessTargets.end(), + [](const auto& accessTarget) + { + return accessTarget.getVisibilityCriteria().template is(); } ); @@ -446,42 +454,44 @@ Array> Generator::computeAccessesForFixedTargets( return {azimuth_rad, elevation_rad, range_m}; }; - const auto computeElevations = [&zenithRotations, &fromPositionCoordinates_ITRF](const Vector3d& aToPositionCoordinates_ITRF) -> VectorXd + const auto computeElevations = + [&zenithRotations, &fromPositionCoordinates_ITRF](const Vector3d& aToPositionCoordinates_ITRF) -> VectorXd { const MatrixXd dx = (-fromPositionCoordinates_ITRF).colwise() + aToPositionCoordinates_ITRF; - const MatrxXd dx_Z = zenithRotations * dx; + const VectorXd dx_Z = zenithRotations.col(0).array() * dx.row(0).transpose().array() + + zenithRotations.col(1).array() * dx.row(1).transpose().array() + + zenithRotations.col(2).array() * dx.row(2).transpose().array(); + return (dx_Z.transpose().array() / dx.colwise().norm().array()).asin(); - } + }; - std::function constraintFilter; + std::function visibilityCriteriaFilter; if (allAccessTargetsHaveAERIntervals) { - // create a stacked matrix of azimuth, elevation, and range bounds for all ground targets + // create a stacked matrix of azimuth, elevation, and range bounds for all access targets MatrixXd aerLowerBounds = MatrixXd::Zero(targetCount, 3); MatrixXd aerUpperBounds = MatrixXd::Zero(targetCount, 3); - const Real degToRad = Real::Pi() / 180.0; - for (Index i = 0; i < targetCount; ++i) { - const Constraint::AERIntervalConstraint intervalConstraint = - someAccessTargets[i].getConstraint().getAERIntervalConstraint().value(); + const VisibilityCriteria::AERInterval visibilityCriteria = + someAccessTargets[i].getVisibilityCriteria().as().value(); - aerLowerBounds(i, 0) = intervalConstraint.azimuth.accessLowerBound() * degToRad; - aerLowerBounds(i, 1) = intervalConstraint.elevation.accessLowerBound() * degToRad; - aerLowerBounds(i, 2) = intervalConstraint.range.accessLowerBound(); + aerLowerBounds(i, 0) = visibilityCriteria.azimuth.accessLowerBound(); + aerLowerBounds(i, 1) = visibilityCriteria.elevation.accessLowerBound(); + aerLowerBounds(i, 2) = visibilityCriteria.range.accessLowerBound(); - aerUpperBounds(i, 0) = intervalConstraint.azimuth.accessUpperBound() * degToRad; - aerUpperBounds(i, 1) = intervalConstraint.elevation.accessUpperBound() * degToRad; - aerUpperBounds(i, 2) = intervalConstraint.range.accessUpperBound(); + aerUpperBounds(i, 0) = visibilityCriteria.azimuth.accessUpperBound(); + aerUpperBounds(i, 1) = visibilityCriteria.elevation.accessUpperBound(); + aerUpperBounds(i, 2) = visibilityCriteria.range.accessUpperBound(); } - constraintFilter = [aerLowerBounds, aerUpperBounds, &computeAer]( - const MatrixXd& aFromPositionCoordinates_ITRF, - const Vector3d& aToPositionCoordinates_ITRF, - const Instant& anInstant - ) + visibilityCriteriaFilter = [aerLowerBounds, aerUpperBounds, &computeAer]( + const MatrixXd& aFromPositionCoordinates_ITRF, + const Vector3d& aToPositionCoordinates_ITRF, + const Instant& anInstant + ) { (void)anInstant; (void)aFromPositionCoordinates_ITRF; @@ -498,43 +508,28 @@ Array> Generator::computeAccessesForFixedTargets( } else if (allAccessTargetsHaveMasks) { - VectorXd rangeLowerBounds = VectorXd::Zero(targetCount, 1); - VectorXd rangeUpperBounds = VectorXd::Zero(targetCount, 1); - - for (Index i = 0; i < targetCount; ++i) - { - const Constraint::MaskConstraint constraint = - someAccessTargets[i].getConstraint().getMaskConstraint().value(); - - rangeLowerBounds(i) = constraint.range.accessLowerBound(); - rangeUpperBounds(i) = constraint.range.accessUpperBound(); - } - - constraintFilter = [&someAccessTargets, &computeAer, rangeLowerBounds, rangeUpperBounds]( - const MatrixXd& aFromPositionCoordinates_ITRF, - const Vector3d& aToPositionCoordinates_ITRF, - const Instant& anInstant - ) + visibilityCriteriaFilter = [&someAccessTargets, &computeAer]( + const MatrixXd& aFromPositionCoordinates_ITRF, + const Vector3d& aToPositionCoordinates_ITRF, + const Instant& anInstant + ) { (void)anInstant; (void)aFromPositionCoordinates_ITRF; const auto [azimuths_rad, elevations_rad, ranges_m] = computeAer(aToPositionCoordinates_ITRF); - ArrayXb mask(azimuths_rad.rows()); - mask = ranges_m.array() > rangeLowerBounds.array() && ranges_m.array() < rangeUpperBounds.array(); + ArrayXb mask(azimuths_rad.rows()); for (Eigen::Index i = 0; i < mask.rows(); ++i) { - const Constraint::MaskConstraint constraint = - someAccessTargets[i].getConstraint().getMaskConstraint().value(); + const VisibilityCriteria::AERMask visibilityCriteria = + someAccessTargets[i].getVisibilityCriteria().as().value(); const double& azimuth_rad = azimuths_rad(i); const double& elevation_rad = elevations_rad(i); + const double& range_m = ranges_m(i); - const AER aer = - AER(Angle::Radians(azimuth_rad), Angle::Radians(elevation_rad), Length::Meters(ranges_m(i))); - - mask(i) = mask(i) && constraint.isSatisfied(aer); + mask(i) = visibilityCriteria.isSatisfied(azimuth_rad, elevation_rad, range_m); } return mask; @@ -542,22 +537,23 @@ Array> Generator::computeAccessesForFixedTargets( } else if (allAccessTargetsHaveLineOfSight) { - constraintFilter = [&someAccessTargets]( - const MatrixXd& aFromPositionCoordinates_ITRF, - const Vector3d& aToPositionCoordinates_ITRF, - const Instant& anInstant - ) + visibilityCriteriaFilter = [&someAccessTargets]( + const MatrixXd& aFromPositionCoordinates_ITRF, + const Vector3d& aToPositionCoordinates_ITRF, + const Instant& anInstant + ) { ArrayXb mask(aFromPositionCoordinates_ITRF.cols()); for (Eigen::Index i = 0; i < mask.rows(); ++i) { - const Constraint::LineOfSightConstraint constraint = - someAccessTargets[i].getConstraint().getLineOfSightConstraint().value(); + const VisibilityCriteria::LineOfSight visibilityCriteria = + someAccessTargets[i].getVisibilityCriteria().as().value(); const Vector3d& fromPositionCoordinate_ITRF = aFromPositionCoordinates_ITRF.col(i); - mask(i) = constraint.isSatisfied(anInstant, fromPositionCoordinate_ITRF, aToPositionCoordinates_ITRF); + mask(i) = + visibilityCriteria.isSatisfied(anInstant, fromPositionCoordinate_ITRF, aToPositionCoordinates_ITRF); } return mask; @@ -569,34 +565,34 @@ Array> Generator::computeAccessesForFixedTargets( VectorXd elevationLowerBounds = VectorXd::Zero(targetCount); VectorXd elevationUpperBounds = VectorXd::Zero(targetCount); - const Real degToRad = Real::Pi() / 180.0; - for (Index i = 0; i < targetCount; ++i) { - const Constraint::ElevationIntervalConstraint intervalConstraint = - someAccessTargets[i].getConstraint().getElevationIntervalConstraint().value(); + const VisibilityCriteria::ElevationInterval visibilityCriteria = + someAccessTargets[i].getVisibilityCriteria().as().value(); - elevationLowerBounds(i) = intervalConstraint.elevation.accessLowerBound() * degToRad; - elevationUpperBounds(i) = intervalConstraint.elevation.accessUpperBound() * degToRad; + elevationLowerBounds(i) = visibilityCriteria.elevation.accessLowerBound(); + elevationUpperBounds(i) = visibilityCriteria.elevation.accessUpperBound(); } - constraintFilter = [elevationLowerBounds, elevationUpperBounds, &computeElevations]( - const MatrixXd& aFromPositionCoordinates_ITRF, - const Vector3d& aToPositionCoordinates_ITRF, - const Instant& anInstant - ) + visibilityCriteriaFilter = [elevationLowerBounds, elevationUpperBounds, &computeElevations]( + const MatrixXd& aFromPositionCoordinates_ITRF, + const Vector3d& aToPositionCoordinates_ITRF, + const Instant& anInstant + ) { (void)anInstant; (void)aFromPositionCoordinates_ITRF; const VectorXd elevations = computeElevations(aToPositionCoordinates_ITRF); - return (elevations.array() > elevationLowerBounds.array() && elevations.array() < elevationUpperBounds.array()).eval(); + return (elevations.array() > elevationLowerBounds.array() && + elevations.array() < elevationUpperBounds.array()) + .eval(); }; } else { - throw ostk::core::error::RuntimeError("All access targets must have the same type of constraint."); + throw ostk::core::error::RuntimeError("All access targets must have the same type of Visibility Criteria."); } const Array instants = anInterval.generateGrid(this->step_); @@ -613,7 +609,8 @@ Array> Generator::computeAccessesForFixedTargets( aToTrajectory.getStateAt(instant).inFrame(Frame::ITRF()).getPosition().getCoordinates(); // check if satellite is in access - const auto inAccess = constraintFilter(fromPositionCoordinates_ITRF, toPositionCoordinates_ITRF, instant); + const auto inAccess = + visibilityCriteriaFilter(fromPositionCoordinates_ITRF, toPositionCoordinates_ITRF, instant); inAccessPerTarget.row(index) = inAccess.cast().transpose(); } @@ -734,74 +731,67 @@ Array Generator::computePreciseCrossings( return {azimuth_rad, elevation_rad, range_m}; }; - if (anAccessTarget.getConstraint().isAERIntervalBased()) + if (anAccessTarget.getVisibilityCriteria().is()) { - const Constraint::AERIntervalConstraint intervalConstraint = - anAccessTarget.getConstraint().getAERIntervalConstraint().value(); + const VisibilityCriteria::AERInterval visibilityCriteria = + anAccessTarget.getVisibilityCriteria().as().value(); - condition = [&computeAER, intervalConstraint](const Instant& instant) -> bool + condition = [&computeAER, visibilityCriteria](const Instant& instant) -> bool { - const double& azimuthLowerBound = intervalConstraint.azimuth.accessLowerBound(); - const double& azimuthUpperBound = intervalConstraint.azimuth.accessUpperBound(); - const double& elevationLowerBound = intervalConstraint.elevation.accessLowerBound(); - const double& elevationUpperBound = intervalConstraint.elevation.accessUpperBound(); - const double& rangeLowerBound = intervalConstraint.range.accessLowerBound(); - const double& rangeUpperBound = intervalConstraint.range.accessUpperBound(); - const auto [azimuth_rad, elevation_rad, range_m] = computeAER(instant); - return intervalConstraint.isSatisfied(azimuth_rad, elevation_rad, range_m); + return visibilityCriteria.isSatisfied(azimuth_rad, elevation_rad, range_m); }; } - else if (anAccessTarget.getConstraint().isMaskBased()) + else if (anAccessTarget.getVisibilityCriteria().is()) { - const Constraint::MaskConstraint maskConstraint = anAccessTarget.getConstraint().getMaskConstraint().value(); + const VisibilityCriteria::AERMask visibilityCriteria = + anAccessTarget.getVisibilityCriteria().as().value(); - condition = [&computeAER, maskConstraint](const Instant& instant) -> bool + condition = [&computeAER, visibilityCriteria](const Instant& instant) -> bool { - const double& rangeLowerBound = maskConstraint.range.accessLowerBound(); - const double& rangeUpperBound = maskConstraint.range.accessUpperBound(); - const auto [azimuth_rad, elevation_rad, range_m] = computeAER(instant); - return maskConstraint.isSatisfied(azimuth_rad, elevation_rad, range_m); + return visibilityCriteria.isSatisfied(azimuth_rad, elevation_rad, range_m); }; } - else if (anAccessTarget.getConstraint().isLineOfSightBased()) + else if (anAccessTarget.getVisibilityCriteria().is()) { - const Constraint::LineOfSightConstraint lineOfSightConstraint = - anAccessTarget.getConstraint().getLineOfSightConstraint().value(); + const VisibilityCriteria::LineOfSight visibilityCriteria = + anAccessTarget.getVisibilityCriteria().as().value(); - condition = [&fromPositionCoordinate_ITRF, &aToTrajectory, lineOfSightConstraint](const Instant& instant - ) -> bool + condition = [&fromPositionCoordinate_ITRF, &aToTrajectory, visibilityCriteria](const Instant& instant) -> bool { const Vector3d toPositionCoordinates_ITRF = aToTrajectory.getStateAt(instant).inFrame(Frame::ITRF()).getPosition().getCoordinates(); - return lineOfSightConstraint.isSatisfied(instant, fromPositionCoordinate_ITRF, toPositionCoordinates_ITRF); + return visibilityCriteria.isSatisfied(instant, fromPositionCoordinate_ITRF, toPositionCoordinates_ITRF); }; } - else (anAccessTarget.getConstraint().isElevationIntervalBased()) + else if (anAccessTarget.getVisibilityCriteria().is()) { - const Constraiont::ElevationIntervalConstraint elevationIntervalConstraint = anAccessTarget.getConstraint().getElevationIntervalConstraint().value(); + const VisibilityCriteria::ElevationInterval visibilityCriteria = + anAccessTarget.getVisibilityCriteria().as().value(); - condition = [&computeAER, &fromPositionCoordinate_ITRF, elevationIntervalConstraint](const Instant& instant) -> bool + condition = [&fromPositionCoordinate_ITRF, &SEZRotation, &aToTrajectory, visibilityCriteria]( + const Instant& instant + ) -> bool { const Vector3d toPositionCoordinates_ITRF = aToTrajectory.getStateAt(instant).inFrame(Frame::ITRF()).getPosition().getCoordinates(); const Vector3d dx = toPositionCoordinates_ITRF - fromPositionCoordinate_ITRF; - const Real dx_Z = SEZRotation.row(3) * dx; + const double dx_Z = SEZRotation.row(2) * dx; const double elevation_rad = std::asin(dx_Z / dx.norm()); - return elevationIntervalConstraint.isSatisfied(elevation_rad); + return visibilityCriteria.isSatisfied(elevation_rad); }; } else { - throw ostk::core::error::RuntimeError("Constraint type not supported."); + throw ostk::core::error::RuntimeError("VisibilityCriteria type not supported."); } Array preciseAccessIntervals = accessIntervals; @@ -1053,7 +1043,7 @@ GeneratorContext::GeneratorContext( { } -bool GeneratorContext::isAccessActive(const Instant& anInstant, const Constraint& aConstraint) +bool GeneratorContext::isAccessActive(const Instant& anInstant, const VisibilityCriteria& aVisibilityCriteria) { const auto [fromState, toState] = GeneratorContext::GetStatesAt(anInstant, this->fromTrajectory_, this->toTrajectory_); @@ -1065,23 +1055,26 @@ bool GeneratorContext::isAccessActive(const Instant& anInstant, const Constraint const auto [fromPosition, toPosition] = GeneratorContext::GetPositionsFromStates(fromState, toState); - if (aConstraint.isLineOfSightBased()) + const Position fromPosition_ITRF = fromPosition.inFrame(Frame::ITRF(), anInstant); + const Position toPosition_ITRF = toPosition.inFrame(Frame::ITRF(), anInstant); + + if (aVisibilityCriteria.is()) { - return aConstraint.getLineOfSightConstraint().value().isSatisfied( - anInstant, fromPosition.accessCoordinates(), toPosition.accessCoordinates() + return aVisibilityCriteria.as().value().isSatisfied( + anInstant, fromPosition_ITRF.accessCoordinates(), toPosition_ITRF.accessCoordinates() ); } const AER aer = GeneratorContext::CalculateAer(anInstant, fromPosition, toPosition, earthSPtr_); - if (aConstraint.isMaskBased()) + if (aVisibilityCriteria.is()) { - return aConstraint.getMaskConstraint().value().isSatisfied(aer); + return aVisibilityCriteria.as().value().isSatisfied(aer); } - if (aConstraint.isAERIntervalBased()) + if (aVisibilityCriteria.is()) { - return aConstraint.getAERIntervalConstraint().value().isSatisfied(aer); + return aVisibilityCriteria.as().value().isSatisfied(aer); } return false; diff --git a/src/OpenSpaceToolkit/Astrodynamics/Access/VisibilityCriteria.cpp b/src/OpenSpaceToolkit/Astrodynamics/Access/VisibilityCriteria.cpp new file mode 100644 index 000000000..b51384130 --- /dev/null +++ b/src/OpenSpaceToolkit/Astrodynamics/Access/VisibilityCriteria.cpp @@ -0,0 +1,38 @@ +/// Apache License 2.0 + +#include + +namespace ostk +{ +namespace astrodynamics +{ +namespace access +{ + +VisibilityCriteria VisibilityCriteria::FromAERInterval( + const Interval& azimuth, const Interval& elevation, const Interval& range +) +{ + return VisibilityCriteria(AERInterval {azimuth, elevation, range}); +} + +VisibilityCriteria VisibilityCriteria::FromAERMask( + const Map& azimuthElevationMask, const Interval& range +) +{ + return VisibilityCriteria(AERMask {azimuthElevationMask, range}); +} + +VisibilityCriteria VisibilityCriteria::FromLineOfSight(const Environment& environment) +{ + return VisibilityCriteria(LineOfSight {environment}); +} + +VisibilityCriteria VisibilityCriteria::FromElevationInterval(const Interval& anElevationInterval) +{ + return VisibilityCriteria(ElevationInterval {anElevationInterval}); +} + +} // namespace access +} // namespace astrodynamics +} // namespace ostk diff --git a/src/OpenSpaceToolkit/Astrodynamics/Access/VisibilityCriteria/AERInterval.cpp b/src/OpenSpaceToolkit/Astrodynamics/Access/VisibilityCriteria/AERInterval.cpp new file mode 100644 index 000000000..587f7fb05 --- /dev/null +++ b/src/OpenSpaceToolkit/Astrodynamics/Access/VisibilityCriteria/AERInterval.cpp @@ -0,0 +1,80 @@ +/// Apache License 2.0 + +#include + +namespace ostk +{ +namespace astrodynamics +{ +namespace access +{ + +VisibilityCriteria::AERInterval::AERInterval( + const Interval& anAzimuthInterval, + const Interval& anElevationInterval, + const Interval& aRangeInterval +) + : azimuth(anAzimuthInterval), + elevation(anElevationInterval), + range(aRangeInterval) +{ + if (!azimuth.isDefined() || !elevation.isDefined() || !range.isDefined()) + { + throw ostk::core::error::runtime::Undefined("Interval"); + } + + if (azimuth.getLowerBound() < 0.0 || azimuth.getUpperBound() > 360.0) + { + throw ostk::core::error::RuntimeError( + "The azimuth interval [{}, {}] must be in the range [0, 360] deg", + azimuth.accessLowerBound(), + azimuth.accessUpperBound() + ); + } + + if (elevation.getLowerBound() < -90.0 || elevation.getUpperBound() > 90.0) + { + throw ostk::core::error::RuntimeError( + "The elevation interval [{}, {}] must be in the range [-90, 90] deg.", + elevation.accessLowerBound(), + elevation.accessUpperBound() + ); + } + + if (range.getLowerBound() < 0.0) + { + throw ostk::core::error::RuntimeError( + "The range interval [{}, {}] must be positive.", range.accessLowerBound(), range.accessUpperBound() + ); + } + + azimuth = Interval( + anAzimuthInterval.getLowerBound() * M_PI / 180.0, + anAzimuthInterval.getUpperBound() * M_PI / 180.0, + anAzimuthInterval.getType() + ); + elevation = Interval( + anElevationInterval.getLowerBound() * M_PI / 180.0, + anElevationInterval.getUpperBound() * M_PI / 180.0, + anAzimuthInterval.getType() + ); +} + +bool VisibilityCriteria::AERInterval::isSatisfied(const AER& anAer) const +{ + return this->isSatisfied( + anAer.getAzimuth().inRadians(), anAer.getElevation().inRadians(), anAer.getRange().inMeters() + ); +} + +bool VisibilityCriteria::AERInterval::isSatisfied( + const Real& anAzimuth_Radians, const Real& anElevation_Radians, const Real& aRange_Meters +) const +{ + return azimuth.contains(anAzimuth_Radians) && elevation.contains(anElevation_Radians) && + range.contains(aRange_Meters); +} + +} // namespace access +} // namespace astrodynamics +} // namespace ostk diff --git a/src/OpenSpaceToolkit/Astrodynamics/Access/VisibilityCriteria/AERMask.cpp b/src/OpenSpaceToolkit/Astrodynamics/Access/VisibilityCriteria/AERMask.cpp new file mode 100644 index 000000000..03be4c44e --- /dev/null +++ b/src/OpenSpaceToolkit/Astrodynamics/Access/VisibilityCriteria/AERMask.cpp @@ -0,0 +1,97 @@ +/// Apache License 2.0 + +#include + +namespace ostk +{ +namespace astrodynamics +{ +namespace access +{ + +using ostk::mathematics::object::Vector2d; + +VisibilityCriteria::AERMask::AERMask( + const Map& anAzimuthElevationMask, const Interval& aRangeInterval +) + : azimuthElevationMask(anAzimuthElevationMask), + range(aRangeInterval) +{ + if (range.getLowerBound() < 0.0) + { + throw ostk::core::error::RuntimeError( + "The range interval [{}, {}] must be positive.", range.accessLowerBound(), range.accessUpperBound() + ); + } + + if ((anAzimuthElevationMask.empty()) || (anAzimuthElevationMask.begin()->first < 0.0) || + (anAzimuthElevationMask.rbegin()->first > 360.0)) + { + throw ostk::core::error::runtime::Wrong("Azimuth-Elevation Mask"); + } + + for (const auto& azimuthElevationPair : anAzimuthElevationMask) + { + if ((azimuthElevationPair.second).abs() > 90.0) + { + throw ostk::core::error::runtime::Wrong("Azimuth-Elevation Mask"); + } + } + + Map anAzimuthElevationMask_deg = anAzimuthElevationMask; + + if (anAzimuthElevationMask_deg.begin()->first != 0.0) + { + anAzimuthElevationMask_deg.insert({0.0, anAzimuthElevationMask_deg.begin()->second}); + } + + if (anAzimuthElevationMask_deg.rbegin()->first != 360.0) + { + anAzimuthElevationMask_deg.insert({360.0, anAzimuthElevationMask_deg.begin()->second}); + } + + Map anAzimuthElevationMask_rad; + + for (auto it = anAzimuthElevationMask_deg.begin(); it != anAzimuthElevationMask_deg.end(); ++it) + { + anAzimuthElevationMask_rad.insert({it->first * M_PI / 180.0, it->second * M_PI / 180.0}); + } + + this->azimuthElevationMask = anAzimuthElevationMask_rad; +} + +bool VisibilityCriteria::AERMask::isSatisfied(const AER& anAer) const +{ + return isSatisfied( + anAer.getAzimuth().inRadians(0.0, Real::TwoPi()), + anAer.getElevation().inRadians(-Real::Pi(), Real::Pi()), + anAer.getRange().inMeters() + ); +} + +bool VisibilityCriteria::AERMask::isSatisfied( + const Real& anAzimuth_Radians, const Real& anElevation_Radians, const Real& aRange_Meters +) const +{ + auto itLow = this->azimuthElevationMask.lower_bound(anAzimuth_Radians); + itLow--; + auto itUp = this->azimuthElevationMask.upper_bound(anAzimuth_Radians); + + // Vector between the two successive mask data points with bounding azimuth values + + const Vector2d lowToUpVector = {itUp->first - itLow->first, itUp->second - itLow->second}; + + // Vector from data point with azimuth lower bound to tested point + + const Vector2d lowToPointVector = {anAzimuth_Radians - itLow->first, anElevation_Radians - itLow->second}; + + // If the determinant of these two vectors is positive, the tested point lies above the function defined by the + // mask + + return ((lowToUpVector[0] * lowToPointVector[1] - lowToUpVector[1] * lowToPointVector[0]) >= 0.0) && + range.contains(aRange_Meters); +} + +} // namespace access +} // namespace astrodynamics +} // namespace ostk diff --git a/src/OpenSpaceToolkit/Astrodynamics/Access/VisibilityCriteria/ElevationInterval.cpp b/src/OpenSpaceToolkit/Astrodynamics/Access/VisibilityCriteria/ElevationInterval.cpp new file mode 100644 index 000000000..0aa3e2466 --- /dev/null +++ b/src/OpenSpaceToolkit/Astrodynamics/Access/VisibilityCriteria/ElevationInterval.cpp @@ -0,0 +1,38 @@ +/// Apache License 2.0 + +#include + +namespace ostk +{ +namespace astrodynamics +{ +namespace access +{ + +VisibilityCriteria::ElevationInterval::ElevationInterval(const Interval& anElevationInterval) + : elevation(anElevationInterval) +{ + if (elevation.getLowerBound() < -90.0 || elevation.getUpperBound() > 90.0) + { + throw ostk::core::error::RuntimeError( + "The elevation interval [{}, {}] must be in the range [-90, 90] deg.", + elevation.accessLowerBound(), + elevation.accessUpperBound() + ); + } + + elevation = Interval( + anElevationInterval.getLowerBound() * M_PI / 180.0, + anElevationInterval.getUpperBound() * M_PI / 180.0, + anElevationInterval.getType() + ); +} + +bool VisibilityCriteria::ElevationInterval::isSatisfied(const Real& anElevation) const +{ + return elevation.contains(anElevation); +} + +} // namespace access +} // namespace astrodynamics +} // namespace ostk diff --git a/src/OpenSpaceToolkit/Astrodynamics/Access/VisibilityCriteria/LineOfSight.cpp b/src/OpenSpaceToolkit/Astrodynamics/Access/VisibilityCriteria/LineOfSight.cpp new file mode 100644 index 000000000..0e92ac697 --- /dev/null +++ b/src/OpenSpaceToolkit/Astrodynamics/Access/VisibilityCriteria/LineOfSight.cpp @@ -0,0 +1,55 @@ +/// Apache License 2.0 + +#include +#include + +#include + +#include + +namespace ostk +{ +namespace astrodynamics +{ +namespace access +{ + +using ostk::core::type::Shared; + +using ostk::mathematics::geometry::d3::object::Point; +using ostk::mathematics::geometry::d3::object::Segment; + +using ostk::physics::coordinate::Frame; +using ostk::physics::environment::Object; + +VisibilityCriteria::LineOfSight::LineOfSight(const Environment& anEnvironment) + : environment(anEnvironment) +{ +} + +bool VisibilityCriteria::LineOfSight::isSatisfied( + const Instant& anInstant, const Vector3d& aFromPositionCoordinates_ITRF, const Vector3d& aToPositionCoordinates_ITRF +) const +{ + static const Shared commonFrameSPtr = Frame::ITRF(); + + this->environment.setInstant(anInstant); + + const Point fromPositionCoordinates = Point::Vector(aFromPositionCoordinates_ITRF); + const Point toPositionCoordinates = Point::Vector(aToPositionCoordinates_ITRF); + + if (fromPositionCoordinates == toPositionCoordinates) + { + return true; + } + + const Segment fromToSegment = {fromPositionCoordinates, toPositionCoordinates}; + + const Object::Geometry fromToSegmentGeometry = {fromToSegment, commonFrameSPtr}; + + return !this->environment.intersects(fromToSegmentGeometry); +} + +} // namespace access +} // namespace astrodynamics +} // namespace ostk diff --git a/test/Global.test.hpp b/test/Global.test.hpp index 8834758fb..5691e0714 100644 --- a/test/Global.test.hpp +++ b/test/Global.test.hpp @@ -40,6 +40,52 @@ } \ } while (0) +#define EXPECT_MATRICES_ALMOST_EQUAL(m1, m2, tolerance) \ + do \ + { \ + if ((m1).rows() != (m2).rows() || (m1).cols() != (m2).cols()) \ + { \ + ADD_FAILURE() << "Matrices have different sizes."; \ + } \ + else \ + { \ + for (int i = 0; i < (m1).rows(); ++i) \ + { \ + for (int j = 0; j < (m1).cols(); ++j) \ + { \ + if (std::abs((m1)(i, j) - (m2)(i, j)) > (tolerance)) \ + { \ + ADD_FAILURE() << "Matrices differ at index (" << i << ", " << j << "). Values: " << (m1)(i, j) \ + << " vs. " << (m2)(i, j); \ + } \ + } \ + } \ + } \ + } while (0) + +#define ASSERT_MATRICES_ALMOST_EQUAL(m1, m2, tolerance) \ + do \ + { \ + if ((m1).rows() != (m2).rows() || (m1).cols() != (m2).cols()) \ + { \ + FAIL() << "Matrices have different sizes."; \ + } \ + else \ + { \ + for (int i = 0; i < (m1).rows(); ++i) \ + { \ + for (int j = 0; j < (m1).cols(); ++j) \ + { \ + if (std::abs((m1)(i, j) - (m2)(i, j)) > (tolerance)) \ + { \ + FAIL() << "Matrices differ at index (" << i << ", " << j << "). Values: " << (m1)(i, j) \ + << " vs. " << (m2)(i, j); \ + } \ + } \ + } \ + } \ + } while (0) + namespace ostk { namespace astrodynamics diff --git a/test/OpenSpaceToolkit/Astrodynamics/Access/Generator.test.cpp b/test/OpenSpaceToolkit/Astrodynamics/Access/Generator.test.cpp index be3def2af..457964da4 100644 --- a/test/OpenSpaceToolkit/Astrodynamics/Access/Generator.test.cpp +++ b/test/OpenSpaceToolkit/Astrodynamics/Access/Generator.test.cpp @@ -56,8 +56,8 @@ using ostk::physics::unit::Length; using ostk::astrodynamics::Access; using ostk::astrodynamics::access::AccessTarget; -using ostk::astrodynamics::access::Constraint; using ostk::astrodynamics::access::Generator; +using ostk::astrodynamics::access::VisibilityCriteria; using ostk::astrodynamics::Trajectory; using ostk::astrodynamics::trajectory::Orbit; using ostk::astrodynamics::trajectory::orbit::model::Kepler; @@ -238,12 +238,12 @@ TEST_F(OpenSpaceToolkit_Astrodynamics_Access_Generator, GetConditionFunction) const Orbit fromOrbit = generateFirstOrbit(); const Orbit toOrbit = generateSecondOrbit(); - const Constraint constraint = Constraint::FromAERIntervals( + const VisibilityCriteria visibilityCriteria = VisibilityCriteria::FromAERInterval( ostk::mathematics::object::Interval::Closed(0.0, 360.0), ostk::mathematics::object::Interval::Closed(-90.0, 90.0), ostk::mathematics::object::Interval::Closed(0.0, 1.0e10) ); - const AccessTarget accessTarget = AccessTarget::FromTrajectory(constraint, fromOrbit); + const AccessTarget accessTarget = AccessTarget::FromTrajectory(visibilityCriteria, fromOrbit); { const auto conditionFunction = defaultGenerator_.getConditionFunction(accessTarget, toOrbit); @@ -319,13 +319,9 @@ TEST_F(OpenSpaceToolkit_Astrodynamics_Access_Generator, ComputeAccesses_1) const Orbit fromOrbit = generateFirstOrbit(); const Orbit toOrbit = generateSecondOrbit(); - const Constraint constraint = Constraint::FromAERIntervals( - ostk::mathematics::object::Interval::Closed(0.0, 360.0), - ostk::mathematics::object::Interval::Closed(-90.0, 90.0), - ostk::mathematics::object::Interval::Closed(0.0, 1.0e10) - ); + const VisibilityCriteria visibilityCriteria = VisibilityCriteria::FromLineOfSight(defaultEnvironment_); - const AccessTarget accessTarget = AccessTarget::FromTrajectory(constraint, fromOrbit); + const AccessTarget accessTarget = AccessTarget::FromTrajectory(visibilityCriteria, fromOrbit); const Array accesses = defaultGenerator_.computeAccesses(interval, accessTarget, toOrbit); @@ -406,12 +402,10 @@ TEST_F(OpenSpaceToolkit_Astrodynamics_Access_Generator, ComputeAccesses_2) return orbit; }; - const Constraint constraint = Constraint::FromAERIntervals( - ostk::mathematics::object::Interval::Closed(0.0, 360.0), - ostk::mathematics::object::Interval::Closed(-90.0, 90.0), - ostk::mathematics::object::Interval::Closed(0.0, 1.0e10) - ); - const AccessTarget groundStationTarget = AccessTarget::FromLLA(constraint, groundStationLla, defaultEarthSPtr_); + const VisibilityCriteria visibilityCriteria = VisibilityCriteria::FromLineOfSight(defaultEnvironment_); + + const AccessTarget groundStationTarget = + AccessTarget::FromLLA(visibilityCriteria, groundStationLla, defaultEarthSPtr_); const Orbit satelliteOrbit = generateSatelliteOrbit(); @@ -477,19 +471,15 @@ TEST_F(OpenSpaceToolkit_Astrodynamics_Access_Generator, ComputeAccesses_3) const SGP4 orbitalModel = {tle}; - const Orbit orbit = {orbitalModel, defaultEnvironment_.accessCelestialObjectWithName("Earth")}; + const Orbit orbit = {orbitalModel, defaultEarthSPtr_}; return orbit; }; - // TBI: Convert to LOS trajectory - const Constraint constraint = Constraint::FromAERIntervals( - ostk::mathematics::object::Interval::Closed(0.0, 360.0), - ostk::mathematics::object::Interval::Closed(-90.0, 90.0), - ostk::mathematics::object::Interval::Closed(0.0, 1.0e10) - ); + const VisibilityCriteria visibilityCriteria = VisibilityCriteria::FromLineOfSight(defaultEnvironment_); - const AccessTarget groundStationTarget = AccessTarget::FromLLA(constraint, groundStationLla, defaultEarthSPtr_); + const AccessTarget groundStationTarget = + AccessTarget::FromLLA(visibilityCriteria, groundStationLla, defaultEarthSPtr_); const Orbit satelliteOrbit = generateSatelliteOrbit(); const Array accesses = defaultGenerator_.computeAccesses(interval, groundStationTarget, satelliteOrbit); @@ -571,14 +561,9 @@ TEST_F(OpenSpaceToolkit_Astrodynamics_Access_Generator, ComputeAccesses_4) Position::Meters({0.0, 0.0, 0.0}, Frame::GCRF()), Velocity::MetersPerSecond({1.0, 0.0, 0.0}, Frame::GCRF()) ); - const AccessTarget accessTarget = AccessTarget::FromTrajectory( - Constraint::FromAERIntervals( - ostk::mathematics::object::Interval::Closed(0.0, 360.0), - ostk::mathematics::object::Interval::Closed(-90.0, 90.0), - ostk::mathematics::object::Interval::Closed(0.0, 1.0e10) - ), - firstTrajectory - ); + const VisibilityCriteria visibilityCriteria = VisibilityCriteria::FromLineOfSight(defaultEnvironment_); + + const AccessTarget accessTarget = AccessTarget::FromTrajectory(visibilityCriteria, firstTrajectory); const Trajectory secondTrajectory = generateTrajectory( Position::Meters({0.0, 0.0, 0.0}, Frame::GCRF()), Velocity::MetersPerSecond({0.0, 0.0, 1.0}, Frame::GCRF()) @@ -645,23 +630,23 @@ TEST_F(OpenSpaceToolkit_Astrodynamics_Access_Generator, AccessTarget) // Constructor { { - const Constraint constraint = - Constraint::FromAERIntervals(azimuthInterval, elevationInterval, rangeInterval); + const VisibilityCriteria visibilityCriteria = + VisibilityCriteria::FromAERInterval(azimuthInterval, elevationInterval, rangeInterval); - EXPECT_NO_THROW(AccessTarget::FromLLA(constraint, lla, defaultEarthSPtr_)); + EXPECT_NO_THROW(AccessTarget::FromLLA(visibilityCriteria, lla, defaultEarthSPtr_)); } { - const Constraint constraint = - Constraint::FromAERIntervals(azimuthInterval, elevationInterval, rangeInterval); + const VisibilityCriteria visibilityCriteria = + VisibilityCriteria::FromAERInterval(azimuthInterval, elevationInterval, rangeInterval); { const Position position = Position::Meters({0.0, 0.0, 0.0}, Frame::ITRF()); - EXPECT_NO_THROW(AccessTarget::FromPosition(constraint, position)); + EXPECT_NO_THROW(AccessTarget::FromPosition(visibilityCriteria, position)); } { const Position position = Position::Meters({0.0, 0.0, 0.0}, Frame::GCRF()); - EXPECT_THROW(AccessTarget::FromPosition(constraint, position), ostk::core::error::RuntimeError); + EXPECT_THROW(AccessTarget::FromPosition(visibilityCriteria, position), ostk::core::error::RuntimeError); } } @@ -671,7 +656,7 @@ TEST_F(OpenSpaceToolkit_Astrodynamics_Access_Generator, AccessTarget) EXPECT_THROW( AccessTarget::FromPosition( - Constraint::FromAERIntervals( + VisibilityCriteria::FromAERInterval( ostk::mathematics::object::Interval::Undefined(), elevationInterval, rangeInterval ), position @@ -680,7 +665,7 @@ TEST_F(OpenSpaceToolkit_Astrodynamics_Access_Generator, AccessTarget) ); EXPECT_THROW( AccessTarget::FromPosition( - Constraint::FromAERIntervals( + VisibilityCriteria::FromAERInterval( azimuthInterval, ostk::mathematics::object::Interval::Undefined(), rangeInterval ), position @@ -689,7 +674,7 @@ TEST_F(OpenSpaceToolkit_Astrodynamics_Access_Generator, AccessTarget) ); EXPECT_THROW( AccessTarget::FromPosition( - Constraint::FromAERIntervals( + VisibilityCriteria::FromAERInterval( azimuthInterval, elevationInterval, ostk::mathematics::object::Interval::Undefined() ), position @@ -698,7 +683,7 @@ TEST_F(OpenSpaceToolkit_Astrodynamics_Access_Generator, AccessTarget) ); EXPECT_THROW( AccessTarget::FromLLA( - Constraint::FromAERIntervals(azimuthInterval, elevationInterval, rangeInterval), + VisibilityCriteria::FromAERInterval(azimuthInterval, elevationInterval, rangeInterval), LLA::Undefined(), defaultEarthSPtr_ ), @@ -711,7 +696,7 @@ TEST_F(OpenSpaceToolkit_Astrodynamics_Access_Generator, AccessTarget) { EXPECT_THROW( AccessTarget::FromLLA( - Constraint::FromAERIntervals( + VisibilityCriteria::FromAERInterval( ostk::mathematics::object::Interval::Closed(-1.0, 350.0), elevationInterval, rangeInterval @@ -723,7 +708,7 @@ TEST_F(OpenSpaceToolkit_Astrodynamics_Access_Generator, AccessTarget) ); EXPECT_THROW( AccessTarget::FromLLA( - Constraint::FromAERIntervals( + VisibilityCriteria::FromAERInterval( ostk::mathematics::object::Interval::Closed(0.0, 360.1), elevationInterval, rangeInterval @@ -737,7 +722,7 @@ TEST_F(OpenSpaceToolkit_Astrodynamics_Access_Generator, AccessTarget) { EXPECT_THROW( AccessTarget::FromLLA( - Constraint::FromAERIntervals( + VisibilityCriteria::FromAERInterval( azimuthInterval, ostk::mathematics::object::Interval::Closed(-91.0, 0.0), rangeInterval @@ -749,7 +734,7 @@ TEST_F(OpenSpaceToolkit_Astrodynamics_Access_Generator, AccessTarget) ); EXPECT_THROW( AccessTarget::FromLLA( - Constraint::FromAERIntervals( + VisibilityCriteria::FromAERInterval( azimuthInterval, ostk::mathematics::object::Interval::Closed(-45.0, 91.0), rangeInterval @@ -763,7 +748,7 @@ TEST_F(OpenSpaceToolkit_Astrodynamics_Access_Generator, AccessTarget) { EXPECT_THROW( AccessTarget::FromLLA( - Constraint::FromAERIntervals( + VisibilityCriteria::FromAERInterval( azimuthInterval, elevationInterval, ostk::mathematics::object::Interval::Closed(-1.0, 5.0) @@ -779,15 +764,17 @@ TEST_F(OpenSpaceToolkit_Astrodynamics_Access_Generator, AccessTarget) // Getters { - const Constraint constraint = Constraint::FromAERIntervals(azimuthInterval, elevationInterval, rangeInterval); - const AccessTarget accessTarget = AccessTarget::FromLLA(constraint, lla, defaultEarthSPtr_); - // TBI: Fix the checks below - EXPECT_EQ(accessTarget.getLLA(defaultEarthSPtr_), lla); - EXPECT_EQ( - accessTarget.getPosition(), + const VisibilityCriteria visibilityCriteria = + VisibilityCriteria::FromAERInterval(azimuthInterval, elevationInterval, rangeInterval); + const AccessTarget accessTarget = AccessTarget::FromLLA(visibilityCriteria, lla, defaultEarthSPtr_); + EXPECT_VECTORS_ALMOST_EQUAL(accessTarget.getLLA(defaultEarthSPtr_).toVector(), lla.toVector(), 1e-15); + EXPECT_VECTORS_ALMOST_EQUAL( + accessTarget.getPosition().accessCoordinates(), Position::Meters( lla.toCartesian(Earth::EGM2008.equatorialRadius_, Earth::EGM2008.flattening_), Frame::ITRF() ) + .accessCoordinates(), + 1e-13 ); EXPECT_NO_THROW(accessTarget.getTrajectory()); @@ -796,7 +783,7 @@ TEST_F(OpenSpaceToolkit_Astrodynamics_Access_Generator, AccessTarget) r_SEZ_ECEF.row(1) = Vector3d {0.0, 1.0, 0.0}; r_SEZ_ECEF.row(2) = Vector3d {1.0, 0.0, 0.0}; - EXPECT_EQ(accessTarget.computeR_SEZ_ECEF(defaultEarthSPtr_), r_SEZ_ECEF); + EXPECT_MATRICES_ALMOST_EQUAL(accessTarget.computeR_SEZ_ECEF(defaultEarthSPtr_), r_SEZ_ECEF, 1e-15); } } @@ -838,12 +825,13 @@ TEST_F(OpenSpaceToolkit_Astrodynamics_Access_Generator, ComputeAccesses) const ostk::mathematics::object::Interval rangeInterval = ostk::mathematics::object::Interval::Closed(0.0, 1.0e10); - const Constraint constraint = Constraint::FromAERIntervals(azimuthInterval, elevationInterval, rangeInterval); + const VisibilityCriteria visibilityCriteria = + VisibilityCriteria::FromAERInterval(azimuthInterval, elevationInterval, rangeInterval); Array accessTargets = LLAs.map( - [&constraint, this](const LLA& lla) -> AccessTarget + [&visibilityCriteria, this](const LLA& lla) -> AccessTarget { - return AccessTarget::FromLLA(constraint, lla, defaultEarthSPtr_); + return AccessTarget::FromLLA(visibilityCriteria, lla, defaultEarthSPtr_); } ); @@ -957,7 +945,8 @@ TEST_F(OpenSpaceToolkit_Astrodynamics_Access_Generator, AerRanges_1) const ostk::mathematics::object::Interval rangeRange = ostk::mathematics::object::Interval::Closed(0.0, 10000e3); - const Constraint constraint = Constraint::FromAERIntervals(azimuthRange, elevationRange, rangeRange); + const VisibilityCriteria visibilityCriteria = + VisibilityCriteria::FromAERInterval(azimuthRange, elevationRange, rangeRange); const Instant startInstant = Instant::DateTime(DateTime(2018, 1, 1, 0, 0, 0), Scale::UTC); const Instant endInstant = Instant::DateTime(DateTime(2018, 1, 10, 0, 0, 0), Scale::UTC); @@ -992,7 +981,8 @@ TEST_F(OpenSpaceToolkit_Astrodynamics_Access_Generator, AerRanges_1) return orbit; }; - const AccessTarget groundStationTarget = AccessTarget::FromLLA(constraint, groundStationLla, defaultEarthSPtr_); + const AccessTarget groundStationTarget = + AccessTarget::FromLLA(visibilityCriteria, groundStationLla, defaultEarthSPtr_); const Orbit satelliteOrbit = generateSatelliteOrbit(); const Array accesses = defaultGenerator_.computeAccesses(interval, groundStationTarget, satelliteOrbit); @@ -1051,7 +1041,7 @@ TEST_F(OpenSpaceToolkit_Astrodynamics_Access_Generator, AerMask_1) const ostk::mathematics::object::Interval rangeRange = ostk::mathematics::object::Interval::Closed(0.0, 10000e3); - const Constraint constraint = Constraint::FromMask(azimuthElevationMask, rangeRange); + const VisibilityCriteria visibilityCriteria = VisibilityCriteria::FromAERMask(azimuthElevationMask, rangeRange); const Instant startInstant = Instant::DateTime(DateTime(2018, 1, 1, 0, 0, 0), Scale::UTC); const Instant endInstant = Instant::DateTime(DateTime(2018, 1, 5, 0, 0, 0), Scale::UTC); @@ -1086,7 +1076,8 @@ TEST_F(OpenSpaceToolkit_Astrodynamics_Access_Generator, AerMask_1) return orbit; }; - const AccessTarget groundStationTarget = AccessTarget::FromLLA(constraint, groundStationLla, defaultEarthSPtr_); + const AccessTarget groundStationTarget = + AccessTarget::FromLLA(visibilityCriteria, groundStationLla, defaultEarthSPtr_); const Orbit satelliteOrbit = generateSatelliteOrbit(); From 2bbdf683ddf34845c6dbbe4739fc64098374bcc3 Mon Sep 17 00:00:00 2001 From: vishwa shah Date: Thu, 28 Nov 2024 07:46:29 +0000 Subject: [PATCH 13/22] feat: fix coarse issues --- .../Astrodynamics/Access/Generator.hpp | 35 +-- .../Astrodynamics/Access/Generator.cpp | 246 +++++++++++------- 2 files changed, 153 insertions(+), 128 deletions(-) diff --git a/include/OpenSpaceToolkit/Astrodynamics/Access/Generator.hpp b/include/OpenSpaceToolkit/Astrodynamics/Access/Generator.hpp index 2d2cdb85c..4d024553f 100644 --- a/include/OpenSpaceToolkit/Astrodynamics/Access/Generator.hpp +++ b/include/OpenSpaceToolkit/Astrodynamics/Access/Generator.hpp @@ -211,13 +211,17 @@ class Generator ) const; Array computeAccesses( - const physics::time::Interval& anInterval, const AccessTarget& anAccessTarget, const Trajectory& aToTrajectory + const physics::time::Interval& anInterval, + const AccessTarget& anAccessTarget, + const Trajectory& aToTrajectory, + const bool& coarse = false ) const; Array> computeAccesses( const physics::time::Interval& anInterval, const Array& someAccessTargets, - const Trajectory& aToTrajectory + const Trajectory& aToTrajectory, + const bool& coarse = false ) const; void setStep(const Duration& aStep); @@ -289,25 +293,6 @@ class Generator const Trajectory& aToTrajectory, const Shared anEarthSPtr ); -}; - -class GeneratorContext -{ - public: - GeneratorContext( - const Trajectory& aFromTrajectory, - const Trajectory& aToTrajectory, - const Environment& anEnvironment, - const Generator& aGenerator - ); - - bool isAccessActive(const Instant& anInstant, const VisibilityCriteria& aVisibilityCriteria); - - static Pair GetStatesAt( - const Instant& anInstant, const Trajectory& aFromTrajectory, const Trajectory& aToTrajectory - ); - - static Pair GetPositionsFromStates(const State& aFromState, const State& aToState); static AER CalculateAer( const Instant& anInstant, @@ -315,14 +300,6 @@ class GeneratorContext const Position& aToPosition, const Shared anEarthSPtr ); - - private: - Trajectory fromTrajectory_; - Trajectory toTrajectory_; - Environment environment_; - const Shared earthSPtr_; - - Generator generator_; }; } // namespace access diff --git a/src/OpenSpaceToolkit/Astrodynamics/Access/Generator.cpp b/src/OpenSpaceToolkit/Astrodynamics/Access/Generator.cpp index 74c4de004..44815275c 100644 --- a/src/OpenSpaceToolkit/Astrodynamics/Access/Generator.cpp +++ b/src/OpenSpaceToolkit/Astrodynamics/Access/Generator.cpp @@ -212,16 +212,52 @@ std::function Generator::getConditionFunction( const VisibilityCriteria visibilityCriteria = anAccessTarget.getVisibilityCriteria(); - GeneratorContext generatorContext = GeneratorContext(aFromTrajectory, aToTrajectory, environment_, *this); - - return [generatorContext, visibilityCriteria](const Instant& anInstant) mutable -> bool + return [visibilityCriteria, aFromTrajectory, &aToTrajectory, this](const Instant& anInstant) mutable -> bool { - return generatorContext.isAccessActive(anInstant, visibilityCriteria); + const State fromState = aFromTrajectory.getStateAt(anInstant); + const State toState = aToTrajectory.getStateAt(anInstant); + + if (this->getStateFilter() && (!this->getStateFilter()(fromState, toState))) + { + return false; + } + + const Position fromPosition = fromState.getPosition(); + const Position toPosition = toState.getPosition(); + + const Position fromPosition_ITRF = fromPosition.inFrame(Frame::ITRF(), anInstant); + const Position toPosition_ITRF = toPosition.inFrame(Frame::ITRF(), anInstant); + + if (visibilityCriteria.is()) + { + return visibilityCriteria.as().value().isSatisfied( + anInstant, fromPosition_ITRF.accessCoordinates(), toPosition_ITRF.accessCoordinates() + ); + } + + const AER aer = Generator::CalculateAer( + anInstant, fromPosition, toPosition, this->environment_.accessCelestialObjectWithName("Earth") + ); + + if (visibilityCriteria.is()) + { + return visibilityCriteria.as().value().isSatisfied(aer); + } + + if (visibilityCriteria.is()) + { + return visibilityCriteria.as().value().isSatisfied(aer); + } + + return false; }; } Array Generator::computeAccesses( - const physics::time::Interval& anInterval, const AccessTarget& anAccessTarget, const Trajectory& aToTrajectory + const physics::time::Interval& anInterval, + const AccessTarget& anAccessTarget, + const Trajectory& aToTrajectory, + const bool& coarse ) const { if (!anInterval.isDefined()) @@ -236,16 +272,24 @@ Array Generator::computeAccesses( if (anAccessTarget.getType() == AccessTarget::Type::Trajectory) { + if (coarse) + { + throw ostk::core::error::RuntimeError("Coarse mode is not supported for trajectory targets."); + } + return this->computeAccessesForTrajectoryTarget(anInterval, anAccessTarget, aToTrajectory); } - return this->computeAccessesForFixedTargets(anInterval, Array {anAccessTarget}, aToTrajectory)[0]; + return this->computeAccessesForFixedTargets( + anInterval, Array {anAccessTarget}, aToTrajectory, coarse + )[0]; } Array> Generator::computeAccesses( const physics::time::Interval& anInterval, const Array& someAccessTargets, - const Trajectory& aToTrajectory + const Trajectory& aToTrajectory, + const bool& coarse ) const { if (!anInterval.isDefined()) @@ -272,6 +316,11 @@ Array> Generator::computeAccesses( } )) { + if (coarse) + { + throw ostk::core::error::RuntimeError("Coarse mode is not supported for trajectory targets."); + } + Array> accessesPerTarget; accessesPerTarget.reserve(someAccessTargets.getSize()); @@ -294,7 +343,7 @@ Array> Generator::computeAccesses( } )) { - return this->computeAccessesForFixedTargets(anInterval, someAccessTargets, aToTrajectory); + return this->computeAccessesForFixedTargets(anInterval, someAccessTargets, aToTrajectory, coarse); } throw ostk::core::error::RuntimeError("All targets must be of same type."); @@ -872,6 +921,7 @@ Array Generator::ComputeIntervals(const VectorXi& inAcc if (diff[j] == -1) { endInstant = instants[j - 1]; + accessIntervals.add(physics::time::Interval::Closed(startInstant, endInstant)); } } @@ -901,11 +951,21 @@ Access Generator::GenerateAccess( : Access::Type::Partial; const Instant acquisitionOfSignal = anAccessInterval.getStart(); + + ostk::physics::time::Interval accessInterval = anAccessInterval; + + if (anAccessInterval.getDuration() == Duration::Zero()) + { + accessInterval = physics::time::Interval::Closed( + anAccessInterval.getStart() - Duration::Seconds(60.0), anAccessInterval.getStart() + Duration::Seconds(60.0) + ); + } + const Instant timeOfClosestApproach = Generator::FindTimeOfClosestApproach(anAccessInterval, aFromTrajectory, aToTrajectory, aTolerance); const Instant lossOfSignal = anAccessInterval.getEnd(); - if (!timeOfClosestApproach.isDefined()) + if (!timeOfClosestApproach.isDefined() and type == Access::Type::Complete) { throw ostk::core::error::RuntimeError( "Cannot find TCA (solution did not converge): {} - {}.", @@ -933,15 +993,12 @@ Instant Generator::FindTimeOfClosestApproach( { const Instant& startInstant; const std::function(const Instant& anInstant)>& getStatesAt; - const std::function(const State& aFromState, const State& aToState)>& - getPositionsFromStates; }; const auto calculateRange = [](const std::vector& x, std::vector& aGradient, void* aDataContext ) -> double { - (void)aGradient; - + // iterationCount++; if (aDataContext == nullptr) { throw ostk::core::error::runtime::Wrong("Data context"); @@ -952,25 +1009,36 @@ Instant Generator::FindTimeOfClosestApproach( const Instant queryInstant = contextPtr->startInstant + Duration::Seconds(x[0]); const auto [queryFromState, queryToState] = contextPtr->getStatesAt(queryInstant); - const auto [queryFromPosition, queryToPosition] = - contextPtr->getPositionsFromStates(queryFromState, queryToState); + const auto queryFromPosition = queryFromState.getPosition(); + const auto queryToPosition = queryToState.getPosition(); - const Real squaredRange_m = - (queryToPosition.accessCoordinates() - queryFromPosition.accessCoordinates()).squaredNorm(); + const Vector3d deltaPosition = + queryToState.getPosition().accessCoordinates() - queryFromState.getPosition().accessCoordinates(); + const Vector3d deltaVelocity = + queryToState.getVelocity().accessCoordinates() - queryFromState.getVelocity().accessCoordinates(); - return squaredRange_m; + const Real rangeSquared = deltaPosition.squaredNorm(); + + if (!aGradient.empty()) + { + aGradient[0] = 2.0 * deltaPosition.dot(deltaVelocity); + } + + return rangeSquared; }; Context context = { anAccessInterval.getStart(), [&aFromTrajectory, &aToTrajectory](const Instant& anInstant) -> Pair { - return GeneratorContext::GetStatesAt(anInstant, aFromTrajectory, aToTrajectory); + return { + aFromTrajectory.getStateAt(anInstant), + aToTrajectory.getStateAt(anInstant), + }; }, - GeneratorContext::GetPositionsFromStates }; - nlopt::opt optimizer = {nlopt::LN_COBYLA, 1}; + nlopt::opt optimizer = {nlopt::LD_MMA, 1}; const std::vector lowerBound = {0.0}; const std::vector upperBound = {anAccessInterval.getDuration().inSeconds()}; @@ -1010,10 +1078,58 @@ Instant Generator::FindTimeOfClosestApproach( } catch (const std::exception& anException) { + std::cout << anAccessInterval << std::endl; throw ostk::core::error::RuntimeError("Cannot find TCA (algorithm failed): [{}].", anException.what()); } } +// Instant Generator::FindTimeOfClosestApproach( +// const physics::time::Interval& anAccessInterval, +// const Trajectory& aFromTrajectory, +// const Trajectory& aToTrajectory, +// const Duration& aTolerance +// ) +// { +// const Instant startInstant = anAccessInterval.getStart(); +// const double durationSeconds = anAccessInterval.getDuration().inSeconds(); + +// // Function representing the derivative of the range between the trajectories +// const auto rangeDerivativeFunction = [&aFromTrajectory, &aToTrajectory, &startInstant](const double t) -> double +// { +// const Instant instant = startInstant + Duration::Seconds(t); +// const State fromState = aFromTrajectory.getStateAt(instant); +// const State toState = aToTrajectory.getStateAt(instant); + +// const Vector3d deltaPosition = +// toState.getPosition().accessCoordinates() - fromState.getPosition().accessCoordinates(); +// const Vector3d deltaVelocity = +// toState.getVelocity().accessCoordinates() - fromState.getVelocity().accessCoordinates(); + +// // Derivative of the range with respect to time +// return deltaPosition.dot(deltaVelocity) / deltaPosition.norm(); +// }; + +// const RootSolver rootSolver(20, aTolerance.inSeconds()); + +// try +// { +// const auto solution = rootSolver.solve(rangeDerivativeFunction, 0.0, durationSeconds); + +// if (!solution.hasConverged) +// { +// return Instant::Undefined(); +// } + +// return startInstant + Duration::Seconds(solution.root); +// } +// catch (const std::exception& anException) +// { +// return Instant::Undefined(); +// } + +// return Instant::Undefined(); +// } + Angle Generator::CalculateElevationAt( const Instant& anInstant, const Trajectory& aFromTrajectory, @@ -1021,91 +1137,23 @@ Angle Generator::CalculateElevationAt( const Shared anEarthSPtr ) { - const auto [fromState, toState] = GeneratorContext::GetStatesAt(anInstant, aFromTrajectory, aToTrajectory); - const auto [fromPosition, toPosition] = GeneratorContext::GetPositionsFromStates(fromState, toState); - - const AER aer = GeneratorContext::CalculateAer(anInstant, fromPosition, toPosition, anEarthSPtr); - - return aer.getElevation(); -} - -GeneratorContext::GeneratorContext( - const Trajectory& aFromTrajectory, - const Trajectory& aToTrajectory, - const Environment& anEnvironment, - const Generator& aGenerator -) - : fromTrajectory_(aFromTrajectory), - toTrajectory_(aToTrajectory), - environment_(anEnvironment), - earthSPtr_(environment_.accessCelestialObjectWithName("Earth")), // [TBR] This is Earth specific - generator_(aGenerator) -{ -} - -bool GeneratorContext::isAccessActive(const Instant& anInstant, const VisibilityCriteria& aVisibilityCriteria) -{ - const auto [fromState, toState] = - GeneratorContext::GetStatesAt(anInstant, this->fromTrajectory_, this->toTrajectory_); - - if (this->generator_.getStateFilter() && (!this->generator_.getStateFilter()(fromState, toState))) - { - return false; - } - - const auto [fromPosition, toPosition] = GeneratorContext::GetPositionsFromStates(fromState, toState); - - const Position fromPosition_ITRF = fromPosition.inFrame(Frame::ITRF(), anInstant); - const Position toPosition_ITRF = toPosition.inFrame(Frame::ITRF(), anInstant); - - if (aVisibilityCriteria.is()) - { - return aVisibilityCriteria.as().value().isSatisfied( - anInstant, fromPosition_ITRF.accessCoordinates(), toPosition_ITRF.accessCoordinates() - ); - } - - const AER aer = GeneratorContext::CalculateAer(anInstant, fromPosition, toPosition, earthSPtr_); - - if (aVisibilityCriteria.is()) - { - return aVisibilityCriteria.as().value().isSatisfied(aer); - } - - if (aVisibilityCriteria.is()) - { - return aVisibilityCriteria.as().value().isSatisfied(aer); - } - - return false; -} - -Pair GeneratorContext::GetStatesAt( - const Instant& anInstant, const Trajectory& aFromTrajectory, const Trajectory& aToTrajectory -) -{ - const State fromState = aFromTrajectory.getStateAt(anInstant); - const State toState = aToTrajectory.getStateAt(anInstant); + const Position fromPosition = aFromTrajectory.getStateAt(anInstant).getPosition(); + const Position toPosition = aToTrajectory.getStateAt(anInstant).getPosition(); - return {fromState, toState}; -} + const LLA lla = LLA::Cartesian( + fromPosition.accessCoordinates(), anEarthSPtr->getEquatorialRadius(), anEarthSPtr->getFlattening() + ); -Pair GeneratorContext::GetPositionsFromStates(const State& aFromState, const State& aToState) -{ - if (aFromState.accessInstant() != aToState.accessInstant()) - { - throw ostk::core::error::RuntimeError("Cannot get positions from states at different instants."); - } + const Vector3d rotationZ = {-std::cos(lla.getLatitude().inRadians()), 0.0, std::sin(lla.getLatitude().inRadians())}; - static const Shared commonFrameSPtr = Frame::GCRF(); + const Vector3d fromToVector = toPosition.accessCoordinates() - fromPosition.accessCoordinates(); - const Position fromPosition = aFromState.getPosition().inFrame(commonFrameSPtr, aFromState.accessInstant()); - const Position toPosition = aToState.getPosition().inFrame(commonFrameSPtr, aFromState.accessInstant()); + const double dx_Z = rotationZ.dot(fromToVector); - return {fromPosition, toPosition}; + return Angle::Radians(std::asin(dx_Z / fromToVector.norm())); } -AER GeneratorContext::CalculateAer( +AER Generator::CalculateAer( const Instant& anInstant, const Position& aFromPosition, const Position& aToPosition, From dd7f6c95be37f3947f9d663ca34c6e16e280cf9a Mon Sep 17 00:00:00 2001 From: vishwa shah Date: Wed, 4 Dec 2024 18:41:14 +0000 Subject: [PATCH 14/22] feat: add bindings and tests --- .../Astrodynamics/Access.benchmark.cpp | 15 +- .../Access.cpp | 2 + .../Access/Generator.cpp | 277 +++++++---- .../Access/VisibilityCriterion.cpp | 449 +++++++++++++++++ bindings/python/test/access/test_generator.py | 96 ++++ .../test/access/test_visibility_criterion.py | 193 ++++++++ .../Astrodynamics/Access/Generator.hpp | 24 +- .../Access/VisibilityCriteria.hpp | 130 ----- .../Access/VisibilityCriterion.hpp | 271 ++++++++++ .../Astrodynamics/Access/Generator.cpp | 181 +++---- .../Access/VisibilityCriteria.cpp | 38 -- .../Access/VisibilityCriterion.cpp | 48 ++ .../AERInterval.cpp | 23 +- .../AERMask.cpp | 18 +- .../ElevationInterval.cpp | 20 +- .../LineOfSight.cpp | 16 +- .../Astrodynamics/Access/Generator.test.cpp | 461 ++++++++++-------- .../Access/VisibilityCriterion.test.cpp | 256 ++++++++++ 18 files changed, 1955 insertions(+), 563 deletions(-) create mode 100644 bindings/python/src/OpenSpaceToolkitAstrodynamicsPy/Access/VisibilityCriterion.cpp create mode 100644 bindings/python/test/access/test_visibility_criterion.py delete mode 100644 include/OpenSpaceToolkit/Astrodynamics/Access/VisibilityCriteria.hpp create mode 100644 include/OpenSpaceToolkit/Astrodynamics/Access/VisibilityCriterion.hpp delete mode 100644 src/OpenSpaceToolkit/Astrodynamics/Access/VisibilityCriteria.cpp create mode 100644 src/OpenSpaceToolkit/Astrodynamics/Access/VisibilityCriterion.cpp rename src/OpenSpaceToolkit/Astrodynamics/Access/{VisibilityCriteria => VisibilityCriterion}/AERInterval.cpp (74%) rename src/OpenSpaceToolkit/Astrodynamics/Access/{VisibilityCriteria => VisibilityCriterion}/AERMask.cpp (86%) rename src/OpenSpaceToolkit/Astrodynamics/Access/{VisibilityCriteria => VisibilityCriterion}/ElevationInterval.cpp (54%) rename src/OpenSpaceToolkit/Astrodynamics/Access/{VisibilityCriteria => VisibilityCriterion}/LineOfSight.cpp (74%) create mode 100644 test/OpenSpaceToolkit/Astrodynamics/Access/VisibilityCriterion.test.cpp diff --git a/benchmark/OpenSpaceToolkit/Astrodynamics/Access.benchmark.cpp b/benchmark/OpenSpaceToolkit/Astrodynamics/Access.benchmark.cpp index cdae0e108..e53a9ab0c 100644 --- a/benchmark/OpenSpaceToolkit/Astrodynamics/Access.benchmark.cpp +++ b/benchmark/OpenSpaceToolkit/Astrodynamics/Access.benchmark.cpp @@ -14,6 +14,7 @@ #include #include +#include #include #include #include @@ -36,7 +37,9 @@ using ostk::physics::unit::Angle; using ostk::physics::unit::Length; using ostk::astrodynamics::Access; +using ostk::astrodynamics::access::AccessTarget; using ostk::astrodynamics::access::Generator; +using ostk::astrodynamics::access::VisibilityCriterion; using ostk::astrodynamics::Trajectory; using ostk::astrodynamics::trajectory::Orbit; using ostk::astrodynamics::trajectory::orbit::model::SGP4; @@ -50,13 +53,15 @@ static const Instant REFERENCE_START_INSTANT = Instant::DateTime(DateTime(2023, static const Instant REFERENCE_END_INSTANT = Instant::DateTime(DateTime(2023, 1, 8, 0, 0, 0), Scale::UTC); -static void computeAccesses(benchmark::State& state, const Trajectory& aFromTrajectory, const Trajectory& aToTrajectory) +static void computeAccesses( + benchmark::State& state, const AccessTarget& anAccessTarget, const Trajectory& aToTrajectory +) { const Generator generator = {REFERENCE_ENVIRONMENT}; const Interval interval = Interval::Closed(REFERENCE_START_INSTANT, REFERENCE_END_INSTANT); state.ResumeTiming(); - benchmark::DoNotOptimize(generator.computeAccesses(interval, aFromTrajectory, aToTrajectory)); + benchmark::DoNotOptimize(generator.computeAccesses(interval, anAccessTarget, aToTrajectory)); } static void benchmark001(benchmark::State& state) @@ -70,7 +75,9 @@ static void benchmark001(benchmark::State& state) const Position groundStationPosition = Position::Meters( groundStationLla.toCartesian(Earth::EGM2008.equatorialRadius_, Earth::EGM2008.flattening_), Frame::ITRF() ); - const Trajectory groundStationTrajectory = Trajectory::Position(groundStationPosition); + const VisibilityCriterion visibilityCriterion = VisibilityCriterion::FromLineOfSight(REFERENCE_ENVIRONMENT); + const AccessTarget groundStationAccessTarget = + AccessTarget::FromPosition(visibilityCriterion, groundStationPosition); // Satellite const TLE tle = { @@ -80,7 +87,7 @@ static void benchmark001(benchmark::State& state) const SGP4 sgp4 = {tle}; const Orbit satelliteOrbit = {sgp4, REFERENCE_ENVIRONMENT.accessCelestialObjectWithName("Earth")}; - computeAccesses(state, groundStationTrajectory, satelliteOrbit); + computeAccesses(state, groundStationAccessTarget, satelliteOrbit); } } diff --git a/bindings/python/src/OpenSpaceToolkitAstrodynamicsPy/Access.cpp b/bindings/python/src/OpenSpaceToolkitAstrodynamicsPy/Access.cpp index 905b4484b..9de73f47b 100644 --- a/bindings/python/src/OpenSpaceToolkitAstrodynamicsPy/Access.cpp +++ b/bindings/python/src/OpenSpaceToolkitAstrodynamicsPy/Access.cpp @@ -3,6 +3,7 @@ #include #include +#include inline void OpenSpaceToolkitAstrodynamicsPy_Access(pybind11::module& aModule) { @@ -199,4 +200,5 @@ inline void OpenSpaceToolkitAstrodynamicsPy_Access(pybind11::module& aModule) // Add elements to "access" module OpenSpaceToolkitAstrodynamicsPy_Access_Generator(access); + OpenSpaceToolkitAstrodynamicsPy_Access_VisibilityCriterion(access); } diff --git a/bindings/python/src/OpenSpaceToolkitAstrodynamicsPy/Access/Generator.cpp b/bindings/python/src/OpenSpaceToolkitAstrodynamicsPy/Access/Generator.cpp index 9574f9204..72987a63f 100644 --- a/bindings/python/src/OpenSpaceToolkitAstrodynamicsPy/Access/Generator.cpp +++ b/bindings/python/src/OpenSpaceToolkitAstrodynamicsPy/Access/Generator.cpp @@ -8,6 +8,7 @@ inline void OpenSpaceToolkitAstrodynamicsPy_Access_Generator(pybind11::module& a { using namespace pybind11; + using ostk::core::container::Array; using ostk::core::container::Map; using ostk::core::type::Real; using ostk::core::type::Shared; @@ -15,11 +16,150 @@ inline void OpenSpaceToolkitAstrodynamicsPy_Access_Generator(pybind11::module& a using ostk::physics::coordinate::spherical::AER; using ostk::physics::Environment; using ostk::physics::time::Duration; + using ostk::physics::time::Interval; using ostk::astrodynamics::Access; + using ostk::astrodynamics::access::AccessTarget; using ostk::astrodynamics::access::Generator; + using ostk::astrodynamics::Trajectory; using ostk::astrodynamics::trajectory::State; + class_ accessTargetClass( + aModule, + "AccessTarget", + R"doc( + Represents the configuration for an Access target, including azimuth, elevation, and range intervals, as well + as position and LLA (Latitude, Longitude, Altitude). + )doc" + ); + + enum_(accessTargetClass, "Type", R"doc( + Enumeration of Access Target types. + )doc") + .value("Fixed", AccessTarget::Type::Fixed) + .value("Trajectory", AccessTarget::Type::Trajectory) + .export_values(); + + accessTargetClass + .def( + "get_type", + &AccessTarget::getType, + R"doc( + Get the type of the access target. + + Returns: + AccessTarget.Type: The type of the access target. + )doc" + ) + .def( + "get_visibility_criterion", + &AccessTarget::getVisibilityCriterion, + R"doc( + Get the visibility criterion associated with the access target. + + Returns: + VisibilityCriterion: The visibility criterion. + )doc" + ) + .def( + "get_trajectory", + &AccessTarget::getTrajectory, + R"doc( + Get the trajectory associated with the access target. + + Returns: + Trajectory: The trajectory. + )doc" + ) + .def( + "get_position", + &AccessTarget::getPosition, + R"doc( + Get the fixed position associated with the access target. + + Returns: + Position: The position. + )doc" + ) + .def( + "get_lla", + &AccessTarget::getLLA, + arg("celestial"), + R"doc( + Get the latitude, longitude, and altitude (LLA) of the access target. + + Args: + celestial (Celestial): The celestial body for the LLA computation. + + Returns: + LLA: The latitude, longitude, and altitude. + )doc" + ) + .def( + "compute_r_sez_ecef", + &AccessTarget::computeR_SEZ_ECEF, + arg("celestial"), + R"doc( + Compute the rotation matrix from ECEF to SEZ frame. + + Args: + celestial (Celestial): The celestial body for the rotation computation. + + Returns: + numpy.ndarray: The rotation matrix (3x3). + )doc" + ) + .def_static( + "from_lla", + &AccessTarget::FromLLA, + arg("visibility_criterion"), + arg("lla"), + arg("celestial"), + R"doc( + Create an AccessTarget from latitude, longitude, and altitude (LLA). + + Args: + visibility_criterion (VisibilityCriterion): The visibility criterion. + lla (LLA): The latitude, longitude, and altitude. + celestial (Celestial): The celestial body. + + Returns: + AccessTarget: The created AccessTarget instance. + )doc" + ) + .def_static( + "from_position", + &AccessTarget::FromPosition, + arg("visibility_criterion"), + arg("position"), + R"doc( + Create an AccessTarget from a fixed position. + + Args: + visibility_criterion (VisibilityCriterion): The visibility criterion. + position (Position): The fixed position. + + Returns: + AccessTarget: The created AccessTarget instance. + )doc" + ) + .def_static( + "from_trajectory", + &AccessTarget::FromTrajectory, + arg("visibility_criterion"), + arg("trajectory"), + R"doc( + Create an AccessTarget from a trajectory. + + Args: + visibility_criterion (VisibilityCriterion): The visibility criterion. + trajectory (Trajectory): The trajectory. + + Returns: + AccessTarget: The created AccessTarget instance. + )doc" + ); + class_>( aModule, "Generator", @@ -32,29 +172,26 @@ inline void OpenSpaceToolkitAstrodynamicsPy_Access_Generator(pybind11::module& a .def( init< const Environment&, - std::function&, - std::function&, - std::function&, const Duration&, - const Duration&>(), + const Duration&, + std::function&, + std::function&>(), R"doc( Constructor. Args: environment (Environment): The environment. - aer_filter (function): The AER filter. - access_filter (function): The access filter. - state_filter (function): The state filter. - step (Duration): The step. - tolerance (Duration): The tolerance. + step (Duration): The step. Defaults to Duration.minutes(1.0). + tolerance (Duration): The tolerance. Defaults to Duration.microseconds(1.0). + access_filter (function): The access filter. Defaults to None. + state_filter (function): The state filter. Defaults to None. )doc", arg("environment"), - arg("aer_filter") = none(), - arg("access_filter") = none(), - arg("state_filter") = none(), arg_v("step", DEFAULT_STEP, "Duration.minutes(1.0)"), - arg_v("tolerance", DEFAULT_TOLERANCE, "Duration.microseconds(1.0)") + arg_v("tolerance", DEFAULT_TOLERANCE, "Duration.microseconds(1.0)"), + arg("access_filter") = none(), + arg("state_filter") = none() ) .def( @@ -91,17 +228,6 @@ inline void OpenSpaceToolkitAstrodynamicsPy_Access_Generator(pybind11::module& a )doc" ) - .def( - "get_aer_filter", - &Generator::getAerFilter, - R"doc( - Get the AER filter. - - Returns: - function: The AER filter. - - )doc" - ) .def( "get_access_filter", &Generator::getAccessFilter, @@ -129,37 +255,64 @@ inline void OpenSpaceToolkitAstrodynamicsPy_Access_Generator(pybind11::module& a "get_condition_function", &Generator::getConditionFunction, R"doc( - Get the condition function. + Get the condition function. - Args: - from_trajectory (State): The state at the start of the interval. - to_trajectory (State): The state at the end of the interval. + Args: + from_trajectory (State): The state at the start of the interval. + to_trajectory (State): The state at the end of the interval. - Returns: - function: The condition function. + Returns: + function: The condition function. - )doc", + )doc", arg("from_trajectory"), arg("to_trajectory") ) .def( "compute_accesses", - &Generator::computeAccesses, + overload_cast( + &Generator::computeAccesses, const_ + ), R"doc( Compute the accesses. Args: - interval (Interval): The interval. - from_trajectory (State): The state at the start of the interval. - to_trajectory (State): The state at the end of the interval. + interval (Interval): The time interval over which to compute accesses. + access_target (AccessTarget): The access target to compute the accesses with. + to_trajectory (Trajectory): The trajectory to co compute the accesses with. + coarse (bool): True to use coarse mode. Defaults to False. Only available for fixed targets. Returns: Accesses: The accesses. )doc", arg("interval"), - arg("from_trajectory"), - arg("to_trajectory") + arg("access_target"), + arg("to_trajectory"), + arg("coarse") = false + ) + .def( + "compute_accesses", + overload_cast&, const Trajectory&, const bool&>( + &Generator::computeAccesses, const_ + ), + R"doc( + Compute the accesses. + + Args: + interval (Interval): The time interval over which to compute accesses. + access_targets (list[AccessTarget]): The access targets to compute the accesses with. + to_trajectory (Trajectory): The trajectory to co compute the accesses with. + coarse (bool): True to use coarse mode. Defaults to False. Only available for fixed targets. + + Returns: + Accesses: The accesses. + + )doc", + arg("interval"), + arg("access_targets"), + arg("to_trajectory"), + arg("coarse") = false ) .def( "set_step", @@ -185,18 +338,6 @@ inline void OpenSpaceToolkitAstrodynamicsPy_Access_Generator(pybind11::module& a )doc", arg("tolerance") ) - .def( - "set_aer_filter", - &Generator::setAerFilter, - R"doc( - Set the AER filter. - - Args: - aer_filter (function): The AER filter. - - )doc", - arg("aer_filter") - ) .def( "set_access_filter", &Generator::setAccessFilter, @@ -232,44 +373,6 @@ inline void OpenSpaceToolkitAstrodynamicsPy_Access_Generator(pybind11::module& a Generator: An undefined generator. )doc" ) - .def_static( - "aer_ranges", - &Generator::AerRanges, - R"doc( - Create an access generator with provided Azimuth Elevation Range intervals. - - Args: - azimuth_range (Interval): The azimuth range. - elevation_range (Interval): The elevation range. - range_range (Interval): The range range. - environment (Environment): The environment. - - Returns: - Generator: The access generator. - )doc", - arg("azimuth_range"), - arg("elevation_range"), - arg("range_range"), - arg("environment") - ) - .def_static( - "aer_mask", - &Generator::AerMask, - R"doc( - Create an access generator with a mask. - - Args: - azimuth_elevation_mask (Interval): The azimuth-elevation mask. - range_range (Interval): The range range. - environment (Environment): The environment. - - Returns: - Generator: The AER generator. - )doc", - arg("azimuth_elevation_mask"), - arg("range_range"), - arg("environment") - ) ; } diff --git a/bindings/python/src/OpenSpaceToolkitAstrodynamicsPy/Access/VisibilityCriterion.cpp b/bindings/python/src/OpenSpaceToolkitAstrodynamicsPy/Access/VisibilityCriterion.cpp new file mode 100644 index 000000000..ad4c34b40 --- /dev/null +++ b/bindings/python/src/OpenSpaceToolkitAstrodynamicsPy/Access/VisibilityCriterion.cpp @@ -0,0 +1,449 @@ +// VisibilityCriterion.cpp + +#include +#include + +#include +#include + +#include + +#include +#include +#include + +#include + +void OpenSpaceToolkitAstrodynamicsPy_Access_VisibilityCriterion(pybind11::module& aModule) +{ + using namespace pybind11; + + using ostk::astrodynamics::access::VisibilityCriterion; + using ostk::core::container::Map; + using ostk::core::type::Real; + using ostk::mathematics::object::Interval; + using ostk::physics::coordinate::spherical::AER; + using ostk::physics::Environment; + + class_ visibilityCriterionClass( + aModule, + "VisibilityCriterion", + R"doc( + A class representing a visibility criterion for accesses between objects. + )doc" + ); + + class_( + visibilityCriterionClass, + "AERInterval", + R"doc( + An AER interval visibility criterion. + )doc" + ) + .def( + init&, const Interval&, const Interval&>(), + arg("azimuth_interval"), + arg("elevation_interval"), + arg("range_interval"), + R"doc( + Constructs an AER interval. + + Args: + azimuth_interval (RealInterval): Azimuth interval in degrees. + elevation_interval (RealInterval): Elevation interval in degrees. + range_interval (RealInterval): Range interval in meters. + )doc" + ) + .def_readonly( + "azimuth", + &VisibilityCriterion::AERInterval::azimuth, + R"doc( + Azimuth interval in radians. + + :type: RealInterval + )doc" + ) + .def_readonly( + "elevation", + &VisibilityCriterion::AERInterval::elevation, + R"doc( + Elevation interval in radians. + + :type: RealInterval + )doc" + ) + .def_readonly( + "range", + &VisibilityCriterion::AERInterval::range, + R"doc( + Range interval in meters. + + :type: RealInterval + )doc" + ) + .def( + "is_satisfied", + overload_cast(&VisibilityCriterion::AERInterval::isSatisfied, const_), + arg("aer"), + R"doc( + Checks if the given AER satisfies the criterion. + + Args: + aer (AER): The Azimuth, Elevation, and Range to check. + + Returns: + bool: True if the criterion is satisfied, False otherwise. + )doc" + ) + .def( + "is_satisfied", + overload_cast( + &VisibilityCriterion::AERInterval::isSatisfied, const_ + ), + arg("azimuth"), + arg("elevation"), + arg("range"), + R"doc( + Checks if the given Azimuth, Elevation, and Range values satisfy the criterion. + + Args: + azimuth (float): Azimuth in radians. + elevation (float): Elevation in radians. + range (float): Range in meters. + + Returns: + bool: True if the criterion is satisfied, False otherwise. + )doc" + ); + + class_( + visibilityCriterionClass, + "AERMask", + R"doc( + An AER mask visibility criterion. + )doc" + ) + .def( + init&, const Interval&>(), + arg("azimuth_elevation_mask"), + arg("range_interval"), + R"doc( + Constructs an AER mask. + + Args: + azimuth_elevation_mask (dict): A map of azimuth angles (degrees) to elevation angles (degrees). + range_interval (RealInterval): Range interval in meters. + )doc" + ) + .def_readonly( + "azimuth_elevation_mask", + &VisibilityCriterion::AERMask::azimuthElevationMask, + R"doc( + A map of azimuth angles to elevation angles in radians defining the mask. + + :type: dict + )doc" + ) + .def_readonly( + "range", + &VisibilityCriterion::AERMask::range, + R"doc( + Range interval in meters. + + :type: RealInterval + )doc" + ) + .def( + "is_satisfied", + overload_cast(&VisibilityCriterion::AERMask::isSatisfied, const_), + arg("aer"), + R"doc( + Checks if the given AER satisfies the criterion. + + Args: + aer (AER): The Azimuth, Elevation, and Range to check. + + Returns: + bool: True if the criterion is satisfied, False otherwise. + )doc" + ) + .def( + "is_satisfied", + overload_cast(&VisibilityCriterion::AERMask::isSatisfied, const_), + arg("azimuth"), + arg("elevation"), + arg("range"), + R"doc( + Checks if the given Azimuth, Elevation, and Range values satisfy the criterion. + + Args: + azimuth (float): Azimuth in radians. + elevation (float): Elevation in radians. + range (float): Range in meters. + + Returns: + bool: True if the criterion is satisfied, False otherwise. + )doc" + ); + + class_( + visibilityCriterionClass, + "LineOfSight", + R"doc( + A line-of-sight visibility criterion. + )doc" + ) + .def( + init(), + arg("environment"), + R"doc( + Constructs a LineOfSight visibility criterion. + + Args: + environment (Environment): The environment to consider for line-of-sight calculations. + )doc" + ) + .def( + "is_satisfied", + &VisibilityCriterion::LineOfSight::isSatisfied, + arg("instant"), + arg("from_position_coordinates"), + arg("to_position_coordinates"), + R"doc( + Checks if the line-of-sight criterion is satisfied between two positions at a given instant. + + Args: + instant (Instant): The time at which to perform the check. + from_position_coordinates (np.ndarray): The position coordinates (in meters) of the observer. + to_position_coordinates (np.ndarray): The position coordinates (in meters) of the target. + + Returns: + bool: True if there is a clear line of sight, False otherwise. + )doc" + ); + + class_( + visibilityCriterionClass, + "ElevationInterval", + R"doc( + An elevation interval visibility criterion. + )doc" + ) + .def( + init&>(), + arg("elevation_interval"), + R"doc( + Constructs an ElevationInterval visibility criterion. + + Args: + elevation_interval (RealInterval): The elevation interval in degrees. + )doc" + ) + .def_readonly( + "elevation", + &VisibilityCriterion::ElevationInterval::elevation, + R"doc( + Elevation interval in radians. + + :type: RealInterval + )doc" + ) + .def( + "is_satisfied", + &VisibilityCriterion::ElevationInterval::isSatisfied, + arg("elevation"), + R"doc( + Checks if the given elevation angle satisfies the criterion. + + Args: + elevation (float): Elevation angle in radians. + + Returns: + bool: True if the criterion is satisfied, False otherwise. + )doc" + ); + + visibilityCriterionClass + .def_static( + "from_aer_interval", + &VisibilityCriterion::FromAERInterval, + arg("azimuth_interval"), + arg("elevation_interval"), + arg("range_interval"), + R"doc( + Creates a visibility criterion from azimuth, elevation, and range intervals. + + Args: + azimuth_interval (RealInterval): Azimuth interval in degrees. + elevation_interval (RealInterval): Elevation interval in degrees. + range_interval (RealInterval): Range interval in meters. + + Returns: + VisibilityCriterion: The visibility criterion instance. + )doc" + ) + .def_static( + "from_aer_mask", + &VisibilityCriterion::FromAERMask, + arg("azimuth_elevation_mask"), + arg("range_interval"), + R"doc( + Creates a visibility criterion from an azimuth-elevation mask and range interval. + + Args: + azimuth_elevation_mask (dict): A map of azimuth angles (degrees) to elevation angles (degrees). + range_interval (RealInterval): Range interval in meters. + + Returns: + VisibilityCriterion: The visibility criterion instance. + )doc" + ) + .def_static( + "from_line_of_sight", + &VisibilityCriterion::FromLineOfSight, + arg("environment"), + R"doc( + Creates a visibility criterion based on line-of-sight considerations. + + Args: + environment (Environment): The environment to consider for line-of-sight calculations. + + Returns: + VisibilityCriterion: The visibility criterion instance. + )doc" + ) + .def_static( + "from_elevation_interval", + &VisibilityCriterion::FromElevationInterval, + arg("elevation_interval"), + R"doc( + Creates a visibility criterion from an elevation interval. + + Args: + elevation_interval (RealInterval): The elevation interval in radians. + + Returns: + VisibilityCriterion: The visibility criterion instance. + )doc" + ) + .def( + "is_aer_interval", + &VisibilityCriterion::is, + R"doc( + Checks if the visibility criterion is an AERInterval. + + Returns: + bool: True if it is an AERInterval criterion, False otherwise. + )doc" + ) + .def( + "is_aer_mask", + &VisibilityCriterion::is, + R"doc( + Checks if the visibility criterion is an AERMask. + + Returns: + bool: True if it is an AERMask criterion, False otherwise. + )doc" + ) + .def( + "is_line_of_sight", + &VisibilityCriterion::is, + R"doc( + Checks if the visibility criterion is a LineOfSight. + + Returns: + bool: True if it is a LineOfSight criterion, False otherwise. + )doc" + ) + .def( + "is_elevation_interval", + &VisibilityCriterion::is, + R"doc( + Checks if the visibility criterion is an ElevationInterval. + + Returns: + bool: True if it is an ElevationInterval criterion, False otherwise. + )doc" + ) + .def( + "as_aer_interval", + [](const VisibilityCriterion& self) + { + const auto result = self.as(); + if (result.has_value()) + return result; + else + throw value_error("VisibilityCriterion is not AERInterval"); + }, + R"doc( + Casts the visibility criterion to an AERInterval. + + Returns: + AERInterval: The AERInterval criterion. + + Raises: + ValueError: If the criterion is not an AERInterval. + )doc" + ) + .def( + "as_aer_mask", + [](const VisibilityCriterion& self) + { + const auto result = self.as(); + if (result.has_value()) + return result; + else + throw value_error("VisibilityCriterion is not AERMask"); + }, + R"doc( + Casts the visibility criterion to an AERMask. + + Returns: + AERMask: The AERMask criterion. + + Raises: + ValueError: If the criterion is not an AERMask. + )doc" + ) + .def( + "as_line_of_sight", + [](const VisibilityCriterion& self) + { + const auto result = self.as(); + if (result.has_value()) + return result; + else + throw value_error("VisibilityCriterion is not LineOfSight"); + }, + R"doc( + Casts the visibility criterion to a LineOfSight. + + Returns: + LineOfSight: The LineOfSight criterion. + + Raises: + ValueError: If the criterion is not a LineOfSight. + )doc" + ) + .def( + "as_elevation_interval", + [](const VisibilityCriterion& self) + { + const auto result = self.as(); + if (result.has_value()) + return result; + else + throw value_error("VisibilityCriterion is not ElevationInterval"); + }, + R"doc( + Casts the visibility criterion to an ElevationInterval. + + Returns: + ElevationInterval: The ElevationInterval criterion. + + Raises: + ValueError: If the criterion is not an ElevationInterval. + )doc" + ); +} diff --git a/bindings/python/test/access/test_generator.py b/bindings/python/test/access/test_generator.py index 3f5337a64..8eac319af 100644 --- a/bindings/python/test/access/test_generator.py +++ b/bindings/python/test/access/test_generator.py @@ -2,6 +2,8 @@ import pytest +import numpy as np + from ostk.mathematics.object import RealInterval from ostk.physics.unit import Length @@ -12,7 +14,11 @@ from ostk.physics.time import Instant from ostk.physics.time import Interval from ostk.physics import Environment +from ostk.physics.coordinate import Position +from ostk.physics.coordinate import Frame +from ostk.physics.coordinate.spherical import LLA from ostk.physics.environment.object import Celestial +from ostk.physics.environment.object.celestial import Earth from ostk.astrodynamics import Trajectory from ostk.astrodynamics.trajectory import Orbit @@ -20,6 +26,8 @@ from ostk.astrodynamics.trajectory.orbit.model.kepler import COE from ostk.astrodynamics import Access from ostk.astrodynamics.access import Generator +from ostk.astrodynamics.access import AccessTarget +from ostk.astrodynamics.access import VisibilityCriterion @pytest.fixture @@ -82,6 +90,94 @@ def to_trajectory(earth: Celestial) -> Trajectory: ) +@pytest.fixture +def visibility_criterion(environment: Environment) -> VisibilityCriterion: + return VisibilityCriterion.from_line_of_sight(environment=environment) + + +@pytest.fixture +def lla() -> LLA: + return LLA.vector([0.0, 0.0, 500.0]) + + +@pytest.fixture +def access_target( + visibility_criterion: VisibilityCriterion, + lla: LLA, + earth: Earth, +) -> AccessTarget: + return AccessTarget.from_lla(visibility_criterion, lla, earth) + + +class TestAccessTarget: + def test_constructor_success(self, access_target: AccessTarget): + assert isinstance(access_target, AccessTarget) + + def test_get_type_success(self, access_target: AccessTarget): + assert access_target.get_type() == AccessTarget.Type.Fixed + + def test_get_visibility_criterion_success(self, access_target: AccessTarget): + assert access_target.get_visibility_criterion() is not None + assert isinstance(access_target.get_visibility_criterion(), VisibilityCriterion) + + def test_get_trajectory_success(self, access_target: AccessTarget): + assert access_target.get_trajectory() is not None + assert isinstance(access_target.get_trajectory(), Trajectory) + + def test_get_position_success(self, access_target: AccessTarget): + assert access_target.get_position() is not None + assert isinstance(access_target.get_position(), Position) + + def test_get_lla_success( + self, + access_target: AccessTarget, + earth: Earth, + ): + assert access_target.get_lla(earth) is not None + assert isinstance(access_target.get_lla(earth), LLA) + + def test_compute_r_sez_ecef_success( + self, + access_target: AccessTarget, + earth: Earth, + ): + assert access_target.compute_r_sez_ecef(earth) is not None + assert isinstance(access_target.compute_r_sez_ecef(earth), np.ndarray) + + def test_from_lla_success( + self, + visibility_criterion: VisibilityCriterion, + lla: LLA, + earth: Earth, + ): + access_target = AccessTarget.from_lla(visibility_criterion, lla, earth) + + assert access_target is not None + assert isinstance(access_target, AccessTarget) + + def test_from_position_success( + self, + visibility_criterion: VisibilityCriterion, + position: Position, + ): + access_target = AccessTarget.from_position( + visibility_criterion, position.in_frame(Frame.ITRF(), Instant.J2000()) + ) + + assert access_target is not None + assert isinstance(access_target, AccessTarget) + + def test_from_trajectory_success( + self, + visibility_criterion: VisibilityCriterion, + trajectory: Trajectory, + ): + access_target = AccessTarget.from_trajectory(visibility_criterion, trajectory) + + assert access_target is not None + assert isinstance(access_target, AccessTarget) + + class TestGenerator: def test_constructor_success_environment(self, environment: Environment): generator = Generator( diff --git a/bindings/python/test/access/test_visibility_criterion.py b/bindings/python/test/access/test_visibility_criterion.py new file mode 100644 index 000000000..b69df963e --- /dev/null +++ b/bindings/python/test/access/test_visibility_criterion.py @@ -0,0 +1,193 @@ +# Apache License 2.0 + +import pytest +import numpy as np + +from ostk.mathematics.object import RealInterval + +from ostk.physics import Environment +from ostk.physics.time import Instant +from ostk.physics.time import DateTime +from ostk.physics.time import Scale +from ostk.physics.coordinate.spherical import AER + +from ostk.astrodynamics.access import VisibilityCriterion + + +@pytest.fixture() +def instant() -> Instant: + return Instant.date_time(DateTime(2023, 1, 1, 0, 0, 0), Scale.UTC) + + +@pytest.fixture +def environment() -> Environment: + return Environment.default() + + +@pytest.fixture +def azimuth_interval() -> RealInterval: + return RealInterval.closed(0.0, 90.0) + + +@pytest.fixture +def elevation_interval() -> RealInterval: + return RealInterval.closed(0.0, 45.0) + + +@pytest.fixture +def range_interval() -> RealInterval: + return RealInterval.closed(0.0, 1e7) + + +@pytest.fixture +def aer_mask() -> dict[float, float]: + return { + 0.0: 0.0, + 90.0: 45.0, + } + + +class TestVisibilityCriterion: + def test_aer_interval_constructor( + self, + azimuth_interval: RealInterval, + elevation_interval: RealInterval, + range_interval: RealInterval, + ): + aer_interval = VisibilityCriterion.AERInterval( + azimuth_interval, + elevation_interval, + range_interval, + ) + assert aer_interval is not None + assert isinstance(aer_interval, VisibilityCriterion.AERInterval) + + def test_aer_mask_constructor( + self, + aer_mask: dict[float, float], + range_interval: RealInterval, + ): + aer_mask_criterion = VisibilityCriterion.AERMask(aer_mask, range_interval) + assert aer_mask_criterion is not None + assert isinstance(aer_mask_criterion, VisibilityCriterion.AERMask) + + def test_line_of_sight_constructor( + self, + environment: Environment, + ): + line_of_sight = VisibilityCriterion.LineOfSight(environment) + assert line_of_sight is not None + assert isinstance(line_of_sight, VisibilityCriterion.LineOfSight) + + def test_elevation_interval_constructor( + self, + elevation_interval: RealInterval, + ): + elevation_criterion = VisibilityCriterion.ElevationInterval(elevation_interval) + assert elevation_criterion is not None + assert isinstance(elevation_criterion, VisibilityCriterion.ElevationInterval) + + def test_visibility_criterion_from_aer_interval( + self, + azimuth_interval: RealInterval, + elevation_interval: RealInterval, + range_interval: RealInterval, + ): + criterion = VisibilityCriterion.from_aer_interval( + azimuth_interval, elevation_interval, range_interval + ) + assert criterion is not None + assert isinstance(criterion, VisibilityCriterion) + assert criterion.is_aer_interval() + + def test_visibility_criterion_from_aer_mask( + self, + aer_mask: dict[float, float], + range_interval: RealInterval, + ): + criterion = VisibilityCriterion.from_aer_mask(aer_mask, range_interval) + assert criterion is not None + assert isinstance(criterion, VisibilityCriterion) + assert criterion.is_aer_mask() + + def test_visibility_criterion_from_line_of_sight( + self, + environment: Environment, + ): + criterion = VisibilityCriterion.from_line_of_sight(environment) + assert criterion is not None + assert isinstance(criterion, VisibilityCriterion) + assert criterion.is_line_of_sight() + + def test_visibility_criterion_from_elevation_interval( + self, elevation_interval: RealInterval + ): + criterion = VisibilityCriterion.from_elevation_interval(elevation_interval) + assert criterion is not None + assert isinstance(criterion, VisibilityCriterion) + assert criterion.is_elevation_interval() + + def test_aer_interval_is_satisfied( + self, + azimuth_interval: RealInterval, + elevation_interval: RealInterval, + range_interval: RealInterval, + ): + aer_interval = VisibilityCriterion.AERInterval( + azimuth_interval, elevation_interval, range_interval + ) + aer = AER(azimuth=np.pi / 4, elevation=np.pi / 8, range=5e6) + assert aer_interval.is_satisfied(aer) is True + + aer_invalid = AER(azimuth=np.pi, elevation=np.pi / 2, range=1e8) + assert aer_interval.is_satisfied(aer_invalid) is False + + def test_elevation_interval_is_satisfied( + self, + elevation_interval: RealInterval, + ): + elevation_criterion = VisibilityCriterion.ElevationInterval(elevation_interval) + elevation_valid = np.pi / 8 # 22.5 degrees + assert elevation_criterion.is_satisfied(elevation_valid) is True + + elevation_invalid = np.pi / 2 # 90 degrees + assert elevation_criterion.is_satisfied(elevation_invalid) is False + + def test_line_of_sight_is_satisfied( + self, + environment: Environment, + ): + line_of_sight = VisibilityCriterion.LineOfSight(environment) + instant = Instant.now() + from_position = np.array([7000e3, 0.0, 0.0]) # 7000 km altitude + to_position = np.array([0.0, 7000e3, 0.0]) # 7000 km altitude + assert line_of_sight.is_satisfied(instant, from_position, to_position) is True + + def test_visibility_criterion_type_checks( + self, + elevation_interval: RealInterval, + environment: Environment, + ): + criterion = VisibilityCriterion.from_elevation_interval(elevation_interval) + assert criterion.is_elevation_interval() is True + assert criterion.is_aer_interval() is False + assert criterion.is_line_of_sight() is False + + criterion = VisibilityCriterion.from_line_of_sight(environment) + assert criterion.is_line_of_sight() is True + + def test_visibility_criterion_cast( + self, + azimuth_interval: RealInterval, + elevation_interval: RealInterval, + range_interval: RealInterval, + ): + criterion = VisibilityCriterion.from_aer_interval( + azimuth_interval, elevation_interval, range_interval + ) + aer_interval = criterion.as_aer_interval() + assert aer_interval is not None + assert isinstance(aer_interval, VisibilityCriterion.AERInterval) + + with pytest.raises(ValueError): + criterion.as_line_of_sight() diff --git a/include/OpenSpaceToolkit/Astrodynamics/Access/Generator.hpp b/include/OpenSpaceToolkit/Astrodynamics/Access/Generator.hpp index 4d024553f..3821cc309 100644 --- a/include/OpenSpaceToolkit/Astrodynamics/Access/Generator.hpp +++ b/include/OpenSpaceToolkit/Astrodynamics/Access/Generator.hpp @@ -19,7 +19,7 @@ #include #include -#include +#include #include namespace ostk @@ -51,7 +51,7 @@ using ostk::physics::unit::Length; using EarthGravitationalModel = ostk::physics::environment::gravitational::Earth; using ostk::astrodynamics::Access; -using ostk::astrodynamics::access::VisibilityCriteria; +using ostk::astrodynamics::access::VisibilityCriterion; using ostk::astrodynamics::Trajectory; using ostk::astrodynamics::trajectory::State; @@ -86,11 +86,11 @@ class AccessTarget /// /// @code{.cpp} /// AccessTarget accessTarget = { ... } ; - /// VisibilityCriteria visibilityCriteria = accessTarget.getVisibilityCriteria(); + /// VisibilityCriterion visibilityCriterion = accessTarget.getVisibilityCriterion(); /// @endcode /// /// @return The visibility criteria - VisibilityCriteria getVisibilityCriteria() const; + VisibilityCriterion getVisibilityCriterion() const; /// @brief Get the trajectory /// @@ -139,7 +139,7 @@ class AccessTarget /// /// @code{.cpp} /// AccessTarget accessTarget = AccessTarget::LLA( - /// visibilityCriteria, lla, celestialSPtr + /// visibilityCriterion, lla, celestialSPtr /// ); /// @endcode /// @@ -148,41 +148,41 @@ class AccessTarget /// @param aCelestialSPtr /// @return Access target static AccessTarget FromLLA( - const VisibilityCriteria& aVisibilityCriteria, const LLA& anLLA, const Shared& aCelestialSPtr + const VisibilityCriterion& aVisibilityCriterion, const LLA& anLLA, const Shared& aCelestialSPtr ); /// @brief Construct an Access Target from a position /// /// @code{.cpp} /// AccessTarget accessTarget = AccessTarget::Position( - /// visibilityCriteria, position + /// visibilityCriterion, position /// ); /// @endcode /// /// @param constraint /// @param aPosition /// @return Access target - static AccessTarget FromPosition(const VisibilityCriteria& aVisibilityCriteria, const Position& aPosition); + static AccessTarget FromPosition(const VisibilityCriterion& aVisibilityCriterion, const Position& aPosition); /// @brief Construct an Access Target from a trajectory /// /// @code{.cpp} /// AccessTarget accessTarget = AccessTarget::FromTrajectory( - /// visibilityCriteria, trajectory + /// visibilityCriterion, trajectory /// ); /// @endcode /// /// @param constraint /// @param aTrajectory /// @return Access target - static AccessTarget FromTrajectory(const VisibilityCriteria& aVisibilityCriteria, const Trajectory& aTrajectory); + static AccessTarget FromTrajectory(const VisibilityCriterion& aVisibilityCriterion, const Trajectory& aTrajectory); private: Type type_; - VisibilityCriteria visibilityCriteria_; + VisibilityCriterion visibilityCriterion_; Trajectory trajectory_; - AccessTarget(const Type& aType, const VisibilityCriteria& constrant, const Trajectory& aTrajectory); + AccessTarget(const Type& aType, const VisibilityCriterion& constrant, const Trajectory& aTrajectory); }; class Generator diff --git a/include/OpenSpaceToolkit/Astrodynamics/Access/VisibilityCriteria.hpp b/include/OpenSpaceToolkit/Astrodynamics/Access/VisibilityCriteria.hpp deleted file mode 100644 index ffc5ae8df..000000000 --- a/include/OpenSpaceToolkit/Astrodynamics/Access/VisibilityCriteria.hpp +++ /dev/null @@ -1,130 +0,0 @@ -/// Apache License 2.0 - -#ifndef __OpenSpaceToolkit_Astrodynamics_Access_VisibilityCriteria__ -#define __OpenSpaceToolkit_Astrodynamics_Access_VisibilityCriteria__ - -#include -#include - -#include -#include - -#include -#include - -#include -#include -#include -#include - -namespace ostk -{ -namespace astrodynamics -{ -namespace access -{ - -using ostk::core::container::Map; -using ostk::core::type::Real; - -using ostk::mathematics::object::Interval; -using ostk::mathematics::object::Vector3d; - -using ostk::physics::coordinate::spherical::AER; -using ostk::physics::Environment; -using ostk::physics::time::Instant; - -class VisibilityCriteria -{ - public: - struct AERInterval - { - Interval azimuth; // radians - Interval elevation; // radians - Interval range; // meters - - AERInterval(const Interval& anAzimuth, const Interval& anElevation, const Interval& aRange); - - bool isSatisfied(const AER& anAer) const; - - bool isSatisfied(const Real& anAzimuthRadians, const Real& anElevationRadians, const Real& aRangeMeters) const; - }; - - struct AERMask - { - Map azimuthElevationMask; // radians, radians - Interval range; // meters - - AERMask(const Map& anAzimuthElevationMask, const Interval& aRange); - - bool isSatisfied(const AER& anAer) const; - - bool isSatisfied(const Real& anAzimuthRadians, const Real& anElevationRadians, const Real& aRangeMeters) const; - }; - - struct LineOfSight - { - mutable Environment environment; - - LineOfSight(const Environment& anEnvironment); - - bool isSatisfied( - const Instant& anInstant, const Vector3d& aFromPositionCoordinates, const Vector3d& aToPositionCoordinates - ) const; - }; - - struct ElevationInterval - { - Interval elevation; // radians - - ElevationInterval(const Interval& anElevationInterval); - - bool isSatisfied(const Real& anElevation) const; - }; - - static VisibilityCriteria FromAERInterval( - const Interval& anAzimuthInterval, - const Interval& anElevationInterval, - const Interval& aRangeInterval - ); - - static VisibilityCriteria FromAERMask( - const Map& anAzimuthElevationMask, const Interval& aRangeInterval - ); - - static VisibilityCriteria FromLineOfSight(const Environment& anEnvironment); - - static VisibilityCriteria FromElevationInterval(const Interval& anElevationInterval); - - template - bool is() const - { - return std::holds_alternative(visibilityCriteria_); - } - - template - std::optional as() const - { - if (const auto* constraintPtr = std::get_if(&visibilityCriteria_)) - { - return *constraintPtr; - } - - return std::nullopt; - } - - private: - std::variant visibilityCriteria_; - - template - explicit VisibilityCriteria(const T& aVisibilityCriteria) - : visibilityCriteria_(aVisibilityCriteria) - { - } -}; - -} // namespace access -} // namespace astrodynamics -} // namespace ostk - -#endif diff --git a/include/OpenSpaceToolkit/Astrodynamics/Access/VisibilityCriterion.hpp b/include/OpenSpaceToolkit/Astrodynamics/Access/VisibilityCriterion.hpp new file mode 100644 index 000000000..4a58f6366 --- /dev/null +++ b/include/OpenSpaceToolkit/Astrodynamics/Access/VisibilityCriterion.hpp @@ -0,0 +1,271 @@ +/// Apache License 2.0 + +#ifndef __OpenSpaceToolkit_Astrodynamics_Access_VisibilityCriterion__ +#define __OpenSpaceToolkit_Astrodynamics_Access_VisibilityCriterion__ + +#include +#include + +#include +#include + +#include +#include + +#include +#include +#include +#include + +namespace ostk +{ +namespace astrodynamics +{ +namespace access +{ + +using ostk::core::container::Map; +using ostk::core::type::Real; + +using ostk::mathematics::object::Interval; +using ostk::mathematics::object::Vector3d; + +using ostk::physics::coordinate::spherical::AER; +using ostk::physics::Environment; +using ostk::physics::time::Instant; + +/// @brief Represents a visibility criterion for accesses between objects. +class VisibilityCriterion +{ + public: + /// @brief Represents an AER interval visibility criterion. + struct AERInterval + { + Interval azimuth; /// Azimuth interval in radians. + Interval elevation; /// Elevation interval in radians. + Interval range; /// Range interval in meters. + + /// @brief Constructs an AERInterval. + /// + /// @param anAzimuth The azimuth interval. + /// @param anElevation The elevation interval. + /// @param aRange The range interval. + AERInterval(const Interval& anAzimuth, const Interval& anElevation, const Interval& aRange); + + /// @brief Checks if the given AER satisfies the criterion. + /// + /// @param anAer The AER to check. + /// @return True if the criterion is satisfied. + bool isSatisfied(const AER& anAer) const; + + /// @brief Checks if the given azimuth, elevation, and range values satisfy the criterion. + /// + /// @param anAzimuthRadians The azimuth in radians. + /// @param anElevationRadians The elevation in radians. + /// @param aRangeMeters The range in meters. + /// @return True if the criterion is satisfied. + bool isSatisfied(const Real& anAzimuthRadians, const Real& anElevationRadians, const Real& aRangeMeters) const; + + /// @brief Equality operator. + /// + /// @param anAerInterval Another AERInterval. + /// @return True if equal. + bool operator==(const AERInterval& anAerInterval) const; + + /// @brief Inequality operator. + /// + /// @param anAerInterval Another AERInterval. + /// @return True if not equal. + bool operator!=(const AERInterval& anAerInterval) const; + }; + + /// @brief Represents an AER mask visibility criterion. + struct AERMask + { + Map azimuthElevationMask; /// Map of azimuth angles to elevation angles in radians. + Interval range; /// Range interval in meters. + + /// @brief Constructs an AERMask. + /// + /// @param anAzimuthElevationMask The azimuth-elevation mask. + /// @param aRange The range interval. + AERMask(const Map& anAzimuthElevationMask, const Interval& aRange); + + /// @brief Checks if the given AER satisfies the criterion. + /// + /// @param anAer The AER to check. + /// @return True if the criterion is satisfied. + bool isSatisfied(const AER& anAer) const; + + /// @brief Checks if the given azimuth, elevation, and range values satisfy the criterion. + /// + /// @param anAzimuthRadians The azimuth in radians. + /// @param anElevationRadians The elevation in radians. + /// @param aRangeMeters The range in meters. + /// @return True if the criterion is satisfied. + bool isSatisfied(const Real& anAzimuthRadians, const Real& anElevationRadians, const Real& aRangeMeters) const; + + /// @brief Equality operator. + /// + /// @param anAerMask Another AERMask. + /// @return True if equal. + bool operator==(const AERMask& anAerMask) const; + + /// @brief Inequality operator. + /// + /// @param anAerMask Another AERMask. + /// @return True if not equal. + bool operator!=(const AERMask& anAerMask) const; + }; + + /// @brief Represents a line-of-sight visibility criterion. + struct LineOfSight + { + mutable Environment environment; /// The environment for line-of-sight calculations. + + /// @brief Constructs a LineOfSight criterion. + /// + /// @param anEnvironment The environment to consider. + LineOfSight(const Environment& anEnvironment); + + /// @brief Checks if the line-of-sight criterion is satisfied between two positions at a given instant. + /// + /// @param anInstant The time at which to perform the check. + /// @param aFromPositionCoordinates The position coordinates (in meters) of the observer. + /// @param aToPositionCoordinates The position coordinates (in meters) of the target. + /// @return True if there is a clear line of sight. + bool isSatisfied( + const Instant& anInstant, const Vector3d& aFromPositionCoordinates, const Vector3d& aToPositionCoordinates + ) const; + + /// @brief Equality operator. + /// + /// @param aLineOfSight Another LineOfSight. + /// @return True if equal. + bool operator==(const LineOfSight& aLineOfSight) const; + + /// @brief Inequality operator. + /// + /// @param aLineOfSight Another LineOfSight. + /// @return True if not equal. + bool operator!=(const LineOfSight& aLineOfSight) const; + }; + + /// @brief Represents an elevation interval visibility criterion. + struct ElevationInterval + { + Interval elevation; /// Elevation interval in radians. + + /// @brief Constructs an ElevationInterval. + /// + /// @param anElevationInterval The elevation interval. + ElevationInterval(const Interval& anElevationInterval); + + /// @brief Checks if the given elevation angle satisfies the criterion. + /// + /// @param anElevation The elevation in radians. + /// @return True if the criterion is satisfied. + bool isSatisfied(const Real& anElevation) const; + + /// @brief Equality operator. + /// + /// @param anElevationInterval Another ElevationInterval. + /// @return True if equal. + bool operator==(const ElevationInterval& anElevationInterval) const; + + /// @brief Inequality operator. + /// + /// @param anElevationInterval Another ElevationInterval. + /// @return True if not equal. + bool operator!=(const ElevationInterval& anElevationInterval) const; + }; + + /// @brief Equality operator. + /// + /// @param aVisibilityCriterion Another VisibilityCriterion. + /// @return True if equal. + bool operator==(const VisibilityCriterion& aVisibilityCriterion) const; + + /// @brief Inequality operator. + /// + /// @param aVisibilityCriterion Another VisibilityCriterion. + /// @return True if not equal. + bool operator!=(const VisibilityCriterion& aVisibilityCriterion) const; + + /// @brief Creates a visibility criterion from azimuth, elevation, and range intervals. + /// + /// @param anAzimuthInterval Azimuth interval in radians. + /// @param anElevationInterval Elevation interval in radians. + /// @param aRangeInterval Range interval in meters. + /// @return The visibility criterion instance. + static VisibilityCriterion FromAERInterval( + const Interval& anAzimuthInterval, + const Interval& anElevationInterval, + const Interval& aRangeInterval + ); + + /// @brief Creates a visibility criterion from an azimuth-elevation mask and range interval. + /// + /// @param anAzimuthElevationMask A map of azimuth angles to elevation angles in radians. + /// @param aRangeInterval Range interval in meters. + /// @return The visibility criterion instance. + static VisibilityCriterion FromAERMask( + const Map& anAzimuthElevationMask, const Interval& aRangeInterval + ); + + /// @brief Creates a visibility criterion based on line-of-sight considerations. + /// + /// @param anEnvironment The environment to consider for line-of-sight calculations. + /// @return The visibility criterion instance. + static VisibilityCriterion FromLineOfSight(const Environment& anEnvironment); + + /// @brief Creates a visibility criterion from an elevation interval. + /// + /// @param anElevationInterval The elevation interval in radians. + /// @return The visibility criterion instance. + static VisibilityCriterion FromElevationInterval(const Interval& anElevationInterval); + + /// @brief Checks if the visibility criterion is of type T. + /// + /// @tparam T The type to check. + /// @return True if the criterion is of type T. + template + bool is() const + { + return std::holds_alternative(visibilityCriterion_); + } + + /// @brief Casts the visibility criterion to type T if possible. + /// + /// @tparam T The type to cast to. + /// @return An optional containing the criterion cast to type T, or std::nullopt if the criterion is not of type T. + template + std::optional as() const + { + if (const auto* constraintPtr = std::get_if(&visibilityCriterion_)) + { + return *constraintPtr; + } + + return std::nullopt; + } + + private: + std::variant visibilityCriterion_; + + /// @brief Constructs a VisibilityCriterion from a specific type. + /// + /// @tparam T The type of the visibility criterion. + /// @param aVisibilityCriterion The visibility criterion. + template + explicit VisibilityCriterion(const T& aVisibilityCriterion) + : visibilityCriterion_(aVisibilityCriterion) + { + } +}; + +} // namespace access +} // namespace astrodynamics +} // namespace ostk + +#endif diff --git a/src/OpenSpaceToolkit/Astrodynamics/Access/Generator.cpp b/src/OpenSpaceToolkit/Astrodynamics/Access/Generator.cpp index 44815275c..4b5eb1c3b 100644 --- a/src/OpenSpaceToolkit/Astrodynamics/Access/Generator.cpp +++ b/src/OpenSpaceToolkit/Astrodynamics/Access/Generator.cpp @@ -49,9 +49,9 @@ AccessTarget::Type AccessTarget::getType() const return type_; } -VisibilityCriteria AccessTarget::getVisibilityCriteria() const +VisibilityCriterion AccessTarget::getVisibilityCriterion() const { - return visibilityCriteria_; + return visibilityCriterion_; } Trajectory AccessTarget::getTrajectory() const @@ -94,33 +94,45 @@ Matrix3d AccessTarget::computeR_SEZ_ECEF(const Shared& aCelesti } AccessTarget AccessTarget::FromLLA( - const VisibilityCriteria& aVisibilityCriteria, const LLA& anLLA, const Shared& aCelestialSPtr + const VisibilityCriterion& aVisibilityCriterion, const LLA& anLLA, const Shared& aCelestialSPtr ) { return AccessTarget( AccessTarget::Type::Fixed, - aVisibilityCriteria, + aVisibilityCriterion, Trajectory::Position(Position::Meters( anLLA.toCartesian(aCelestialSPtr->getEquatorialRadius(), aCelestialSPtr->getFlattening()), Frame::ITRF() )) ); } -AccessTarget AccessTarget::FromPosition(const VisibilityCriteria& aVisibilityCriteria, const Position& aPosition) +AccessTarget AccessTarget::FromPosition(const VisibilityCriterion& aVisibilityCriterion, const Position& aPosition) { - return AccessTarget(AccessTarget::Type::Fixed, aVisibilityCriteria, Trajectory::Position(aPosition)); + if (!aPosition.isDefined()) + { + throw ostk::core::error::runtime::Undefined("Position"); + } + + return AccessTarget(AccessTarget::Type::Fixed, aVisibilityCriterion, Trajectory::Position(aPosition)); } -AccessTarget AccessTarget::FromTrajectory(const VisibilityCriteria& aVisibilityCriteria, const Trajectory& aTrajectory) +AccessTarget AccessTarget::FromTrajectory( + const VisibilityCriterion& aVisibilityCriterion, const Trajectory& aTrajectory +) { - return AccessTarget(AccessTarget::Type::Trajectory, aVisibilityCriteria, aTrajectory); + if (!aTrajectory.isDefined()) + { + throw ostk::core::error::runtime::Undefined("Trajectory"); + } + + return AccessTarget(AccessTarget::Type::Trajectory, aVisibilityCriterion, aTrajectory); } AccessTarget::AccessTarget( - const AccessTarget::Type& aType, const VisibilityCriteria& aVisibilityCriteria, const Trajectory& aTrajectory + const AccessTarget::Type& aType, const VisibilityCriterion& aVisibilityCriterion, const Trajectory& aTrajectory ) : type_(aType), - visibilityCriteria_(aVisibilityCriteria), + visibilityCriterion_(aVisibilityCriterion), trajectory_(aTrajectory) { if (!aTrajectory.isDefined()) @@ -210,9 +222,9 @@ std::function Generator::getConditionFunction( throw ostk::core::error::runtime::Undefined("Generator"); } - const VisibilityCriteria visibilityCriteria = anAccessTarget.getVisibilityCriteria(); + const VisibilityCriterion visibilityCriterion = anAccessTarget.getVisibilityCriterion(); - return [visibilityCriteria, aFromTrajectory, &aToTrajectory, this](const Instant& anInstant) mutable -> bool + return [visibilityCriterion, aFromTrajectory, &aToTrajectory, this](const Instant& anInstant) mutable -> bool { const State fromState = aFromTrajectory.getStateAt(anInstant); const State toState = aToTrajectory.getStateAt(anInstant); @@ -228,9 +240,9 @@ std::function Generator::getConditionFunction( const Position fromPosition_ITRF = fromPosition.inFrame(Frame::ITRF(), anInstant); const Position toPosition_ITRF = toPosition.inFrame(Frame::ITRF(), anInstant); - if (visibilityCriteria.is()) + if (visibilityCriterion.is()) { - return visibilityCriteria.as().value().isSatisfied( + return visibilityCriterion.as().value().isSatisfied( anInstant, fromPosition_ITRF.accessCoordinates(), toPosition_ITRF.accessCoordinates() ); } @@ -239,14 +251,14 @@ std::function Generator::getConditionFunction( anInstant, fromPosition, toPosition, this->environment_.accessCelestialObjectWithName("Earth") ); - if (visibilityCriteria.is()) + if (visibilityCriterion.is()) { - return visibilityCriteria.as().value().isSatisfied(aer); + return visibilityCriterion.as().value().isSatisfied(aer); } - if (visibilityCriteria.is()) + if (visibilityCriterion.is()) { - return visibilityCriteria.as().value().isSatisfied(aer); + return visibilityCriterion.as().value().isSatisfied(aer); } return false; @@ -442,7 +454,7 @@ Array> Generator::computeAccessesForFixedTargets( someAccessTargets.end(), [](const auto& accessTarget) { - return accessTarget.getVisibilityCriteria().template is(); + return accessTarget.getVisibilityCriterion().template is(); } ); const bool allAccessTargetsHaveAERIntervals = std::all_of( @@ -450,7 +462,7 @@ Array> Generator::computeAccessesForFixedTargets( someAccessTargets.end(), [](const auto& accessTarget) { - return accessTarget.getVisibilityCriteria().template is(); + return accessTarget.getVisibilityCriterion().template is(); } ); const bool allAccessTargetsHaveLineOfSight = std::all_of( @@ -458,7 +470,7 @@ Array> Generator::computeAccessesForFixedTargets( someAccessTargets.end(), [](const auto& accessTarget) { - return accessTarget.getVisibilityCriteria().template is(); + return accessTarget.getVisibilityCriterion().template is(); } ); const bool allAccessTargetsHaveElevationIntervals = std::all_of( @@ -466,7 +478,7 @@ Array> Generator::computeAccessesForFixedTargets( someAccessTargets.end(), [](const auto& accessTarget) { - return accessTarget.getVisibilityCriteria().template is(); + return accessTarget.getVisibilityCriterion().template is(); } ); @@ -514,7 +526,7 @@ Array> Generator::computeAccessesForFixedTargets( return (dx_Z.transpose().array() / dx.colwise().norm().array()).asin(); }; - std::function visibilityCriteriaFilter; + std::function visibilityCriterionFilter; if (allAccessTargetsHaveAERIntervals) { @@ -524,23 +536,23 @@ Array> Generator::computeAccessesForFixedTargets( for (Index i = 0; i < targetCount; ++i) { - const VisibilityCriteria::AERInterval visibilityCriteria = - someAccessTargets[i].getVisibilityCriteria().as().value(); + const VisibilityCriterion::AERInterval visibilityCriterion = + someAccessTargets[i].getVisibilityCriterion().as().value(); - aerLowerBounds(i, 0) = visibilityCriteria.azimuth.accessLowerBound(); - aerLowerBounds(i, 1) = visibilityCriteria.elevation.accessLowerBound(); - aerLowerBounds(i, 2) = visibilityCriteria.range.accessLowerBound(); + aerLowerBounds(i, 0) = visibilityCriterion.azimuth.accessLowerBound(); + aerLowerBounds(i, 1) = visibilityCriterion.elevation.accessLowerBound(); + aerLowerBounds(i, 2) = visibilityCriterion.range.accessLowerBound(); - aerUpperBounds(i, 0) = visibilityCriteria.azimuth.accessUpperBound(); - aerUpperBounds(i, 1) = visibilityCriteria.elevation.accessUpperBound(); - aerUpperBounds(i, 2) = visibilityCriteria.range.accessUpperBound(); + aerUpperBounds(i, 0) = visibilityCriterion.azimuth.accessUpperBound(); + aerUpperBounds(i, 1) = visibilityCriterion.elevation.accessUpperBound(); + aerUpperBounds(i, 2) = visibilityCriterion.range.accessUpperBound(); } - visibilityCriteriaFilter = [aerLowerBounds, aerUpperBounds, &computeAer]( - const MatrixXd& aFromPositionCoordinates_ITRF, - const Vector3d& aToPositionCoordinates_ITRF, - const Instant& anInstant - ) + visibilityCriterionFilter = [aerLowerBounds, aerUpperBounds, &computeAer]( + const MatrixXd& aFromPositionCoordinates_ITRF, + const Vector3d& aToPositionCoordinates_ITRF, + const Instant& anInstant + ) { (void)anInstant; (void)aFromPositionCoordinates_ITRF; @@ -557,11 +569,11 @@ Array> Generator::computeAccessesForFixedTargets( } else if (allAccessTargetsHaveMasks) { - visibilityCriteriaFilter = [&someAccessTargets, &computeAer]( - const MatrixXd& aFromPositionCoordinates_ITRF, - const Vector3d& aToPositionCoordinates_ITRF, - const Instant& anInstant - ) + visibilityCriterionFilter = [&someAccessTargets, &computeAer]( + const MatrixXd& aFromPositionCoordinates_ITRF, + const Vector3d& aToPositionCoordinates_ITRF, + const Instant& anInstant + ) { (void)anInstant; (void)aFromPositionCoordinates_ITRF; @@ -571,14 +583,14 @@ Array> Generator::computeAccessesForFixedTargets( ArrayXb mask(azimuths_rad.rows()); for (Eigen::Index i = 0; i < mask.rows(); ++i) { - const VisibilityCriteria::AERMask visibilityCriteria = - someAccessTargets[i].getVisibilityCriteria().as().value(); + const VisibilityCriterion::AERMask visibilityCriterion = + someAccessTargets[i].getVisibilityCriterion().as().value(); const double& azimuth_rad = azimuths_rad(i); const double& elevation_rad = elevations_rad(i); const double& range_m = ranges_m(i); - mask(i) = visibilityCriteria.isSatisfied(azimuth_rad, elevation_rad, range_m); + mask(i) = visibilityCriterion.isSatisfied(azimuth_rad, elevation_rad, range_m); } return mask; @@ -586,23 +598,24 @@ Array> Generator::computeAccessesForFixedTargets( } else if (allAccessTargetsHaveLineOfSight) { - visibilityCriteriaFilter = [&someAccessTargets]( - const MatrixXd& aFromPositionCoordinates_ITRF, - const Vector3d& aToPositionCoordinates_ITRF, - const Instant& anInstant - ) + visibilityCriterionFilter = [&someAccessTargets]( + const MatrixXd& aFromPositionCoordinates_ITRF, + const Vector3d& aToPositionCoordinates_ITRF, + const Instant& anInstant + ) { ArrayXb mask(aFromPositionCoordinates_ITRF.cols()); for (Eigen::Index i = 0; i < mask.rows(); ++i) { - const VisibilityCriteria::LineOfSight visibilityCriteria = - someAccessTargets[i].getVisibilityCriteria().as().value(); + const VisibilityCriterion::LineOfSight visibilityCriterion = + someAccessTargets[i].getVisibilityCriterion().as().value(); const Vector3d& fromPositionCoordinate_ITRF = aFromPositionCoordinates_ITRF.col(i); - mask(i) = - visibilityCriteria.isSatisfied(anInstant, fromPositionCoordinate_ITRF, aToPositionCoordinates_ITRF); + mask(i) = visibilityCriterion.isSatisfied( + anInstant, fromPositionCoordinate_ITRF, aToPositionCoordinates_ITRF + ); } return mask; @@ -616,18 +629,18 @@ Array> Generator::computeAccessesForFixedTargets( for (Index i = 0; i < targetCount; ++i) { - const VisibilityCriteria::ElevationInterval visibilityCriteria = - someAccessTargets[i].getVisibilityCriteria().as().value(); + const VisibilityCriterion::ElevationInterval visibilityCriterion = + someAccessTargets[i].getVisibilityCriterion().as().value(); - elevationLowerBounds(i) = visibilityCriteria.elevation.accessLowerBound(); - elevationUpperBounds(i) = visibilityCriteria.elevation.accessUpperBound(); + elevationLowerBounds(i) = visibilityCriterion.elevation.accessLowerBound(); + elevationUpperBounds(i) = visibilityCriterion.elevation.accessUpperBound(); } - visibilityCriteriaFilter = [elevationLowerBounds, elevationUpperBounds, &computeElevations]( - const MatrixXd& aFromPositionCoordinates_ITRF, - const Vector3d& aToPositionCoordinates_ITRF, - const Instant& anInstant - ) + visibilityCriterionFilter = [elevationLowerBounds, elevationUpperBounds, &computeElevations]( + const MatrixXd& aFromPositionCoordinates_ITRF, + const Vector3d& aToPositionCoordinates_ITRF, + const Instant& anInstant + ) { (void)anInstant; (void)aFromPositionCoordinates_ITRF; @@ -659,7 +672,7 @@ Array> Generator::computeAccessesForFixedTargets( // check if satellite is in access const auto inAccess = - visibilityCriteriaFilter(fromPositionCoordinates_ITRF, toPositionCoordinates_ITRF, instant); + visibilityCriterionFilter(fromPositionCoordinates_ITRF, toPositionCoordinates_ITRF, instant); inAccessPerTarget.row(index) = inAccess.cast().transpose(); } @@ -780,49 +793,49 @@ Array Generator::computePreciseCrossings( return {azimuth_rad, elevation_rad, range_m}; }; - if (anAccessTarget.getVisibilityCriteria().is()) + if (anAccessTarget.getVisibilityCriterion().is()) { - const VisibilityCriteria::AERInterval visibilityCriteria = - anAccessTarget.getVisibilityCriteria().as().value(); + const VisibilityCriterion::AERInterval visibilityCriterion = + anAccessTarget.getVisibilityCriterion().as().value(); - condition = [&computeAER, visibilityCriteria](const Instant& instant) -> bool + condition = [&computeAER, visibilityCriterion](const Instant& instant) -> bool { const auto [azimuth_rad, elevation_rad, range_m] = computeAER(instant); - return visibilityCriteria.isSatisfied(azimuth_rad, elevation_rad, range_m); + return visibilityCriterion.isSatisfied(azimuth_rad, elevation_rad, range_m); }; } - else if (anAccessTarget.getVisibilityCriteria().is()) + else if (anAccessTarget.getVisibilityCriterion().is()) { - const VisibilityCriteria::AERMask visibilityCriteria = - anAccessTarget.getVisibilityCriteria().as().value(); + const VisibilityCriterion::AERMask visibilityCriterion = + anAccessTarget.getVisibilityCriterion().as().value(); - condition = [&computeAER, visibilityCriteria](const Instant& instant) -> bool + condition = [&computeAER, visibilityCriterion](const Instant& instant) -> bool { const auto [azimuth_rad, elevation_rad, range_m] = computeAER(instant); - return visibilityCriteria.isSatisfied(azimuth_rad, elevation_rad, range_m); + return visibilityCriterion.isSatisfied(azimuth_rad, elevation_rad, range_m); }; } - else if (anAccessTarget.getVisibilityCriteria().is()) + else if (anAccessTarget.getVisibilityCriterion().is()) { - const VisibilityCriteria::LineOfSight visibilityCriteria = - anAccessTarget.getVisibilityCriteria().as().value(); + const VisibilityCriterion::LineOfSight visibilityCriterion = + anAccessTarget.getVisibilityCriterion().as().value(); - condition = [&fromPositionCoordinate_ITRF, &aToTrajectory, visibilityCriteria](const Instant& instant) -> bool + condition = [&fromPositionCoordinate_ITRF, &aToTrajectory, visibilityCriterion](const Instant& instant) -> bool { const Vector3d toPositionCoordinates_ITRF = aToTrajectory.getStateAt(instant).inFrame(Frame::ITRF()).getPosition().getCoordinates(); - return visibilityCriteria.isSatisfied(instant, fromPositionCoordinate_ITRF, toPositionCoordinates_ITRF); + return visibilityCriterion.isSatisfied(instant, fromPositionCoordinate_ITRF, toPositionCoordinates_ITRF); }; } - else if (anAccessTarget.getVisibilityCriteria().is()) + else if (anAccessTarget.getVisibilityCriterion().is()) { - const VisibilityCriteria::ElevationInterval visibilityCriteria = - anAccessTarget.getVisibilityCriteria().as().value(); + const VisibilityCriterion::ElevationInterval visibilityCriterion = + anAccessTarget.getVisibilityCriterion().as().value(); - condition = [&fromPositionCoordinate_ITRF, &SEZRotation, &aToTrajectory, visibilityCriteria]( + condition = [&fromPositionCoordinate_ITRF, &SEZRotation, &aToTrajectory, visibilityCriterion]( const Instant& instant ) -> bool { @@ -835,12 +848,12 @@ Array Generator::computePreciseCrossings( const double elevation_rad = std::asin(dx_Z / dx.norm()); - return visibilityCriteria.isSatisfied(elevation_rad); + return visibilityCriterion.isSatisfied(elevation_rad); }; } else { - throw ostk::core::error::RuntimeError("VisibilityCriteria type not supported."); + throw ostk::core::error::RuntimeError("VisibilityCriterion type not supported."); } Array preciseAccessIntervals = accessIntervals; diff --git a/src/OpenSpaceToolkit/Astrodynamics/Access/VisibilityCriteria.cpp b/src/OpenSpaceToolkit/Astrodynamics/Access/VisibilityCriteria.cpp deleted file mode 100644 index b51384130..000000000 --- a/src/OpenSpaceToolkit/Astrodynamics/Access/VisibilityCriteria.cpp +++ /dev/null @@ -1,38 +0,0 @@ -/// Apache License 2.0 - -#include - -namespace ostk -{ -namespace astrodynamics -{ -namespace access -{ - -VisibilityCriteria VisibilityCriteria::FromAERInterval( - const Interval& azimuth, const Interval& elevation, const Interval& range -) -{ - return VisibilityCriteria(AERInterval {azimuth, elevation, range}); -} - -VisibilityCriteria VisibilityCriteria::FromAERMask( - const Map& azimuthElevationMask, const Interval& range -) -{ - return VisibilityCriteria(AERMask {azimuthElevationMask, range}); -} - -VisibilityCriteria VisibilityCriteria::FromLineOfSight(const Environment& environment) -{ - return VisibilityCriteria(LineOfSight {environment}); -} - -VisibilityCriteria VisibilityCriteria::FromElevationInterval(const Interval& anElevationInterval) -{ - return VisibilityCriteria(ElevationInterval {anElevationInterval}); -} - -} // namespace access -} // namespace astrodynamics -} // namespace ostk diff --git a/src/OpenSpaceToolkit/Astrodynamics/Access/VisibilityCriterion.cpp b/src/OpenSpaceToolkit/Astrodynamics/Access/VisibilityCriterion.cpp new file mode 100644 index 000000000..e0dfd115e --- /dev/null +++ b/src/OpenSpaceToolkit/Astrodynamics/Access/VisibilityCriterion.cpp @@ -0,0 +1,48 @@ +/// Apache License 2.0 + +#include + +namespace ostk +{ +namespace astrodynamics +{ +namespace access +{ + +bool VisibilityCriterion::operator==(const VisibilityCriterion& aVisibilityCriterion) const +{ + return this->visibilityCriterion_ == aVisibilityCriterion.visibilityCriterion_; +} + +bool VisibilityCriterion::operator!=(const VisibilityCriterion& aVisibilityCriterion) const +{ + return !(*this == aVisibilityCriterion); +} + +VisibilityCriterion VisibilityCriterion::FromAERInterval( + const Interval& azimuth, const Interval& elevation, const Interval& range +) +{ + return VisibilityCriterion(AERInterval {azimuth, elevation, range}); +} + +VisibilityCriterion VisibilityCriterion::FromAERMask( + const Map& azimuthElevationMask, const Interval& range +) +{ + return VisibilityCriterion(AERMask {azimuthElevationMask, range}); +} + +VisibilityCriterion VisibilityCriterion::FromLineOfSight(const Environment& environment) +{ + return VisibilityCriterion(LineOfSight {environment}); +} + +VisibilityCriterion VisibilityCriterion::FromElevationInterval(const Interval& anElevationInterval) +{ + return VisibilityCriterion(ElevationInterval {anElevationInterval}); +} + +} // namespace access +} // namespace astrodynamics +} // namespace ostk diff --git a/src/OpenSpaceToolkit/Astrodynamics/Access/VisibilityCriteria/AERInterval.cpp b/src/OpenSpaceToolkit/Astrodynamics/Access/VisibilityCriterion/AERInterval.cpp similarity index 74% rename from src/OpenSpaceToolkit/Astrodynamics/Access/VisibilityCriteria/AERInterval.cpp rename to src/OpenSpaceToolkit/Astrodynamics/Access/VisibilityCriterion/AERInterval.cpp index 587f7fb05..aff94f3c8 100644 --- a/src/OpenSpaceToolkit/Astrodynamics/Access/VisibilityCriteria/AERInterval.cpp +++ b/src/OpenSpaceToolkit/Astrodynamics/Access/VisibilityCriterion/AERInterval.cpp @@ -1,6 +1,6 @@ /// Apache License 2.0 -#include +#include namespace ostk { @@ -9,7 +9,7 @@ namespace astrodynamics namespace access { -VisibilityCriteria::AERInterval::AERInterval( +VisibilityCriterion::AERInterval::AERInterval( const Interval& anAzimuthInterval, const Interval& anElevationInterval, const Interval& aRangeInterval @@ -60,19 +60,30 @@ VisibilityCriteria::AERInterval::AERInterval( ); } -bool VisibilityCriteria::AERInterval::isSatisfied(const AER& anAer) const +bool VisibilityCriterion::AERInterval::isSatisfied(const AER& anAer) const { return this->isSatisfied( anAer.getAzimuth().inRadians(), anAer.getElevation().inRadians(), anAer.getRange().inMeters() ); } -bool VisibilityCriteria::AERInterval::isSatisfied( +bool VisibilityCriterion::AERInterval::isSatisfied( const Real& anAzimuth_Radians, const Real& anElevation_Radians, const Real& aRange_Meters ) const { - return azimuth.contains(anAzimuth_Radians) && elevation.contains(anElevation_Radians) && - range.contains(aRange_Meters); + return this->azimuth.contains(anAzimuth_Radians) && this->elevation.contains(anElevation_Radians) && + this->range.contains(aRange_Meters); +} + +bool VisibilityCriterion::AERInterval::operator==(const AERInterval& anAerInterval) const +{ + return this->azimuth == anAerInterval.azimuth && this->elevation == anAerInterval.elevation && + this->range == anAerInterval.range; +} + +bool VisibilityCriterion::AERInterval::operator!=(const AERInterval& anAerInterval) const +{ + return !((*this) == anAerInterval); } } // namespace access diff --git a/src/OpenSpaceToolkit/Astrodynamics/Access/VisibilityCriteria/AERMask.cpp b/src/OpenSpaceToolkit/Astrodynamics/Access/VisibilityCriterion/AERMask.cpp similarity index 86% rename from src/OpenSpaceToolkit/Astrodynamics/Access/VisibilityCriteria/AERMask.cpp rename to src/OpenSpaceToolkit/Astrodynamics/Access/VisibilityCriterion/AERMask.cpp index 03be4c44e..3f2d6c5b3 100644 --- a/src/OpenSpaceToolkit/Astrodynamics/Access/VisibilityCriteria/AERMask.cpp +++ b/src/OpenSpaceToolkit/Astrodynamics/Access/VisibilityCriterion/AERMask.cpp @@ -1,6 +1,6 @@ /// Apache License 2.0 -#include +#include namespace ostk { @@ -11,7 +11,7 @@ namespace access using ostk::mathematics::object::Vector2d; -VisibilityCriteria::AERMask::AERMask( +VisibilityCriterion::AERMask::AERMask( const Map& anAzimuthElevationMask, const Interval& aRangeInterval ) : azimuthElevationMask(anAzimuthElevationMask), @@ -60,7 +60,7 @@ VisibilityCriteria::AERMask::AERMask( this->azimuthElevationMask = anAzimuthElevationMask_rad; } -bool VisibilityCriteria::AERMask::isSatisfied(const AER& anAer) const +bool VisibilityCriterion::AERMask::isSatisfied(const AER& anAer) const { return isSatisfied( anAer.getAzimuth().inRadians(0.0, Real::TwoPi()), @@ -69,7 +69,7 @@ bool VisibilityCriteria::AERMask::isSatisfied(const AER& anAer) const ); } -bool VisibilityCriteria::AERMask::isSatisfied( +bool VisibilityCriterion::AERMask::isSatisfied( const Real& anAzimuth_Radians, const Real& anElevation_Radians, const Real& aRange_Meters ) const { @@ -92,6 +92,16 @@ bool VisibilityCriteria::AERMask::isSatisfied( range.contains(aRange_Meters); } +bool VisibilityCriterion::AERMask::operator==(const AERMask& anAerMask) const +{ + return this->azimuthElevationMask == anAerMask.azimuthElevationMask && this->range == anAerMask.range; +} + +bool VisibilityCriterion::AERMask::operator!=(const AERMask& anAerMask) const +{ + return !((*this) == anAerMask); +} + } // namespace access } // namespace astrodynamics } // namespace ostk diff --git a/src/OpenSpaceToolkit/Astrodynamics/Access/VisibilityCriteria/ElevationInterval.cpp b/src/OpenSpaceToolkit/Astrodynamics/Access/VisibilityCriterion/ElevationInterval.cpp similarity index 54% rename from src/OpenSpaceToolkit/Astrodynamics/Access/VisibilityCriteria/ElevationInterval.cpp rename to src/OpenSpaceToolkit/Astrodynamics/Access/VisibilityCriterion/ElevationInterval.cpp index 0aa3e2466..8ec17c666 100644 --- a/src/OpenSpaceToolkit/Astrodynamics/Access/VisibilityCriteria/ElevationInterval.cpp +++ b/src/OpenSpaceToolkit/Astrodynamics/Access/VisibilityCriterion/ElevationInterval.cpp @@ -1,6 +1,6 @@ /// Apache License 2.0 -#include +#include namespace ostk { @@ -9,7 +9,7 @@ namespace astrodynamics namespace access { -VisibilityCriteria::ElevationInterval::ElevationInterval(const Interval& anElevationInterval) +VisibilityCriterion::ElevationInterval::ElevationInterval(const Interval& anElevationInterval) : elevation(anElevationInterval) { if (elevation.getLowerBound() < -90.0 || elevation.getUpperBound() > 90.0) @@ -21,16 +21,26 @@ VisibilityCriteria::ElevationInterval::ElevationInterval(const Interval& a ); } - elevation = Interval( + this->elevation = Interval( anElevationInterval.getLowerBound() * M_PI / 180.0, anElevationInterval.getUpperBound() * M_PI / 180.0, anElevationInterval.getType() ); } -bool VisibilityCriteria::ElevationInterval::isSatisfied(const Real& anElevation) const +bool VisibilityCriterion::ElevationInterval::isSatisfied(const Real& anElevation) const { - return elevation.contains(anElevation); + return this->elevation.contains(anElevation); +} + +bool VisibilityCriterion::ElevationInterval::operator==(const ElevationInterval& anElevationInterval) const +{ + return this->elevation == anElevationInterval.elevation; +} + +bool VisibilityCriterion::ElevationInterval::operator!=(const ElevationInterval& anElevationInterval) const +{ + return !(*this == anElevationInterval); } } // namespace access diff --git a/src/OpenSpaceToolkit/Astrodynamics/Access/VisibilityCriteria/LineOfSight.cpp b/src/OpenSpaceToolkit/Astrodynamics/Access/VisibilityCriterion/LineOfSight.cpp similarity index 74% rename from src/OpenSpaceToolkit/Astrodynamics/Access/VisibilityCriteria/LineOfSight.cpp rename to src/OpenSpaceToolkit/Astrodynamics/Access/VisibilityCriterion/LineOfSight.cpp index 0e92ac697..fff5d66e2 100644 --- a/src/OpenSpaceToolkit/Astrodynamics/Access/VisibilityCriteria/LineOfSight.cpp +++ b/src/OpenSpaceToolkit/Astrodynamics/Access/VisibilityCriterion/LineOfSight.cpp @@ -5,7 +5,7 @@ #include -#include +#include namespace ostk { @@ -22,12 +22,12 @@ using ostk::mathematics::geometry::d3::object::Segment; using ostk::physics::coordinate::Frame; using ostk::physics::environment::Object; -VisibilityCriteria::LineOfSight::LineOfSight(const Environment& anEnvironment) +VisibilityCriterion::LineOfSight::LineOfSight(const Environment& anEnvironment) : environment(anEnvironment) { } -bool VisibilityCriteria::LineOfSight::isSatisfied( +bool VisibilityCriterion::LineOfSight::isSatisfied( const Instant& anInstant, const Vector3d& aFromPositionCoordinates_ITRF, const Vector3d& aToPositionCoordinates_ITRF ) const { @@ -50,6 +50,16 @@ bool VisibilityCriteria::LineOfSight::isSatisfied( return !this->environment.intersects(fromToSegmentGeometry); } +bool VisibilityCriterion::LineOfSight::operator==(const VisibilityCriterion::LineOfSight& aLineOfSight) const +{ + return this->environment.accessObjects() == aLineOfSight.environment.accessObjects(); +} + +bool VisibilityCriterion::LineOfSight::operator!=(const VisibilityCriterion::LineOfSight& aLineOfSight) const +{ + return !(*this == aLineOfSight); +} + } // namespace access } // namespace astrodynamics } // namespace ostk diff --git a/test/OpenSpaceToolkit/Astrodynamics/Access/Generator.test.cpp b/test/OpenSpaceToolkit/Astrodynamics/Access/Generator.test.cpp index 457964da4..9b88ca412 100644 --- a/test/OpenSpaceToolkit/Astrodynamics/Access/Generator.test.cpp +++ b/test/OpenSpaceToolkit/Astrodynamics/Access/Generator.test.cpp @@ -57,7 +57,7 @@ using ostk::physics::unit::Length; using ostk::astrodynamics::Access; using ostk::astrodynamics::access::AccessTarget; using ostk::astrodynamics::access::Generator; -using ostk::astrodynamics::access::VisibilityCriteria; +using ostk::astrodynamics::access::VisibilityCriterion; using ostk::astrodynamics::Trajectory; using ostk::astrodynamics::trajectory::Orbit; using ostk::astrodynamics::trajectory::orbit::model::Kepler; @@ -76,6 +76,176 @@ class OpenSpaceToolkit_Astrodynamics_Access_Generator : public ::testing::Test Generator defaultGenerator_ = {this->defaultEnvironment_, this->defaultStep_, this->defaultTolerance_}; }; +class OpenSpaceToolkit_Astrodynamics_Access_AccessTarget : public ::testing::Test +{ + protected: + Environment defaultEnvironment_ = Environment::Default(); + const Shared defaultEarthSPtr_ = defaultEnvironment_.accessCelestialObjectWithName("Earth"); + const ostk::mathematics::object::Interval defaultAzimuthInterval_ = + ostk::mathematics::object::Interval::Closed(0.0, 360.0); + const ostk::mathematics::object::Interval defaultElevationInterval_ = + ostk::mathematics::object::Interval::Closed(0.0, 90.0); + const ostk::mathematics::object::Interval defaultRangeInterval_ = + ostk::mathematics::object::Interval::Closed(0.0, 1.0e10); + const LLA defaultLLA_ = LLA::Vector({0.0, 0.0, 0.0}); + + const VisibilityCriterion defaultVisibilityCriterion_ = + VisibilityCriterion::FromAERInterval(defaultAzimuthInterval_, defaultElevationInterval_, defaultRangeInterval_); + + const AccessTarget defaultAccessTarget_ = + AccessTarget::FromLLA(defaultVisibilityCriterion_, defaultLLA_, defaultEarthSPtr_); +}; + +TEST_F(OpenSpaceToolkit_Astrodynamics_Access_AccessTarget, Constructor) +{ + // Constructor + { + { + const VisibilityCriterion visibilityCriterion = VisibilityCriterion::FromAERInterval( + defaultAzimuthInterval_, defaultElevationInterval_, defaultRangeInterval_ + ); + + EXPECT_NO_THROW(AccessTarget::FromLLA(visibilityCriterion, defaultLLA_, defaultEarthSPtr_)); + } + + { + const VisibilityCriterion visibilityCriterion = VisibilityCriterion::FromAERInterval( + defaultAzimuthInterval_, defaultElevationInterval_, defaultRangeInterval_ + ); + { + const Position position = Position::Meters({0.0, 0.0, 0.0}, Frame::ITRF()); + EXPECT_NO_THROW(AccessTarget::FromPosition(visibilityCriterion, position)); + } + + { + const Position position = Position::Meters({0.0, 0.0, 0.0}, Frame::GCRF()); + EXPECT_THROW( + AccessTarget::FromPosition(visibilityCriterion, position), ostk::core::error::RuntimeError + ); + } + } + + // undefined + { + const Position position = Position::Meters({0.0, 0.0, 0.0}, Frame::ITRF()); + + EXPECT_THROW( + AccessTarget::FromPosition( + VisibilityCriterion::FromAERInterval( + ostk::mathematics::object::Interval::Undefined(), + defaultElevationInterval_, + defaultRangeInterval_ + ), + position + ), + ostk::core::error::runtime::Undefined + ); + EXPECT_THROW( + AccessTarget::FromPosition( + VisibilityCriterion::FromAERInterval( + defaultAzimuthInterval_, + ostk::mathematics::object::Interval::Undefined(), + defaultRangeInterval_ + ), + position + ), + ostk::core::error::runtime::Undefined + ); + EXPECT_THROW( + AccessTarget::FromPosition( + VisibilityCriterion::FromAERInterval( + defaultAzimuthInterval_, + defaultElevationInterval_, + ostk::mathematics::object::Interval::Undefined() + ), + position + ), + ostk::core::error::runtime::Undefined + ); + EXPECT_THROW( + AccessTarget::FromLLA( + VisibilityCriterion::FromAERInterval( + defaultAzimuthInterval_, defaultElevationInterval_, defaultRangeInterval_ + ), + LLA::Undefined(), + defaultEarthSPtr_ + ), + ostk::core::error::runtime::Undefined + ); + } + + // incorrect bounds + { + { + EXPECT_THROW( + AccessTarget::FromLLA( + VisibilityCriterion::FromAERInterval( + ostk::mathematics::object::Interval::Closed(-1.0, 350.0), + defaultElevationInterval_, + defaultRangeInterval_ + ), + defaultLLA_, + defaultEarthSPtr_ + ), + ostk::core::error::RuntimeError + ); + EXPECT_THROW( + AccessTarget::FromLLA( + VisibilityCriterion::FromAERInterval( + ostk::mathematics::object::Interval::Closed(0.0, 360.1), + defaultElevationInterval_, + defaultRangeInterval_ + ), + defaultLLA_, + defaultEarthSPtr_ + ), + ostk::core::error::RuntimeError + ); + } + { + EXPECT_THROW( + AccessTarget::FromLLA( + VisibilityCriterion::FromAERInterval( + defaultAzimuthInterval_, + ostk::mathematics::object::Interval::Closed(-91.0, 0.0), + defaultRangeInterval_ + ), + defaultLLA_, + defaultEarthSPtr_ + ), + ostk::core::error::RuntimeError + ); + EXPECT_THROW( + AccessTarget::FromLLA( + VisibilityCriterion::FromAERInterval( + defaultAzimuthInterval_, + ostk::mathematics::object::Interval::Closed(-45.0, 91.0), + defaultRangeInterval_ + ), + defaultLLA_, + defaultEarthSPtr_ + ), + ostk::core::error::RuntimeError + ); + } + { + EXPECT_THROW( + AccessTarget::FromLLA( + VisibilityCriterion::FromAERInterval( + defaultAzimuthInterval_, + defaultElevationInterval_, + ostk::mathematics::object::Interval::Closed(-1.0, 5.0) + ), + defaultLLA_, + defaultEarthSPtr_ + ), + ostk::core::error::RuntimeError + ); + } + } + } +} + TEST_F(OpenSpaceToolkit_Astrodynamics_Access_Generator, Constructor) { { @@ -108,6 +278,86 @@ TEST_F(OpenSpaceToolkit_Astrodynamics_Access_Generator, Constructor) } } +TEST_F(OpenSpaceToolkit_Astrodynamics_Access_AccessTarget, getType) +{ + EXPECT_EQ(defaultAccessTarget_.getType(), AccessTarget::Type::Fixed); +} + +TEST_F(OpenSpaceToolkit_Astrodynamics_Access_AccessTarget, getVisibilityCriterion) +{ + EXPECT_EQ(defaultAccessTarget_.getVisibilityCriterion(), defaultVisibilityCriterion_); +} + +TEST_F(OpenSpaceToolkit_Astrodynamics_Access_AccessTarget, getTrajectory) +{ + EXPECT_NO_THROW(defaultAccessTarget_.getTrajectory()); +} + +TEST_F(OpenSpaceToolkit_Astrodynamics_Access_AccessTarget, getPosition) +{ + { + EXPECT_VECTORS_ALMOST_EQUAL( + defaultAccessTarget_.getPosition().accessCoordinates(), + Position::Meters( + defaultLLA_.toCartesian(Earth::EGM2008.equatorialRadius_, Earth::EGM2008.flattening_), Frame::ITRF() + ) + .accessCoordinates(), + 1e-13 + ); + } + + { + const AccessTarget nonFixedAccessTarget = AccessTarget::FromTrajectory( + defaultVisibilityCriterion_, Trajectory::Position(defaultAccessTarget_.getPosition()) + ); + EXPECT_THROW(nonFixedAccessTarget.getPosition(), ostk::core::error::RuntimeError); + } +} + +TEST_F(OpenSpaceToolkit_Astrodynamics_Access_AccessTarget, getLLA) +{ + EXPECT_VECTORS_ALMOST_EQUAL( + defaultAccessTarget_.getLLA(defaultEarthSPtr_).toVector(), defaultLLA_.toVector(), 1e-15 + ); +} + +TEST_F(OpenSpaceToolkit_Astrodynamics_Access_AccessTarget, computeR_SEZ_ECEF) +{ + Matrix3d r_SEZ_ECEF; + r_SEZ_ECEF.row(0) = Vector3d {0.0, 0.0, -1.0}; + r_SEZ_ECEF.row(1) = Vector3d {0.0, 1.0, 0.0}; + r_SEZ_ECEF.row(2) = Vector3d {1.0, 0.0, 0.0}; + + EXPECT_MATRICES_ALMOST_EQUAL(defaultAccessTarget_.computeR_SEZ_ECEF(defaultEarthSPtr_), r_SEZ_ECEF, 1e-15); +} + +TEST_F(OpenSpaceToolkit_Astrodynamics_Access_AccessTarget, FromLLA) +{ + const AccessTarget accessTarget = + AccessTarget::FromLLA(defaultVisibilityCriterion_, defaultLLA_, defaultEarthSPtr_); + + EXPECT_EQ(accessTarget.getType(), AccessTarget::Type::Fixed); + EXPECT_EQ(accessTarget.getVisibilityCriterion(), defaultVisibilityCriterion_); +} + +TEST_F(OpenSpaceToolkit_Astrodynamics_Access_AccessTarget, FromPosition) +{ + const Position position = Position::Meters({0.0, 0.0, 0.0}, Frame::ITRF()); + const AccessTarget accessTarget = AccessTarget::FromPosition(defaultVisibilityCriterion_, position); + + EXPECT_EQ(accessTarget.getType(), AccessTarget::Type::Fixed); + EXPECT_EQ(accessTarget.getVisibilityCriterion(), defaultVisibilityCriterion_); +} + +TEST_F(OpenSpaceToolkit_Astrodynamics_Access_AccessTarget, FromTrajectory) +{ + const Trajectory trajectory = Trajectory::Position(Position::Meters({0.0, 0.0, 0.0}, Frame::ITRF())); + const AccessTarget accessTarget = AccessTarget::FromTrajectory(defaultVisibilityCriterion_, trajectory); + + EXPECT_EQ(accessTarget.getType(), AccessTarget::Type::Trajectory); + EXPECT_EQ(accessTarget.getVisibilityCriterion(), defaultVisibilityCriterion_); +} + TEST_F(OpenSpaceToolkit_Astrodynamics_Access_Generator, IsDefined) { { @@ -238,12 +488,12 @@ TEST_F(OpenSpaceToolkit_Astrodynamics_Access_Generator, GetConditionFunction) const Orbit fromOrbit = generateFirstOrbit(); const Orbit toOrbit = generateSecondOrbit(); - const VisibilityCriteria visibilityCriteria = VisibilityCriteria::FromAERInterval( + const VisibilityCriterion visibilityCriterion = VisibilityCriterion::FromAERInterval( ostk::mathematics::object::Interval::Closed(0.0, 360.0), ostk::mathematics::object::Interval::Closed(-90.0, 90.0), ostk::mathematics::object::Interval::Closed(0.0, 1.0e10) ); - const AccessTarget accessTarget = AccessTarget::FromTrajectory(visibilityCriteria, fromOrbit); + const AccessTarget accessTarget = AccessTarget::FromTrajectory(visibilityCriterion, fromOrbit); { const auto conditionFunction = defaultGenerator_.getConditionFunction(accessTarget, toOrbit); @@ -319,9 +569,9 @@ TEST_F(OpenSpaceToolkit_Astrodynamics_Access_Generator, ComputeAccesses_1) const Orbit fromOrbit = generateFirstOrbit(); const Orbit toOrbit = generateSecondOrbit(); - const VisibilityCriteria visibilityCriteria = VisibilityCriteria::FromLineOfSight(defaultEnvironment_); + const VisibilityCriterion visibilityCriterion = VisibilityCriterion::FromLineOfSight(defaultEnvironment_); - const AccessTarget accessTarget = AccessTarget::FromTrajectory(visibilityCriteria, fromOrbit); + const AccessTarget accessTarget = AccessTarget::FromTrajectory(visibilityCriterion, fromOrbit); const Array accesses = defaultGenerator_.computeAccesses(interval, accessTarget, toOrbit); @@ -402,10 +652,10 @@ TEST_F(OpenSpaceToolkit_Astrodynamics_Access_Generator, ComputeAccesses_2) return orbit; }; - const VisibilityCriteria visibilityCriteria = VisibilityCriteria::FromLineOfSight(defaultEnvironment_); + const VisibilityCriterion visibilityCriterion = VisibilityCriterion::FromLineOfSight(defaultEnvironment_); const AccessTarget groundStationTarget = - AccessTarget::FromLLA(visibilityCriteria, groundStationLla, defaultEarthSPtr_); + AccessTarget::FromLLA(visibilityCriterion, groundStationLla, defaultEarthSPtr_); const Orbit satelliteOrbit = generateSatelliteOrbit(); @@ -476,10 +726,10 @@ TEST_F(OpenSpaceToolkit_Astrodynamics_Access_Generator, ComputeAccesses_3) return orbit; }; - const VisibilityCriteria visibilityCriteria = VisibilityCriteria::FromLineOfSight(defaultEnvironment_); + const VisibilityCriterion visibilityCriterion = VisibilityCriterion::FromLineOfSight(defaultEnvironment_); const AccessTarget groundStationTarget = - AccessTarget::FromLLA(visibilityCriteria, groundStationLla, defaultEarthSPtr_); + AccessTarget::FromLLA(visibilityCriterion, groundStationLla, defaultEarthSPtr_); const Orbit satelliteOrbit = generateSatelliteOrbit(); const Array accesses = defaultGenerator_.computeAccesses(interval, groundStationTarget, satelliteOrbit); @@ -561,9 +811,9 @@ TEST_F(OpenSpaceToolkit_Astrodynamics_Access_Generator, ComputeAccesses_4) Position::Meters({0.0, 0.0, 0.0}, Frame::GCRF()), Velocity::MetersPerSecond({1.0, 0.0, 0.0}, Frame::GCRF()) ); - const VisibilityCriteria visibilityCriteria = VisibilityCriteria::FromLineOfSight(defaultEnvironment_); + const VisibilityCriterion visibilityCriterion = VisibilityCriterion::FromLineOfSight(defaultEnvironment_); - const AccessTarget accessTarget = AccessTarget::FromTrajectory(visibilityCriteria, firstTrajectory); + const AccessTarget accessTarget = AccessTarget::FromTrajectory(visibilityCriterion, firstTrajectory); const Trajectory secondTrajectory = generateTrajectory( Position::Meters({0.0, 0.0, 0.0}, Frame::GCRF()), Velocity::MetersPerSecond({0.0, 0.0, 1.0}, Frame::GCRF()) @@ -617,176 +867,6 @@ TEST_F(OpenSpaceToolkit_Astrodynamics_Access_Generator, ComputeAccesses_4) } } -TEST_F(OpenSpaceToolkit_Astrodynamics_Access_Generator, AccessTarget) -{ - const ostk::mathematics::object::Interval azimuthInterval = - ostk::mathematics::object::Interval::Closed(0.0, 360.0); - const ostk::mathematics::object::Interval elevationInterval = - ostk::mathematics::object::Interval::Closed(0.0, 90.0); - const ostk::mathematics::object::Interval rangeInterval = - ostk::mathematics::object::Interval::Closed(0.0, 1.0e10); - const LLA lla = LLA::Vector({0.0, 0.0, 0.0}); - - // Constructor - { - { - const VisibilityCriteria visibilityCriteria = - VisibilityCriteria::FromAERInterval(azimuthInterval, elevationInterval, rangeInterval); - - EXPECT_NO_THROW(AccessTarget::FromLLA(visibilityCriteria, lla, defaultEarthSPtr_)); - } - - { - const VisibilityCriteria visibilityCriteria = - VisibilityCriteria::FromAERInterval(azimuthInterval, elevationInterval, rangeInterval); - { - const Position position = Position::Meters({0.0, 0.0, 0.0}, Frame::ITRF()); - EXPECT_NO_THROW(AccessTarget::FromPosition(visibilityCriteria, position)); - } - - { - const Position position = Position::Meters({0.0, 0.0, 0.0}, Frame::GCRF()); - EXPECT_THROW(AccessTarget::FromPosition(visibilityCriteria, position), ostk::core::error::RuntimeError); - } - } - - // undefined - { - const Position position = Position::Meters({0.0, 0.0, 0.0}, Frame::ITRF()); - - EXPECT_THROW( - AccessTarget::FromPosition( - VisibilityCriteria::FromAERInterval( - ostk::mathematics::object::Interval::Undefined(), elevationInterval, rangeInterval - ), - position - ), - ostk::core::error::runtime::Undefined - ); - EXPECT_THROW( - AccessTarget::FromPosition( - VisibilityCriteria::FromAERInterval( - azimuthInterval, ostk::mathematics::object::Interval::Undefined(), rangeInterval - ), - position - ), - ostk::core::error::runtime::Undefined - ); - EXPECT_THROW( - AccessTarget::FromPosition( - VisibilityCriteria::FromAERInterval( - azimuthInterval, elevationInterval, ostk::mathematics::object::Interval::Undefined() - ), - position - ), - ostk::core::error::runtime::Undefined - ); - EXPECT_THROW( - AccessTarget::FromLLA( - VisibilityCriteria::FromAERInterval(azimuthInterval, elevationInterval, rangeInterval), - LLA::Undefined(), - defaultEarthSPtr_ - ), - ostk::core::error::runtime::Undefined - ); - } - - // incorrect bounds - { - { - EXPECT_THROW( - AccessTarget::FromLLA( - VisibilityCriteria::FromAERInterval( - ostk::mathematics::object::Interval::Closed(-1.0, 350.0), - elevationInterval, - rangeInterval - ), - lla, - defaultEarthSPtr_ - ), - ostk::core::error::RuntimeError - ); - EXPECT_THROW( - AccessTarget::FromLLA( - VisibilityCriteria::FromAERInterval( - ostk::mathematics::object::Interval::Closed(0.0, 360.1), - elevationInterval, - rangeInterval - ), - lla, - defaultEarthSPtr_ - ), - ostk::core::error::RuntimeError - ); - } - { - EXPECT_THROW( - AccessTarget::FromLLA( - VisibilityCriteria::FromAERInterval( - azimuthInterval, - ostk::mathematics::object::Interval::Closed(-91.0, 0.0), - rangeInterval - ), - lla, - defaultEarthSPtr_ - ), - ostk::core::error::RuntimeError - ); - EXPECT_THROW( - AccessTarget::FromLLA( - VisibilityCriteria::FromAERInterval( - azimuthInterval, - ostk::mathematics::object::Interval::Closed(-45.0, 91.0), - rangeInterval - ), - lla, - defaultEarthSPtr_ - ), - ostk::core::error::RuntimeError - ); - } - { - EXPECT_THROW( - AccessTarget::FromLLA( - VisibilityCriteria::FromAERInterval( - azimuthInterval, - elevationInterval, - ostk::mathematics::object::Interval::Closed(-1.0, 5.0) - ), - lla, - defaultEarthSPtr_ - ), - ostk::core::error::RuntimeError - ); - } - } - } - - // Getters - { - const VisibilityCriteria visibilityCriteria = - VisibilityCriteria::FromAERInterval(azimuthInterval, elevationInterval, rangeInterval); - const AccessTarget accessTarget = AccessTarget::FromLLA(visibilityCriteria, lla, defaultEarthSPtr_); - EXPECT_VECTORS_ALMOST_EQUAL(accessTarget.getLLA(defaultEarthSPtr_).toVector(), lla.toVector(), 1e-15); - EXPECT_VECTORS_ALMOST_EQUAL( - accessTarget.getPosition().accessCoordinates(), - Position::Meters( - lla.toCartesian(Earth::EGM2008.equatorialRadius_, Earth::EGM2008.flattening_), Frame::ITRF() - ) - .accessCoordinates(), - 1e-13 - ); - EXPECT_NO_THROW(accessTarget.getTrajectory()); - - Matrix3d r_SEZ_ECEF; - r_SEZ_ECEF.row(0) = Vector3d {0.0, 0.0, -1.0}; - r_SEZ_ECEF.row(1) = Vector3d {0.0, 1.0, 0.0}; - r_SEZ_ECEF.row(2) = Vector3d {1.0, 0.0, 0.0}; - - EXPECT_MATRICES_ALMOST_EQUAL(accessTarget.computeR_SEZ_ECEF(defaultEarthSPtr_), r_SEZ_ECEF, 1e-15); - } -} - TEST_F(OpenSpaceToolkit_Astrodynamics_Access_Generator, ComputeAccesses) { { @@ -825,13 +905,13 @@ TEST_F(OpenSpaceToolkit_Astrodynamics_Access_Generator, ComputeAccesses) const ostk::mathematics::object::Interval rangeInterval = ostk::mathematics::object::Interval::Closed(0.0, 1.0e10); - const VisibilityCriteria visibilityCriteria = - VisibilityCriteria::FromAERInterval(azimuthInterval, elevationInterval, rangeInterval); + const VisibilityCriterion visibilityCriterion = + VisibilityCriterion::FromAERInterval(azimuthInterval, elevationInterval, rangeInterval); Array accessTargets = LLAs.map( - [&visibilityCriteria, this](const LLA& lla) -> AccessTarget + [&visibilityCriterion, this](const LLA& lla) -> AccessTarget { - return AccessTarget::FromLLA(visibilityCriteria, lla, defaultEarthSPtr_); + return AccessTarget::FromLLA(visibilityCriterion, lla, defaultEarthSPtr_); } ); @@ -945,8 +1025,8 @@ TEST_F(OpenSpaceToolkit_Astrodynamics_Access_Generator, AerRanges_1) const ostk::mathematics::object::Interval rangeRange = ostk::mathematics::object::Interval::Closed(0.0, 10000e3); - const VisibilityCriteria visibilityCriteria = - VisibilityCriteria::FromAERInterval(azimuthRange, elevationRange, rangeRange); + const VisibilityCriterion visibilityCriterion = + VisibilityCriterion::FromAERInterval(azimuthRange, elevationRange, rangeRange); const Instant startInstant = Instant::DateTime(DateTime(2018, 1, 1, 0, 0, 0), Scale::UTC); const Instant endInstant = Instant::DateTime(DateTime(2018, 1, 10, 0, 0, 0), Scale::UTC); @@ -982,7 +1062,7 @@ TEST_F(OpenSpaceToolkit_Astrodynamics_Access_Generator, AerRanges_1) }; const AccessTarget groundStationTarget = - AccessTarget::FromLLA(visibilityCriteria, groundStationLla, defaultEarthSPtr_); + AccessTarget::FromLLA(visibilityCriterion, groundStationLla, defaultEarthSPtr_); const Orbit satelliteOrbit = generateSatelliteOrbit(); const Array accesses = defaultGenerator_.computeAccesses(interval, groundStationTarget, satelliteOrbit); @@ -1041,7 +1121,8 @@ TEST_F(OpenSpaceToolkit_Astrodynamics_Access_Generator, AerMask_1) const ostk::mathematics::object::Interval rangeRange = ostk::mathematics::object::Interval::Closed(0.0, 10000e3); - const VisibilityCriteria visibilityCriteria = VisibilityCriteria::FromAERMask(azimuthElevationMask, rangeRange); + const VisibilityCriterion visibilityCriterion = + VisibilityCriterion::FromAERMask(azimuthElevationMask, rangeRange); const Instant startInstant = Instant::DateTime(DateTime(2018, 1, 1, 0, 0, 0), Scale::UTC); const Instant endInstant = Instant::DateTime(DateTime(2018, 1, 5, 0, 0, 0), Scale::UTC); @@ -1077,7 +1158,7 @@ TEST_F(OpenSpaceToolkit_Astrodynamics_Access_Generator, AerMask_1) }; const AccessTarget groundStationTarget = - AccessTarget::FromLLA(visibilityCriteria, groundStationLla, defaultEarthSPtr_); + AccessTarget::FromLLA(visibilityCriterion, groundStationLla, defaultEarthSPtr_); const Orbit satelliteOrbit = generateSatelliteOrbit(); diff --git a/test/OpenSpaceToolkit/Astrodynamics/Access/VisibilityCriterion.test.cpp b/test/OpenSpaceToolkit/Astrodynamics/Access/VisibilityCriterion.test.cpp new file mode 100644 index 000000000..d059c044f --- /dev/null +++ b/test/OpenSpaceToolkit/Astrodynamics/Access/VisibilityCriterion.test.cpp @@ -0,0 +1,256 @@ +/// Apache License 2.0 + +#include + +#include + +#include + +#include + +using ostk::core::container::Map; +using ostk::core::type::Real; +using ostk::core::type::Shared; + +using ostk::physics::coordinate::spherical::AER; +using ostk::physics::Environment; +using ostk::physics::environment::object::Celestial; +using ostk::physics::time::Instant; +using ostk::physics::unit::Angle; +using ostk::physics::unit::Length; + +using ostk::mathematics::object::Interval; +using ostk::mathematics::object::Vector3d; + +using ostk::astrodynamics::access::VisibilityCriterion; + +const double deg2rad = M_PI / 180.0; + +class OpenSpaceToolkit_Astrodynamics_Access_VisibilityCriterion : public ::testing::Test +{ + protected: + Environment defaultEnvironment_ = Environment::Default(); + const Shared defaultEarthSPtr_ = defaultEnvironment_.accessCelestialObjectWithName("Earth"); +}; + +TEST_F(OpenSpaceToolkit_Astrodynamics_Access_VisibilityCriterion, Constructor) +{ + // Test FromAERInterval + { + const Interval azimuthInterval = Interval::Closed(0.0, 360.0); + const Interval elevationInterval = Interval::Closed(0.0, 90.0); + const Interval rangeInterval = Interval::Closed(0.0, 1e6); + + EXPECT_NO_THROW(VisibilityCriterion visibilityCriterion = + VisibilityCriterion::FromAERInterval(azimuthInterval, elevationInterval, rangeInterval);); + } + + // Test FromAERMask + { + const Map azimuthElevationMask = {{0.0, 10.0}, {90.0, 15.0}, {180.0, 20.0}}; + const Interval rangeInterval = Interval::Closed(0.0, 1e6); + + EXPECT_NO_THROW(VisibilityCriterion visibilityCriterion = + VisibilityCriterion::FromAERMask(azimuthElevationMask, rangeInterval);); + } + + // Test FromLineOfSight + { + EXPECT_NO_THROW(VisibilityCriterion visibilityCriterion = + VisibilityCriterion::FromLineOfSight(defaultEnvironment_);); + } + + // Test FromElevationInterval + { + const Interval elevationInterval = Interval::Closed(0.0, 90.0); + + EXPECT_NO_THROW(VisibilityCriterion visibilityCriterion = + VisibilityCriterion::FromElevationInterval(elevationInterval);); + } +} + +TEST_F(OpenSpaceToolkit_Astrodynamics_Access_VisibilityCriterion, Is) +{ + // AERInterval + { + const Interval azimuthInterval = Interval::Closed(0.0, 360.0); + const Interval elevationInterval = Interval::Closed(0.0, 90.0); + const Interval rangeInterval = Interval::Closed(0.0, 1e6); + + const VisibilityCriterion visibilityCriterion = + VisibilityCriterion::FromAERInterval(azimuthInterval, elevationInterval, rangeInterval); + + EXPECT_TRUE(visibilityCriterion.is()); + } + + // AERMask + { + const Map azimuthElevationMask = {{0.0, 10.0}, {90.0, 15.0}, {180.0, 20.0}}; + const Interval rangeInterval = Interval::Closed(0.0, 1e6); + + const VisibilityCriterion visibilityCriterion = + VisibilityCriterion::FromAERMask(azimuthElevationMask, rangeInterval); + + EXPECT_TRUE(visibilityCriterion.is()); + } + + // LineOfSight + { + const VisibilityCriterion visibilityCriterion = VisibilityCriterion::FromLineOfSight(defaultEnvironment_); + + EXPECT_TRUE(visibilityCriterion.is()); + } + + // ElevationInterval + { + const Interval elevationInterval = Interval::Closed(0.0, 90.0); + + const VisibilityCriterion visibilityCriterion = VisibilityCriterion::FromElevationInterval(elevationInterval); + + EXPECT_TRUE(visibilityCriterion.is()); + } +} + +TEST_F(OpenSpaceToolkit_Astrodynamics_Access_VisibilityCriterion, As) +{ + // AERInterval + { + const Interval azimuthInterval = Interval::Closed(0.0, 360.0); + const Interval elevationInterval = Interval::Closed(0.0, 90.0); + const Interval rangeInterval = Interval::Closed(0.0, 1e6); + + const VisibilityCriterion visibilityCriterion = + VisibilityCriterion::FromAERInterval(azimuthInterval, elevationInterval, rangeInterval); + + const auto aerInterval = visibilityCriterion.as(); + ASSERT_TRUE(aerInterval.has_value()); + EXPECT_EQ(aerInterval.value().azimuth.getLowerBound(), azimuthInterval.getLowerBound() * deg2rad); + EXPECT_EQ(aerInterval.value().azimuth.getUpperBound(), azimuthInterval.getUpperBound() * deg2rad); + EXPECT_EQ(aerInterval.value().elevation.getLowerBound(), elevationInterval.getLowerBound() * deg2rad); + EXPECT_EQ(aerInterval.value().elevation.getUpperBound(), elevationInterval.getUpperBound() * deg2rad); + EXPECT_EQ(aerInterval.value().range, rangeInterval); + } + + // AERMask + { + const Map azimuthElevationMask = {{0.0, 10.0}, {90.0, 15.0}, {180.0, 20.0}}; + const Interval rangeInterval = Interval::Closed(0.0, 1e6); + + const VisibilityCriterion visibilityCriterion = + VisibilityCriterion::FromAERMask(azimuthElevationMask, rangeInterval); + + const auto aerMask = visibilityCriterion.as(); + ASSERT_TRUE(aerMask.has_value()); + for (const auto& [azimuth, elevation] : azimuthElevationMask) + { + EXPECT_EQ(aerMask.value().azimuthElevationMask.at(azimuth * deg2rad), elevation * deg2rad); + } + EXPECT_EQ(aerMask.value().range, rangeInterval); + } + + // LineOfSight + { + const VisibilityCriterion visibilityCriterion = VisibilityCriterion::FromLineOfSight(defaultEnvironment_); + + const auto lineOfSight = visibilityCriterion.as(); + ASSERT_TRUE(lineOfSight.has_value()); + EXPECT_TRUE(lineOfSight.value().environment.isDefined()); + } + + // ElevationInterval + { + const Interval elevationInterval = Interval::Closed(0.0, 90.0); + + const VisibilityCriterion visibilityCriterion = VisibilityCriterion::FromElevationInterval(elevationInterval); + + const auto elevationIntervalCriterion = visibilityCriterion.as(); + ASSERT_TRUE(elevationIntervalCriterion.has_value()); + EXPECT_EQ( + elevationIntervalCriterion.value().elevation.getLowerBound(), elevationInterval.getLowerBound() * deg2rad + ); + EXPECT_EQ( + elevationIntervalCriterion.value().elevation.getUpperBound(), elevationInterval.getUpperBound() * deg2rad + ); + } +} + +TEST_F(OpenSpaceToolkit_Astrodynamics_Access_VisibilityCriterion, AERIntervalIsSatisfied) +{ + { + const Interval azimuthInterval = Interval::Closed(0.0, 360.0); + const Interval elevationInterval = Interval::Closed(0.0, 90.0); + const Interval rangeInterval = Interval::Closed(0.0, 500e3); + + const VisibilityCriterion visibilityCriterion = + VisibilityCriterion::FromAERInterval(azimuthInterval, elevationInterval, rangeInterval); + + const auto aerInterval = visibilityCriterion.as(); + ASSERT_TRUE(aerInterval.has_value()); + + AER aer(Angle::Degrees(90.0), Angle::Degrees(22.5), Length::Meters(250e3)); + + EXPECT_TRUE(aerInterval.value().isSatisfied(aer)); + + AER aerOutside(Angle::Degrees(180.0), Angle::Degrees(-22.5), Length::Meters(250e3)); + + EXPECT_FALSE(aerInterval.value().isSatisfied(aerOutside)); + } +} + +TEST_F(OpenSpaceToolkit_Astrodynamics_Access_VisibilityCriterion, AERMaskIsSatisfied) +{ + { + Map azimuthElevationMask = {{0.0, 10.0}, {45.0, 15.0}, {90.0, 20.0}}; + const Interval rangeInterval = Interval::Closed(0.0, 1e6); + + const VisibilityCriterion visibilityCriterion = + VisibilityCriterion::FromAERMask(azimuthElevationMask, rangeInterval); + + const auto aerMask = visibilityCriterion.as(); + ASSERT_TRUE(aerMask.has_value()); + + AER aer(Angle::Degrees(22.5), Angle::Degrees(20.0), Length::Meters(500e3)); + + EXPECT_TRUE(aerMask.value().isSatisfied(aer)); + + AER aerOutside(Angle::Degrees(22.5), Angle::Degrees(5.0), Length::Meters(500e3)); + + EXPECT_FALSE(aerMask.value().isSatisfied(aerOutside)); + } +} + +TEST_F(OpenSpaceToolkit_Astrodynamics_Access_VisibilityCriterion, LineOfSightIsSatisfied) +{ + { + const VisibilityCriterion visibilityCriterion = VisibilityCriterion::FromLineOfSight(defaultEnvironment_); + + const auto lineOfSight = visibilityCriterion.as(); + ASSERT_TRUE(lineOfSight.has_value()); + + const Instant instant = Instant::J2000(); + const Vector3d fromPosition(7000e3, 0.0, 0.0); + const Vector3d toPosition(0.0, 7000e3, 0.0); + + EXPECT_FALSE(lineOfSight.value().isSatisfied(instant, fromPosition, toPosition)); + } +} + +TEST_F(OpenSpaceToolkit_Astrodynamics_Access_VisibilityCriterion, ElevationIntervalIsSatisfied) +{ + { + const Interval elevationInterval = Interval::Closed(10.0, 80.0); + + const VisibilityCriterion visibilityCriterion = VisibilityCriterion::FromElevationInterval(elevationInterval); + + const auto elevationIntervalCriterion = visibilityCriterion.as(); + ASSERT_TRUE(elevationIntervalCriterion.has_value()); + + const Real elevation = 45.0 * deg2rad; + + EXPECT_TRUE(elevationIntervalCriterion.value().isSatisfied(elevation)); + + const Real elevationOutside = 5.0 * deg2rad; + + EXPECT_FALSE(elevationIntervalCriterion.value().isSatisfied(elevationOutside)); + } +} From 08d7b6880be2a0fc6029769a1499edd221fb2887 Mon Sep 17 00:00:00 2001 From: vishwa shah Date: Thu, 5 Dec 2024 16:53:30 +0000 Subject: [PATCH 15/22] chore: cleanup --- .../Astrodynamics/Access/Generator.cpp | 81 +------------------ 1 file changed, 4 insertions(+), 77 deletions(-) diff --git a/src/OpenSpaceToolkit/Astrodynamics/Access/Generator.cpp b/src/OpenSpaceToolkit/Astrodynamics/Access/Generator.cpp index 4b5eb1c3b..1f26df390 100644 --- a/src/OpenSpaceToolkit/Astrodynamics/Access/Generator.cpp +++ b/src/OpenSpaceToolkit/Astrodynamics/Access/Generator.cpp @@ -419,8 +419,6 @@ Array> Generator::computeAccessesForFixedTargets( const bool& coarse ) const { - std::cout << "Computing accesses for fixed targets" << std::endl; - if (stateFilter_) { throw ostk::core::error::RuntimeError("State filter is not supported for multiple access targets."); @@ -661,7 +659,6 @@ Array> Generator::computeAccessesForFixedTargets( MatrixXi inAccessPerTarget = MatrixXi::Zero(instants.getSize(), targetCount); - std::chrono::steady_clock::time_point start = std::chrono::steady_clock::now(); for (Index index = 0; index < instants.getSize(); ++index) { const Instant& instant = instants[index]; @@ -677,12 +674,6 @@ Array> Generator::computeAccessesForFixedTargets( inAccessPerTarget.row(index) = inAccess.cast().transpose(); } - std::chrono::steady_clock::time_point end = std::chrono::steady_clock::now(); - - std::cout << "Access checks took " << std::chrono::duration_cast(end - start).count() - << "ms" << std::endl; - - start = std::chrono::steady_clock::now(); Array> accessIntervalsPerTarget = Array>(targetCount, Array::Empty()); @@ -691,12 +682,6 @@ Array> Generator::computeAccessesForFixedTargets( accessIntervalsPerTarget[i] = ComputeIntervals(inAccessPerTarget.col(i), instants); } - end = std::chrono::steady_clock::now(); - - std::cout << "Computing intervals took " - << std::chrono::duration_cast(end - start).count() << "ms" << std::endl; - - start = std::chrono::steady_clock::now(); if (!coarse) { for (Index i = 0; i < accessIntervalsPerTarget.getSize(); ++i) @@ -710,12 +695,7 @@ Array> Generator::computeAccessesForFixedTargets( ); } } - end = std::chrono::steady_clock::now(); - - std::cout << "Computing precise crossings took " - << std::chrono::duration_cast(end - start).count() << "ms" << std::endl; - start = std::chrono::steady_clock::now(); Array> accesses = Array>(targetCount, Array::Empty()); for (Index i = 0; i < accessIntervalsPerTarget.getSize(); ++i) { @@ -723,10 +703,6 @@ Array> Generator::computeAccessesForFixedTargets( accesses[i] = this->generateAccessesFromIntervals(accessIntervalsPerTarget[i], anInterval, fromTrajectory, aToTrajectory); } - end = std::chrono::steady_clock::now(); - - std::cout << "Generating accesses took " - << std::chrono::duration_cast(end - start).count() << "ms" << std::endl; return accesses; } @@ -1022,13 +998,11 @@ Instant Generator::FindTimeOfClosestApproach( const Instant queryInstant = contextPtr->startInstant + Duration::Seconds(x[0]); const auto [queryFromState, queryToState] = contextPtr->getStatesAt(queryInstant); - const auto queryFromPosition = queryFromState.getPosition(); - const auto queryToPosition = queryToState.getPosition(); const Vector3d deltaPosition = - queryToState.getPosition().accessCoordinates() - queryFromState.getPosition().accessCoordinates(); + queryFromState.getPosition().accessCoordinates() - queryToState.getPosition().accessCoordinates(); const Vector3d deltaVelocity = - queryToState.getVelocity().accessCoordinates() - queryFromState.getVelocity().accessCoordinates(); + queryFromState.getVelocity().accessCoordinates() - queryToState.getVelocity().accessCoordinates(); const Real rangeSquared = deltaPosition.squaredNorm(); @@ -1051,7 +1025,8 @@ Instant Generator::FindTimeOfClosestApproach( }, }; - nlopt::opt optimizer = {nlopt::LD_MMA, 1}; + // nlopt::opt optimizer = {nlopt::LD_MMA, 1}; + nlopt::opt optimizer = {nlopt::LN_COBYLA, 1}; const std::vector lowerBound = {0.0}; const std::vector upperBound = {anAccessInterval.getDuration().inSeconds()}; @@ -1091,58 +1066,10 @@ Instant Generator::FindTimeOfClosestApproach( } catch (const std::exception& anException) { - std::cout << anAccessInterval << std::endl; throw ostk::core::error::RuntimeError("Cannot find TCA (algorithm failed): [{}].", anException.what()); } } -// Instant Generator::FindTimeOfClosestApproach( -// const physics::time::Interval& anAccessInterval, -// const Trajectory& aFromTrajectory, -// const Trajectory& aToTrajectory, -// const Duration& aTolerance -// ) -// { -// const Instant startInstant = anAccessInterval.getStart(); -// const double durationSeconds = anAccessInterval.getDuration().inSeconds(); - -// // Function representing the derivative of the range between the trajectories -// const auto rangeDerivativeFunction = [&aFromTrajectory, &aToTrajectory, &startInstant](const double t) -> double -// { -// const Instant instant = startInstant + Duration::Seconds(t); -// const State fromState = aFromTrajectory.getStateAt(instant); -// const State toState = aToTrajectory.getStateAt(instant); - -// const Vector3d deltaPosition = -// toState.getPosition().accessCoordinates() - fromState.getPosition().accessCoordinates(); -// const Vector3d deltaVelocity = -// toState.getVelocity().accessCoordinates() - fromState.getVelocity().accessCoordinates(); - -// // Derivative of the range with respect to time -// return deltaPosition.dot(deltaVelocity) / deltaPosition.norm(); -// }; - -// const RootSolver rootSolver(20, aTolerance.inSeconds()); - -// try -// { -// const auto solution = rootSolver.solve(rangeDerivativeFunction, 0.0, durationSeconds); - -// if (!solution.hasConverged) -// { -// return Instant::Undefined(); -// } - -// return startInstant + Duration::Seconds(solution.root); -// } -// catch (const std::exception& anException) -// { -// return Instant::Undefined(); -// } - -// return Instant::Undefined(); -// } - Angle Generator::CalculateElevationAt( const Instant& anInstant, const Trajectory& aFromTrajectory, From 451ebf7d413017a4762a58358493c2f7b50cdb4b Mon Sep 17 00:00:00 2001 From: Vishwa Shah Date: Sat, 4 Jan 2025 09:08:45 -0600 Subject: [PATCH 16/22] Apply suggestions from code review --- include/OpenSpaceToolkit/Astrodynamics/Access/Generator.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/OpenSpaceToolkit/Astrodynamics/Access/Generator.hpp b/include/OpenSpaceToolkit/Astrodynamics/Access/Generator.hpp index 3821cc309..02e93acce 100644 --- a/include/OpenSpaceToolkit/Astrodynamics/Access/Generator.hpp +++ b/include/OpenSpaceToolkit/Astrodynamics/Access/Generator.hpp @@ -182,7 +182,7 @@ class AccessTarget VisibilityCriterion visibilityCriterion_; Trajectory trajectory_; - AccessTarget(const Type& aType, const VisibilityCriterion& constrant, const Trajectory& aTrajectory); + AccessTarget(const Type& aType, const VisibilityCriterion& constraint, const Trajectory& aTrajectory); }; class Generator From 0b114aabc6eeda139a0d4c00b9b06bb0cf231a4b Mon Sep 17 00:00:00 2001 From: Vishwa Shah Date: Sat, 4 Jan 2025 09:11:50 -0600 Subject: [PATCH 17/22] Apply suggestions from code review --- .../Astrodynamics/Access/Generator.hpp | 12 ++++++------ .../Astrodynamics/Access/VisibilityCriterion.hpp | 4 ++-- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/include/OpenSpaceToolkit/Astrodynamics/Access/Generator.hpp b/include/OpenSpaceToolkit/Astrodynamics/Access/Generator.hpp index 02e93acce..e699b8efb 100644 --- a/include/OpenSpaceToolkit/Astrodynamics/Access/Generator.hpp +++ b/include/OpenSpaceToolkit/Astrodynamics/Access/Generator.hpp @@ -138,12 +138,12 @@ class AccessTarget /// @brief Construct an Access Target from an LLA (Latitude, Longitude, Altitude) /// /// @code{.cpp} - /// AccessTarget accessTarget = AccessTarget::LLA( + /// AccessTarget accessTarget = AccessTarget::FromLLA( /// visibilityCriterion, lla, celestialSPtr /// ); /// @endcode /// - /// @param constraint + /// @param aVisibilityCriterion /// @param anLLA /// @param aCelestialSPtr /// @return Access target @@ -154,12 +154,12 @@ class AccessTarget /// @brief Construct an Access Target from a position /// /// @code{.cpp} - /// AccessTarget accessTarget = AccessTarget::Position( + /// AccessTarget accessTarget = AccessTarget::FromPosition( /// visibilityCriterion, position /// ); /// @endcode /// - /// @param constraint + /// @param aVisibilityCriterion /// @param aPosition /// @return Access target static AccessTarget FromPosition(const VisibilityCriterion& aVisibilityCriterion, const Position& aPosition); @@ -172,7 +172,7 @@ class AccessTarget /// ); /// @endcode /// - /// @param constraint + /// @param aVisibilityCriterion /// @param aTrajectory /// @return Access target static AccessTarget FromTrajectory(const VisibilityCriterion& aVisibilityCriterion, const Trajectory& aTrajectory); @@ -182,7 +182,7 @@ class AccessTarget VisibilityCriterion visibilityCriterion_; Trajectory trajectory_; - AccessTarget(const Type& aType, const VisibilityCriterion& constraint, const Trajectory& aTrajectory); + AccessTarget(const Type& aType, const VisibilityCriterion& aVisibilityCriterion, const Trajectory& aTrajectory); }; class Generator diff --git a/include/OpenSpaceToolkit/Astrodynamics/Access/VisibilityCriterion.hpp b/include/OpenSpaceToolkit/Astrodynamics/Access/VisibilityCriterion.hpp index 4a58f6366..be72c84ed 100644 --- a/include/OpenSpaceToolkit/Astrodynamics/Access/VisibilityCriterion.hpp +++ b/include/OpenSpaceToolkit/Astrodynamics/Access/VisibilityCriterion.hpp @@ -242,9 +242,9 @@ class VisibilityCriterion template std::optional as() const { - if (const auto* constraintPtr = std::get_if(&visibilityCriterion_)) + if (const auto* visibilityCriterionPtr = std::get_if(&visibilityCriterion_)) { - return *constraintPtr; + return *visibilityCriterionPtr; } return std::nullopt; From cc9a38e12c1405e352eb9c8ac37e40b49ca55f40 Mon Sep 17 00:00:00 2001 From: vishwa shah Date: Sat, 4 Jan 2025 18:05:50 +0000 Subject: [PATCH 18/22] feat: fix tests --- .../Access/Generator.cpp | 12 +- bindings/python/test/access/test_generator.py | 95 ++-- .../Astrodynamics/Access/Generator.hpp | 18 +- .../Astrodynamics/Access/Generator.cpp | 73 ++-- .../Astrodynamics/Access/Generator.test.cpp | 405 ++++++++++-------- .../Access/VisibilityCriterion.test.cpp | 65 ++- 6 files changed, 370 insertions(+), 298 deletions(-) diff --git a/bindings/python/src/OpenSpaceToolkitAstrodynamicsPy/Access/Generator.cpp b/bindings/python/src/OpenSpaceToolkitAstrodynamicsPy/Access/Generator.cpp index 72987a63f..1a5fa5721 100644 --- a/bindings/python/src/OpenSpaceToolkitAstrodynamicsPy/Access/Generator.cpp +++ b/bindings/python/src/OpenSpaceToolkitAstrodynamicsPy/Access/Generator.cpp @@ -43,7 +43,7 @@ inline void OpenSpaceToolkitAstrodynamicsPy_Access_Generator(pybind11::module& a accessTargetClass .def( "get_type", - &AccessTarget::getType, + &AccessTarget::accessType, R"doc( Get the type of the access target. @@ -53,7 +53,7 @@ inline void OpenSpaceToolkitAstrodynamicsPy_Access_Generator(pybind11::module& a ) .def( "get_visibility_criterion", - &AccessTarget::getVisibilityCriterion, + &AccessTarget::accessVisibilityCriterion, R"doc( Get the visibility criterion associated with the access target. @@ -63,7 +63,7 @@ inline void OpenSpaceToolkitAstrodynamicsPy_Access_Generator(pybind11::module& a ) .def( "get_trajectory", - &AccessTarget::getTrajectory, + &AccessTarget::accessTrajectory, R"doc( Get the trajectory associated with the access target. @@ -258,14 +258,14 @@ inline void OpenSpaceToolkitAstrodynamicsPy_Access_Generator(pybind11::module& a Get the condition function. Args: - from_trajectory (State): The state at the start of the interval. - to_trajectory (State): The state at the end of the interval. + access_target (AccessTarget): The access target from which the condition function is being evaluated against. + to_trajectory (Trajectory): The trajectory to which the condition function is being evaluated against. Returns: function: The condition function. )doc", - arg("from_trajectory"), + arg("access_target"), arg("to_trajectory") ) .def( diff --git a/bindings/python/test/access/test_generator.py b/bindings/python/test/access/test_generator.py index 8eac319af..8a0a70463 100644 --- a/bindings/python/test/access/test_generator.py +++ b/bindings/python/test/access/test_generator.py @@ -44,9 +44,8 @@ def earth(environment: Environment) -> Celestial: def generator(environment: Environment) -> Generator: return Generator( environment=environment, - aer_filter=lambda aer: True, access_filter=lambda access: True, - state_filter=lambda state_1, state_2: True, + state_filter=None, ) @@ -109,6 +108,14 @@ def access_target( return AccessTarget.from_lla(visibility_criterion, lla, earth) +@pytest.fixture +def trajectory_target( + visibility_criterion: VisibilityCriterion, + from_trajectory: Trajectory, +) -> AccessTarget: + return AccessTarget.from_trajectory(visibility_criterion, from_trajectory) + + class TestAccessTarget: def test_constructor_success(self, access_target: AccessTarget): assert isinstance(access_target, AccessTarget) @@ -187,15 +194,6 @@ def test_constructor_success_environment(self, environment: Environment): assert generator is not None assert isinstance(generator, Generator) - def test_constructor_success_environment_aer_filter(self, environment: Environment): - generator = Generator( - environment=environment, - aer_filter=lambda aer: True, - ) - - assert generator is not None - assert isinstance(generator, Generator) - def test_constructor_success_environment_access_filter( self, environment: Environment, @@ -238,18 +236,17 @@ def test_constructor_success_environment_step_tolerance( def test_getters_success(self, generator: Generator): assert generator.get_step() == Duration.minutes(1.0) assert generator.get_tolerance() == Duration.microseconds(1.0) - assert generator.get_aer_filter() is not None assert generator.get_access_filter() is not None - assert generator.get_state_filter() is not None + assert generator.get_state_filter() is None def test_get_condition_function_success( self, generator: Generator, - from_trajectory: Trajectory, + trajectory_target: AccessTarget, to_trajectory: Trajectory, ): condition_function = generator.get_condition_function( - from_trajectory=from_trajectory, + access_target=trajectory_target, to_trajectory=to_trajectory, ) @@ -264,7 +261,7 @@ def test_get_condition_function_success( def test_compute_accesses_success( self, generator: Generator, - from_trajectory: Trajectory, + trajectory_target: AccessTarget, to_trajectory: Trajectory, ): accesses = generator.compute_accesses( @@ -272,7 +269,7 @@ def test_compute_accesses_success( Instant.date_time(DateTime(2018, 1, 1, 0, 0, 0), Scale.UTC), Instant.date_time(DateTime(2018, 1, 1, 2, 0, 0), Scale.UTC), ), - from_trajectory=from_trajectory, + access_target=trajectory_target, to_trajectory=to_trajectory, ) @@ -281,15 +278,33 @@ def test_compute_accesses_success( assert accesses[0] is not None assert isinstance(accesses[0], Access) + def test_compute_accesses_multiple_targets_success( + self, + generator: Generator, + trajectory_target: AccessTarget, + to_trajectory: Trajectory, + ): + accesses = generator.compute_accesses( + interval=Interval.closed( + Instant.date_time(DateTime(2018, 1, 1, 0, 0, 0), Scale.UTC), + Instant.date_time(DateTime(2018, 1, 1, 2, 0, 0), Scale.UTC), + ), + access_targets=[trajectory_target], + to_trajectory=to_trajectory, + ) + + assert accesses is not None + assert isinstance(accesses, list) + assert accesses[0] is not None + assert isinstance(accesses[0], list) + assert isinstance(accesses[0][0], Access) + def test_set_step_success(self, generator: Generator): generator.set_step(Duration.seconds(1.0)) def test_set_tolerance_success(self, generator: Generator): generator.set_tolerance(Duration.seconds(1.0)) - def test_set_aer_filter_success(self, generator: Generator): - generator.set_aer_filter(aer_filter=lambda aer: True) - def test_set_access_filter_success(self, generator: Generator): generator.set_access_filter(access_filter=lambda access: True) @@ -302,43 +317,3 @@ def test_undefined_success(self): assert generator is not None assert isinstance(generator, Generator) assert generator.is_defined() is False - - def test_aer_ranges_success(self, environment: Environment): - # Construct arbitrary AER ranges - azimuth_interval = RealInterval.closed(0.0, 360.0) - elevation_interval = RealInterval.closed(0.0, 90.0) - range_interval = RealInterval.closed(0.0, 7000e3) - - generator = Generator.aer_ranges( - azimuth_range=azimuth_interval, - elevation_range=elevation_interval, - range_range=range_interval, - environment=environment, - ) - - assert generator is not None - assert isinstance(generator, Generator) - assert generator.is_defined() - - def test_aer_mask_success(self, environment: Environment): - # Construct arbitrary anAzimuthElevationMask using python dict - an_azimuth_elevation_mask = { - 0.0: 30.0, - 90.0: 60.0, - 180.0: 60.0, - 270.0: 30.0, - 359.0: 30.0, - } - - # Construct arbitrary aRangerange - a_range_range = RealInterval(0.0, 10e4, RealInterval.Type.Closed) - - generator = Generator.aer_mask( - azimuth_elevation_mask=an_azimuth_elevation_mask, - range_range=a_range_range, - environment=environment, - ) - - assert generator is not None - assert isinstance(generator, Generator) - assert generator.is_defined() diff --git a/include/OpenSpaceToolkit/Astrodynamics/Access/Generator.hpp b/include/OpenSpaceToolkit/Astrodynamics/Access/Generator.hpp index e699b8efb..4afbecc44 100644 --- a/include/OpenSpaceToolkit/Astrodynamics/Access/Generator.hpp +++ b/include/OpenSpaceToolkit/Astrodynamics/Access/Generator.hpp @@ -72,35 +72,35 @@ class AccessTarget Trajectory }; - /// @brief Get the type + /// @brief Access the type /// /// @code{.cpp} /// AccessTarget accessTarget = { ... } ; - /// AccessTarget::Type type = accessTarget.getType(); + /// AccessTarget::Type type = accessTarget.accessType(); /// @endcode /// /// @return The type - Type getType() const; + const Type& accessType() const; - /// @brief Get the visibility criteria + /// @brief Access the visibility criteria /// /// @code{.cpp} /// AccessTarget accessTarget = { ... } ; - /// VisibilityCriterion visibilityCriterion = accessTarget.getVisibilityCriterion(); + /// VisibilityCriterion visibilityCriterion = accessTarget.accessVisibilityCriterion(); /// @endcode /// /// @return The visibility criteria - VisibilityCriterion getVisibilityCriterion() const; + const VisibilityCriterion& accessVisibilityCriterion() const; - /// @brief Get the trajectory + /// @brief Access the trajectory /// /// @code{.cpp} /// AccessTarget accessTarget = { ... } ; - /// Trajectory trajectory = accessTarget.getTrajectory(); + /// const Trajectory& trajectory = accessTarget.accessTrajectory(); /// @endcode /// /// @return The trajectory - Trajectory getTrajectory() const; + const Trajectory& accessTrajectory() const; /// @brief Get the position /// diff --git a/src/OpenSpaceToolkit/Astrodynamics/Access/Generator.cpp b/src/OpenSpaceToolkit/Astrodynamics/Access/Generator.cpp index 1f26df390..fd420273c 100644 --- a/src/OpenSpaceToolkit/Astrodynamics/Access/Generator.cpp +++ b/src/OpenSpaceToolkit/Astrodynamics/Access/Generator.cpp @@ -44,17 +44,17 @@ namespace astrodynamics namespace access { -AccessTarget::Type AccessTarget::getType() const +const AccessTarget::Type& AccessTarget::accessType() const { return type_; } -VisibilityCriterion AccessTarget::getVisibilityCriterion() const +const VisibilityCriterion& AccessTarget::accessVisibilityCriterion() const { return visibilityCriterion_; } -Trajectory AccessTarget::getTrajectory() const +const Trajectory& AccessTarget::accessTrajectory() const { return trajectory_; } @@ -205,13 +205,6 @@ std::function Generator::getConditionFunction( const AccessTarget& anAccessTarget, const Trajectory& aToTrajectory ) const { - const Trajectory aFromTrajectory = anAccessTarget.getTrajectory(); - - if (!aFromTrajectory.isDefined()) - { - throw ostk::core::error::runtime::Undefined("From Trajectory"); - } - if (!aToTrajectory.isDefined()) { throw ostk::core::error::runtime::Undefined("To Trajectory"); @@ -222,11 +215,9 @@ std::function Generator::getConditionFunction( throw ostk::core::error::runtime::Undefined("Generator"); } - const VisibilityCriterion visibilityCriterion = anAccessTarget.getVisibilityCriterion(); - - return [visibilityCriterion, aFromTrajectory, &aToTrajectory, this](const Instant& anInstant) mutable -> bool + return [&anAccessTarget, &aToTrajectory, this](const Instant& anInstant) mutable -> bool { - const State fromState = aFromTrajectory.getStateAt(anInstant); + const State fromState = anAccessTarget.accessTrajectory().getStateAt(anInstant); const State toState = aToTrajectory.getStateAt(anInstant); if (this->getStateFilter() && (!this->getStateFilter()(fromState, toState))) @@ -240,6 +231,8 @@ std::function Generator::getConditionFunction( const Position fromPosition_ITRF = fromPosition.inFrame(Frame::ITRF(), anInstant); const Position toPosition_ITRF = toPosition.inFrame(Frame::ITRF(), anInstant); + const VisibilityCriterion& visibilityCriterion = anAccessTarget.accessVisibilityCriterion(); + if (visibilityCriterion.is()) { return visibilityCriterion.as().value().isSatisfied( @@ -282,7 +275,7 @@ Array Generator::computeAccesses( throw ostk::core::error::runtime::Undefined("To Trajectory"); } - if (anAccessTarget.getType() == AccessTarget::Type::Trajectory) + if (anAccessTarget.accessType() == AccessTarget::Type::Trajectory) { if (coarse) { @@ -324,7 +317,7 @@ Array> Generator::computeAccesses( someAccessTargets.end(), [](const auto& accessTarget) { - return accessTarget.getType() == AccessTarget::Type::Trajectory; + return accessTarget.accessType() == AccessTarget::Type::Trajectory; } )) { @@ -351,7 +344,7 @@ Array> Generator::computeAccesses( someAccessTargets.end(), [](const auto& accessTarget) { - return accessTarget.getType() == AccessTarget::Type::Fixed; + return accessTarget.accessType() == AccessTarget::Type::Fixed; } )) { @@ -407,7 +400,7 @@ Array Generator::computeAccessesForTrajectoryTarget( const Array accessIntervals = temporalConditionSolver.solve(this->getConditionFunction(anAccessTarget, aToTrajectory), anInterval); - const Trajectory aFromTrajectory = anAccessTarget.getTrajectory(); + const Trajectory& aFromTrajectory = anAccessTarget.accessTrajectory(); return generateAccessesFromIntervals(accessIntervals, anInterval, aFromTrajectory, aToTrajectory); } @@ -452,7 +445,7 @@ Array> Generator::computeAccessesForFixedTargets( someAccessTargets.end(), [](const auto& accessTarget) { - return accessTarget.getVisibilityCriterion().template is(); + return accessTarget.accessVisibilityCriterion().template is(); } ); const bool allAccessTargetsHaveAERIntervals = std::all_of( @@ -460,7 +453,7 @@ Array> Generator::computeAccessesForFixedTargets( someAccessTargets.end(), [](const auto& accessTarget) { - return accessTarget.getVisibilityCriterion().template is(); + return accessTarget.accessVisibilityCriterion().template is(); } ); const bool allAccessTargetsHaveLineOfSight = std::all_of( @@ -468,7 +461,7 @@ Array> Generator::computeAccessesForFixedTargets( someAccessTargets.end(), [](const auto& accessTarget) { - return accessTarget.getVisibilityCriterion().template is(); + return accessTarget.accessVisibilityCriterion().template is(); } ); const bool allAccessTargetsHaveElevationIntervals = std::all_of( @@ -476,7 +469,7 @@ Array> Generator::computeAccessesForFixedTargets( someAccessTargets.end(), [](const auto& accessTarget) { - return accessTarget.getVisibilityCriterion().template is(); + return accessTarget.accessVisibilityCriterion().template is(); } ); @@ -535,7 +528,7 @@ Array> Generator::computeAccessesForFixedTargets( for (Index i = 0; i < targetCount; ++i) { const VisibilityCriterion::AERInterval visibilityCriterion = - someAccessTargets[i].getVisibilityCriterion().as().value(); + someAccessTargets[i].accessVisibilityCriterion().as().value(); aerLowerBounds(i, 0) = visibilityCriterion.azimuth.accessLowerBound(); aerLowerBounds(i, 1) = visibilityCriterion.elevation.accessLowerBound(); @@ -582,7 +575,7 @@ Array> Generator::computeAccessesForFixedTargets( for (Eigen::Index i = 0; i < mask.rows(); ++i) { const VisibilityCriterion::AERMask visibilityCriterion = - someAccessTargets[i].getVisibilityCriterion().as().value(); + someAccessTargets[i].accessVisibilityCriterion().as().value(); const double& azimuth_rad = azimuths_rad(i); const double& elevation_rad = elevations_rad(i); @@ -607,7 +600,7 @@ Array> Generator::computeAccessesForFixedTargets( for (Eigen::Index i = 0; i < mask.rows(); ++i) { const VisibilityCriterion::LineOfSight visibilityCriterion = - someAccessTargets[i].getVisibilityCriterion().as().value(); + someAccessTargets[i].accessVisibilityCriterion().as().value(); const Vector3d& fromPositionCoordinate_ITRF = aFromPositionCoordinates_ITRF.col(i); @@ -628,7 +621,7 @@ Array> Generator::computeAccessesForFixedTargets( for (Index i = 0; i < targetCount; ++i) { const VisibilityCriterion::ElevationInterval visibilityCriterion = - someAccessTargets[i].getVisibilityCriterion().as().value(); + someAccessTargets[i].accessVisibilityCriterion().as().value(); elevationLowerBounds(i) = visibilityCriterion.elevation.accessLowerBound(); elevationUpperBounds(i) = visibilityCriterion.elevation.accessUpperBound(); @@ -699,7 +692,7 @@ Array> Generator::computeAccessesForFixedTargets( Array> accesses = Array>(targetCount, Array::Empty()); for (Index i = 0; i < accessIntervalsPerTarget.getSize(); ++i) { - const Trajectory fromTrajectory = someAccessTargets[i].getTrajectory(); + const Trajectory& fromTrajectory = someAccessTargets[i].accessTrajectory(); accesses[i] = this->generateAccessesFromIntervals(accessIntervalsPerTarget[i], anInterval, fromTrajectory, aToTrajectory); } @@ -769,10 +762,10 @@ Array Generator::computePreciseCrossings( return {azimuth_rad, elevation_rad, range_m}; }; - if (anAccessTarget.getVisibilityCriterion().is()) + if (anAccessTarget.accessVisibilityCriterion().is()) { const VisibilityCriterion::AERInterval visibilityCriterion = - anAccessTarget.getVisibilityCriterion().as().value(); + anAccessTarget.accessVisibilityCriterion().as().value(); condition = [&computeAER, visibilityCriterion](const Instant& instant) -> bool { @@ -781,10 +774,10 @@ Array Generator::computePreciseCrossings( return visibilityCriterion.isSatisfied(azimuth_rad, elevation_rad, range_m); }; } - else if (anAccessTarget.getVisibilityCriterion().is()) + else if (anAccessTarget.accessVisibilityCriterion().is()) { const VisibilityCriterion::AERMask visibilityCriterion = - anAccessTarget.getVisibilityCriterion().as().value(); + anAccessTarget.accessVisibilityCriterion().as().value(); condition = [&computeAER, visibilityCriterion](const Instant& instant) -> bool { @@ -793,10 +786,10 @@ Array Generator::computePreciseCrossings( return visibilityCriterion.isSatisfied(azimuth_rad, elevation_rad, range_m); }; } - else if (anAccessTarget.getVisibilityCriterion().is()) + else if (anAccessTarget.accessVisibilityCriterion().is()) { const VisibilityCriterion::LineOfSight visibilityCriterion = - anAccessTarget.getVisibilityCriterion().as().value(); + anAccessTarget.accessVisibilityCriterion().as().value(); condition = [&fromPositionCoordinate_ITRF, &aToTrajectory, visibilityCriterion](const Instant& instant) -> bool { @@ -806,10 +799,10 @@ Array Generator::computePreciseCrossings( return visibilityCriterion.isSatisfied(instant, fromPositionCoordinate_ITRF, toPositionCoordinates_ITRF); }; } - else if (anAccessTarget.getVisibilityCriterion().is()) + else if (anAccessTarget.accessVisibilityCriterion().is()) { const VisibilityCriterion::ElevationInterval visibilityCriterion = - anAccessTarget.getVisibilityCriterion().as().value(); + anAccessTarget.accessVisibilityCriterion().as().value(); condition = [&fromPositionCoordinate_ITRF, &SEZRotation, &aToTrajectory, visibilityCriterion]( const Instant& instant @@ -987,7 +980,7 @@ Instant Generator::FindTimeOfClosestApproach( const auto calculateRange = [](const std::vector& x, std::vector& aGradient, void* aDataContext ) -> double { - // iterationCount++; + (void)aGradient; if (aDataContext == nullptr) { throw ostk::core::error::runtime::Wrong("Data context"); @@ -1001,16 +994,9 @@ Instant Generator::FindTimeOfClosestApproach( const Vector3d deltaPosition = queryFromState.getPosition().accessCoordinates() - queryToState.getPosition().accessCoordinates(); - const Vector3d deltaVelocity = - queryFromState.getVelocity().accessCoordinates() - queryToState.getVelocity().accessCoordinates(); const Real rangeSquared = deltaPosition.squaredNorm(); - if (!aGradient.empty()) - { - aGradient[0] = 2.0 * deltaPosition.dot(deltaVelocity); - } - return rangeSquared; }; @@ -1025,7 +1011,6 @@ Instant Generator::FindTimeOfClosestApproach( }, }; - // nlopt::opt optimizer = {nlopt::LD_MMA, 1}; nlopt::opt optimizer = {nlopt::LN_COBYLA, 1}; const std::vector lowerBound = {0.0}; diff --git a/test/OpenSpaceToolkit/Astrodynamics/Access/Generator.test.cpp b/test/OpenSpaceToolkit/Astrodynamics/Access/Generator.test.cpp index 9b88ca412..90394a72f 100644 --- a/test/OpenSpaceToolkit/Astrodynamics/Access/Generator.test.cpp +++ b/test/OpenSpaceToolkit/Astrodynamics/Access/Generator.test.cpp @@ -99,147 +99,47 @@ class OpenSpaceToolkit_Astrodynamics_Access_AccessTarget : public ::testing::Tes TEST_F(OpenSpaceToolkit_Astrodynamics_Access_AccessTarget, Constructor) { // Constructor + { + // FromLLA + { - const VisibilityCriterion visibilityCriterion = VisibilityCriterion::FromAERInterval( - defaultAzimuthInterval_, defaultElevationInterval_, defaultRangeInterval_ - ); + EXPECT_NO_THROW(AccessTarget::FromLLA(defaultVisibilityCriterion_, defaultLLA_, defaultEarthSPtr_)); - EXPECT_NO_THROW(AccessTarget::FromLLA(visibilityCriterion, defaultLLA_, defaultEarthSPtr_)); + EXPECT_THROW( + AccessTarget::FromLLA(defaultVisibilityCriterion_, LLA::Undefined(), defaultEarthSPtr_), + ostk::core::error::runtime::Undefined + ); } + // FromPosition + { - const VisibilityCriterion visibilityCriterion = VisibilityCriterion::FromAERInterval( - defaultAzimuthInterval_, defaultElevationInterval_, defaultRangeInterval_ - ); { const Position position = Position::Meters({0.0, 0.0, 0.0}, Frame::ITRF()); - EXPECT_NO_THROW(AccessTarget::FromPosition(visibilityCriterion, position)); + EXPECT_NO_THROW(AccessTarget::FromPosition(defaultVisibilityCriterion_, position)); } { - const Position position = Position::Meters({0.0, 0.0, 0.0}, Frame::GCRF()); EXPECT_THROW( - AccessTarget::FromPosition(visibilityCriterion, position), ostk::core::error::RuntimeError + AccessTarget::FromPosition(defaultVisibilityCriterion_, Position::Undefined()), + ostk::core::error::runtime::Undefined ); } } - // undefined - { - const Position position = Position::Meters({0.0, 0.0, 0.0}, Frame::ITRF()); + // FromTrajectory - EXPECT_THROW( - AccessTarget::FromPosition( - VisibilityCriterion::FromAERInterval( - ostk::mathematics::object::Interval::Undefined(), - defaultElevationInterval_, - defaultRangeInterval_ - ), - position - ), - ostk::core::error::runtime::Undefined - ); - EXPECT_THROW( - AccessTarget::FromPosition( - VisibilityCriterion::FromAERInterval( - defaultAzimuthInterval_, - ostk::mathematics::object::Interval::Undefined(), - defaultRangeInterval_ - ), - position - ), - ostk::core::error::runtime::Undefined - ); - EXPECT_THROW( - AccessTarget::FromPosition( - VisibilityCriterion::FromAERInterval( - defaultAzimuthInterval_, - defaultElevationInterval_, - ostk::mathematics::object::Interval::Undefined() - ), - position - ), - ostk::core::error::runtime::Undefined - ); - EXPECT_THROW( - AccessTarget::FromLLA( - VisibilityCriterion::FromAERInterval( - defaultAzimuthInterval_, defaultElevationInterval_, defaultRangeInterval_ - ), - LLA::Undefined(), - defaultEarthSPtr_ - ), - ostk::core::error::runtime::Undefined - ); - } - - // incorrect bounds { { - EXPECT_THROW( - AccessTarget::FromLLA( - VisibilityCriterion::FromAERInterval( - ostk::mathematics::object::Interval::Closed(-1.0, 350.0), - defaultElevationInterval_, - defaultRangeInterval_ - ), - defaultLLA_, - defaultEarthSPtr_ - ), - ostk::core::error::RuntimeError - ); - EXPECT_THROW( - AccessTarget::FromLLA( - VisibilityCriterion::FromAERInterval( - ostk::mathematics::object::Interval::Closed(0.0, 360.1), - defaultElevationInterval_, - defaultRangeInterval_ - ), - defaultLLA_, - defaultEarthSPtr_ - ), - ostk::core::error::RuntimeError - ); - } - { - EXPECT_THROW( - AccessTarget::FromLLA( - VisibilityCriterion::FromAERInterval( - defaultAzimuthInterval_, - ostk::mathematics::object::Interval::Closed(-91.0, 0.0), - defaultRangeInterval_ - ), - defaultLLA_, - defaultEarthSPtr_ - ), - ostk::core::error::RuntimeError - ); - EXPECT_THROW( - AccessTarget::FromLLA( - VisibilityCriterion::FromAERInterval( - defaultAzimuthInterval_, - ostk::mathematics::object::Interval::Closed(-45.0, 91.0), - defaultRangeInterval_ - ), - defaultLLA_, - defaultEarthSPtr_ - ), - ostk::core::error::RuntimeError - ); + const Trajectory trajectory = Trajectory::Position(Position::Meters({0.0, 0.0, 0.0}, Frame::ITRF())); + EXPECT_NO_THROW(AccessTarget::FromTrajectory(defaultVisibilityCriterion_, trajectory)); } + { EXPECT_THROW( - AccessTarget::FromLLA( - VisibilityCriterion::FromAERInterval( - defaultAzimuthInterval_, - defaultElevationInterval_, - ostk::mathematics::object::Interval::Closed(-1.0, 5.0) - ), - defaultLLA_, - defaultEarthSPtr_ - ), - ostk::core::error::RuntimeError + AccessTarget::FromTrajectory(defaultVisibilityCriterion_, Trajectory::Undefined()), + ostk::core::error::runtime::Undefined ); } } @@ -278,22 +178,22 @@ TEST_F(OpenSpaceToolkit_Astrodynamics_Access_Generator, Constructor) } } -TEST_F(OpenSpaceToolkit_Astrodynamics_Access_AccessTarget, getType) +TEST_F(OpenSpaceToolkit_Astrodynamics_Access_AccessTarget, AccessType) { - EXPECT_EQ(defaultAccessTarget_.getType(), AccessTarget::Type::Fixed); + EXPECT_EQ(defaultAccessTarget_.accessType(), AccessTarget::Type::Fixed); } -TEST_F(OpenSpaceToolkit_Astrodynamics_Access_AccessTarget, getVisibilityCriterion) +TEST_F(OpenSpaceToolkit_Astrodynamics_Access_AccessTarget, AccessVisibilityCriterion) { - EXPECT_EQ(defaultAccessTarget_.getVisibilityCriterion(), defaultVisibilityCriterion_); + EXPECT_EQ(defaultAccessTarget_.accessVisibilityCriterion(), defaultVisibilityCriterion_); } -TEST_F(OpenSpaceToolkit_Astrodynamics_Access_AccessTarget, getTrajectory) +TEST_F(OpenSpaceToolkit_Astrodynamics_Access_AccessTarget, AccessTrajectory) { - EXPECT_NO_THROW(defaultAccessTarget_.getTrajectory()); + EXPECT_NO_THROW(defaultAccessTarget_.accessTrajectory()); } -TEST_F(OpenSpaceToolkit_Astrodynamics_Access_AccessTarget, getPosition) +TEST_F(OpenSpaceToolkit_Astrodynamics_Access_AccessTarget, GetPosition) { { EXPECT_VECTORS_ALMOST_EQUAL( @@ -314,14 +214,14 @@ TEST_F(OpenSpaceToolkit_Astrodynamics_Access_AccessTarget, getPosition) } } -TEST_F(OpenSpaceToolkit_Astrodynamics_Access_AccessTarget, getLLA) +TEST_F(OpenSpaceToolkit_Astrodynamics_Access_AccessTarget, GetLLA) { EXPECT_VECTORS_ALMOST_EQUAL( defaultAccessTarget_.getLLA(defaultEarthSPtr_).toVector(), defaultLLA_.toVector(), 1e-15 ); } -TEST_F(OpenSpaceToolkit_Astrodynamics_Access_AccessTarget, computeR_SEZ_ECEF) +TEST_F(OpenSpaceToolkit_Astrodynamics_Access_AccessTarget, ComputeR_SEZ_ECEF) { Matrix3d r_SEZ_ECEF; r_SEZ_ECEF.row(0) = Vector3d {0.0, 0.0, -1.0}; @@ -336,8 +236,8 @@ TEST_F(OpenSpaceToolkit_Astrodynamics_Access_AccessTarget, FromLLA) const AccessTarget accessTarget = AccessTarget::FromLLA(defaultVisibilityCriterion_, defaultLLA_, defaultEarthSPtr_); - EXPECT_EQ(accessTarget.getType(), AccessTarget::Type::Fixed); - EXPECT_EQ(accessTarget.getVisibilityCriterion(), defaultVisibilityCriterion_); + EXPECT_EQ(accessTarget.accessType(), AccessTarget::Type::Fixed); + EXPECT_EQ(accessTarget.accessVisibilityCriterion(), defaultVisibilityCriterion_); } TEST_F(OpenSpaceToolkit_Astrodynamics_Access_AccessTarget, FromPosition) @@ -345,8 +245,8 @@ TEST_F(OpenSpaceToolkit_Astrodynamics_Access_AccessTarget, FromPosition) const Position position = Position::Meters({0.0, 0.0, 0.0}, Frame::ITRF()); const AccessTarget accessTarget = AccessTarget::FromPosition(defaultVisibilityCriterion_, position); - EXPECT_EQ(accessTarget.getType(), AccessTarget::Type::Fixed); - EXPECT_EQ(accessTarget.getVisibilityCriterion(), defaultVisibilityCriterion_); + EXPECT_EQ(accessTarget.accessType(), AccessTarget::Type::Fixed); + EXPECT_EQ(accessTarget.accessVisibilityCriterion(), defaultVisibilityCriterion_); } TEST_F(OpenSpaceToolkit_Astrodynamics_Access_AccessTarget, FromTrajectory) @@ -354,8 +254,8 @@ TEST_F(OpenSpaceToolkit_Astrodynamics_Access_AccessTarget, FromTrajectory) const Trajectory trajectory = Trajectory::Position(Position::Meters({0.0, 0.0, 0.0}, Frame::ITRF())); const AccessTarget accessTarget = AccessTarget::FromTrajectory(defaultVisibilityCriterion_, trajectory); - EXPECT_EQ(accessTarget.getType(), AccessTarget::Type::Trajectory); - EXPECT_EQ(accessTarget.getVisibilityCriterion(), defaultVisibilityCriterion_); + EXPECT_EQ(accessTarget.accessType(), AccessTarget::Type::Trajectory); + EXPECT_EQ(accessTarget.accessVisibilityCriterion(), defaultVisibilityCriterion_); } TEST_F(OpenSpaceToolkit_Astrodynamics_Access_Generator, IsDefined) @@ -505,6 +405,10 @@ TEST_F(OpenSpaceToolkit_Astrodynamics_Access_Generator, GetConditionFunction) { EXPECT_ANY_THROW(Generator::Undefined().getConditionFunction(accessTarget, toOrbit)); } + + { + EXPECT_ANY_THROW(defaultGenerator_.getConditionFunction(accessTarget, Trajectory::Undefined())); + } } TEST_F(OpenSpaceToolkit_Astrodynamics_Access_Generator, ComputeAccesses_1) @@ -869,6 +773,99 @@ TEST_F(OpenSpaceToolkit_Astrodynamics_Access_Generator, ComputeAccesses_4) TEST_F(OpenSpaceToolkit_Astrodynamics_Access_Generator, ComputeAccesses) { + { + const Instant startInstant = Instant::J2000(); + const Instant endInstant = startInstant + Duration::Days(1.0); + const Interval interval = Interval::Closed(startInstant, endInstant); + + const TLE tle = { + "1 39419U 13066D 18248.44969859 -.00000394 00000-0 -31796-4 0 9997", + "2 39419 97.6313 314.6863 0012643 218.7350 141.2966 14.93878994260975" + }; + + const SGP4 sgp4 = SGP4(tle); + const Orbit orbit = Orbit(sgp4, defaultEarthSPtr_); + + const ostk::mathematics::object::Interval azimuthInterval = + ostk::mathematics::object::Interval::Closed(0.0, 360.0); + const ostk::mathematics::object::Interval elevationInterval = + ostk::mathematics::object::Interval::Closed(0.0, 90.0); + const ostk::mathematics::object::Interval rangeInterval = + ostk::mathematics::object::Interval::Closed(0.0, 1.0e10); + + const VisibilityCriterion visibilityCriterion = + VisibilityCriterion::FromAERInterval(azimuthInterval, elevationInterval, rangeInterval); + + const AccessTarget accessTarget = AccessTarget::FromLLA( + visibilityCriterion, + LLA(Angle::Degrees(53.406), Angle::Degrees(-6.225), Length::Meters(50.5)), + defaultEarthSPtr_ + ); + + const AccessTarget trajectoryTarget = AccessTarget::FromTrajectory(visibilityCriterion, orbit); + + // array of targets + + { + EXPECT_THROW( + defaultGenerator_.computeAccesses(Interval::Undefined(), {accessTarget}, orbit), + ostk::core::error::runtime::Undefined + ); + + EXPECT_THROW(defaultGenerator_.computeAccesses(interval, {}, orbit), ostk::core::error::runtime::Undefined); + + EXPECT_THROW( + defaultGenerator_.computeAccesses(interval, {accessTarget}, Trajectory::Undefined()), + ostk::core::error::runtime::Undefined + ); + + EXPECT_THROW( + defaultGenerator_.computeAccesses(interval, {trajectoryTarget}, orbit, true), + ostk::core::error::RuntimeError + ); + + EXPECT_THROW( + defaultGenerator_.computeAccesses(interval, {trajectoryTarget, accessTarget}, orbit, true), + ostk::core::error::RuntimeError + ); + + { + const auto stateFilter = [](const State&, const State&) -> bool + { + return true; + }; + + defaultGenerator_.setStateFilter(stateFilter); + + EXPECT_THROW( + defaultGenerator_.computeAccesses(interval, {accessTarget, accessTarget}, orbit), + ostk::core::error::RuntimeError + ); + + defaultGenerator_.setStateFilter(nullptr); + } + } + + // single target + + { + EXPECT_THROW( + defaultGenerator_.computeAccesses(Interval::Undefined(), accessTarget, orbit), + ostk::core::error::runtime::Undefined + ); + + EXPECT_THROW( + defaultGenerator_.computeAccesses(interval, accessTarget, Trajectory::Undefined()), + ostk::core::error::runtime::Undefined + ); + + EXPECT_THROW( + defaultGenerator_.computeAccesses(interval, trajectoryTarget, orbit, true), + ostk::core::error::RuntimeError + ); + } + } + { const TLE tle = { "1 60504U 24149AN 24293.10070306 .00000000 00000-0 58313-3 0 08", @@ -878,7 +875,7 @@ TEST_F(OpenSpaceToolkit_Astrodynamics_Access_Generator, ComputeAccesses) const Orbit aToTrajectory = Orbit(sgp4, defaultEnvironment_.accessCelestialObjectWithName("Earth")); const Instant startInstant = Instant::Parse("2024-10-19 02:25:00.744.384", Scale::UTC); - const Instant endInstant = startInstant + Duration::Days(1.0); + const Instant endInstant = startInstant + Duration::Hours(6.0); const Interval interval = Interval::Closed(startInstant, endInstant); const Array LLAs = { @@ -898,57 +895,115 @@ TEST_F(OpenSpaceToolkit_Astrodynamics_Access_Generator, ComputeAccesses) LLA(Angle::Degrees(-52.9351), Angle::Degrees(-70.8713), Length::Meters(23)) }; - const ostk::mathematics::object::Interval azimuthInterval = - ostk::mathematics::object::Interval::Closed(0.0, 360.0); - const ostk::mathematics::object::Interval elevationInterval = - ostk::mathematics::object::Interval::Closed(0.0, 90.0); - const ostk::mathematics::object::Interval rangeInterval = - ostk::mathematics::object::Interval::Closed(0.0, 1.0e10); + // AER Interval visibility criterion, multiple targets - const VisibilityCriterion visibilityCriterion = - VisibilityCriterion::FromAERInterval(azimuthInterval, elevationInterval, rangeInterval); + { + const ostk::mathematics::object::Interval azimuthInterval = + ostk::mathematics::object::Interval::Closed(0.0, 360.0); + const ostk::mathematics::object::Interval elevationInterval = + ostk::mathematics::object::Interval::Closed(0.0, 90.0); + const ostk::mathematics::object::Interval rangeInterval = + ostk::mathematics::object::Interval::Closed(0.0, 1.0e10); + + const VisibilityCriterion visibilityCriterion = + VisibilityCriterion::FromAERInterval(azimuthInterval, elevationInterval, rangeInterval); + + Array accessTargets = LLAs.map( + [&visibilityCriterion, this](const LLA& lla) -> AccessTarget + { + return AccessTarget::FromLLA(visibilityCriterion, lla, defaultEarthSPtr_); + } + ); - Array accessTargets = LLAs.map( - [&visibilityCriterion, this](const LLA& lla) -> AccessTarget + const Array> accessesPerTarget = + defaultGenerator_.computeAccesses(interval, accessTargets, aToTrajectory); + + ASSERT_EQ(accessesPerTarget.getSize(), accessTargets.getSize()); + + for (Index i = 0; i < accessesPerTarget.getSize(); ++i) { - return AccessTarget::FromLLA(visibilityCriterion, lla, defaultEarthSPtr_); + const Array accesses = accessesPerTarget.at(i); + const AccessTarget groundTarget = accessTargets.at(i); + + const Array expectedAccesses = + defaultGenerator_.computeAccesses(interval, groundTarget, aToTrajectory); + + ASSERT_EQ(accesses.getSize(), expectedAccesses.getSize()); + + for (Index j = 0; j < accesses.getSize(); ++j) + { + const Access& access = accesses.at(j); + const Access& expectedAccess = expectedAccesses.at(j); + + EXPECT_TRUE(access.getAcquisitionOfSignal().isNear( + expectedAccess.getAcquisitionOfSignal(), Duration::Microseconds(1.0) + )) << access.getAcquisitionOfSignal().toString() + << " ~ " << expectedAccess.getAcquisitionOfSignal().toString(); + EXPECT_TRUE(access.getTimeOfClosestApproach().isNear( + expectedAccess.getTimeOfClosestApproach(), Duration::Microseconds(1.0) + )) << access.getTimeOfClosestApproach().toString() + << " ~ " << expectedAccess.getTimeOfClosestApproach().toString(); + EXPECT_TRUE( + access.getLossOfSignal().isNear(expectedAccess.getLossOfSignal(), Duration::Microseconds(1.0)) + ) << access.getLossOfSignal().toString() + << " ~ " << expectedAccess.getLossOfSignal().toString(); + EXPECT_TRUE(access.getDuration().isNear(expectedAccess.getDuration(), Duration::Microseconds(1.0))) + << access.getDuration().toString() << " ~ " << expectedAccess.getDuration().toString(); + } } - ); - - const Array> accessesPerTarget = - defaultGenerator_.computeAccesses(interval, accessTargets, aToTrajectory); + } - ASSERT_EQ(accessesPerTarget.getSize(), accessTargets.getSize()); + // Elevation Interval visibility criterion, multiple targets - for (Index i = 0; i < accessesPerTarget.getSize(); ++i) { - const Array accesses = accessesPerTarget.at(i); - const AccessTarget groundTarget = accessTargets.at(i); + const ostk::mathematics::object::Interval elevationInterval = + ostk::mathematics::object::Interval::Closed(0.0, 90.0); + + const VisibilityCriterion visibilityCriterion = + VisibilityCriterion::FromElevationInterval(elevationInterval); + + Array accessTargets = LLAs.map( + [&visibilityCriterion, this](const LLA& lla) -> AccessTarget + { + return AccessTarget::FromLLA(visibilityCriterion, lla, defaultEarthSPtr_); + } + ); - const Array expectedAccesses = - defaultGenerator_.computeAccesses(interval, groundTarget, aToTrajectory); + const Array> accessesPerTarget = + defaultGenerator_.computeAccesses(interval, accessTargets, aToTrajectory); - ASSERT_EQ(accesses.getSize(), expectedAccesses.getSize()); + ASSERT_EQ(accessesPerTarget.getSize(), accessTargets.getSize()); - for (Index j = 0; j < accesses.getSize(); ++j) + for (Index i = 0; i < accessesPerTarget.getSize(); ++i) { - const Access& access = accesses.at(j); - const Access& expectedAccess = expectedAccesses.at(j); - - EXPECT_TRUE(access.getAcquisitionOfSignal().isNear( - expectedAccess.getAcquisitionOfSignal(), Duration::Microseconds(1.0) - )) << access.getAcquisitionOfSignal().toString() - << " ~ " << expectedAccess.getAcquisitionOfSignal().toString(); - EXPECT_TRUE(access.getTimeOfClosestApproach().isNear( - expectedAccess.getTimeOfClosestApproach(), Duration::Microseconds(1.0) - )) << access.getTimeOfClosestApproach().toString() - << " ~ " << expectedAccess.getTimeOfClosestApproach().toString(); - EXPECT_TRUE( - access.getLossOfSignal().isNear(expectedAccess.getLossOfSignal(), Duration::Microseconds(1.0)) - ) << access.getLossOfSignal().toString() - << " ~ " << expectedAccess.getLossOfSignal().toString(); - EXPECT_TRUE(access.getDuration().isNear(expectedAccess.getDuration(), Duration::Microseconds(1.0))) - << access.getDuration().toString() << " ~ " << expectedAccess.getDuration().toString(); + const Array accesses = accessesPerTarget.at(i); + const AccessTarget groundTarget = accessTargets.at(i); + + const Array expectedAccesses = + defaultGenerator_.computeAccesses(interval, groundTarget, aToTrajectory); + + ASSERT_EQ(accesses.getSize(), expectedAccesses.getSize()); + + for (Index j = 0; j < accesses.getSize(); ++j) + { + const Access& access = accesses.at(j); + const Access& expectedAccess = expectedAccesses.at(j); + + EXPECT_TRUE(access.getAcquisitionOfSignal().isNear( + expectedAccess.getAcquisitionOfSignal(), Duration::Microseconds(1.0) + )) << access.getAcquisitionOfSignal().toString() + << " ~ " << expectedAccess.getAcquisitionOfSignal().toString(); + EXPECT_TRUE(access.getTimeOfClosestApproach().isNear( + expectedAccess.getTimeOfClosestApproach(), Duration::Microseconds(1.0) + )) << access.getTimeOfClosestApproach().toString() + << " ~ " << expectedAccess.getTimeOfClosestApproach().toString(); + EXPECT_TRUE( + access.getLossOfSignal().isNear(expectedAccess.getLossOfSignal(), Duration::Microseconds(1.0)) + ) << access.getLossOfSignal().toString() + << " ~ " << expectedAccess.getLossOfSignal().toString(); + EXPECT_TRUE(access.getDuration().isNear(expectedAccess.getDuration(), Duration::Microseconds(1.0))) + << access.getDuration().toString() << " ~ " << expectedAccess.getDuration().toString(); + } } } } diff --git a/test/OpenSpaceToolkit/Astrodynamics/Access/VisibilityCriterion.test.cpp b/test/OpenSpaceToolkit/Astrodynamics/Access/VisibilityCriterion.test.cpp index d059c044f..c4e4694ec 100644 --- a/test/OpenSpaceToolkit/Astrodynamics/Access/VisibilityCriterion.test.cpp +++ b/test/OpenSpaceToolkit/Astrodynamics/Access/VisibilityCriterion.test.cpp @@ -35,7 +35,7 @@ class OpenSpaceToolkit_Astrodynamics_Access_VisibilityCriterion : public ::testi TEST_F(OpenSpaceToolkit_Astrodynamics_Access_VisibilityCriterion, Constructor) { - // Test FromAERInterval + // FromAERInterval { const Interval azimuthInterval = Interval::Closed(0.0, 360.0); const Interval elevationInterval = Interval::Closed(0.0, 90.0); @@ -43,9 +43,66 @@ TEST_F(OpenSpaceToolkit_Astrodynamics_Access_VisibilityCriterion, Constructor) EXPECT_NO_THROW(VisibilityCriterion visibilityCriterion = VisibilityCriterion::FromAERInterval(azimuthInterval, elevationInterval, rangeInterval);); + + // Undefined + + { + EXPECT_THROW( + VisibilityCriterion::FromAERInterval(Interval::Undefined(), elevationInterval, rangeInterval), + ostk::core::error::runtime::Undefined + ); + EXPECT_THROW( + VisibilityCriterion::FromAERInterval(azimuthInterval, Interval::Undefined(), rangeInterval), + ostk::core::error::runtime::Undefined + ); + EXPECT_THROW( + VisibilityCriterion::FromAERInterval(azimuthInterval, elevationInterval, Interval::Undefined()), + ostk::core::error::runtime::Undefined + ); + } + + // incorrect bounds + { + { + EXPECT_THROW( + VisibilityCriterion::FromAERInterval( + ostk::mathematics::object::Interval::Closed(-1.0, 350.0), elevationInterval, rangeInterval + ), + ostk::core::error::RuntimeError + ); + EXPECT_THROW( + VisibilityCriterion::FromAERInterval( + ostk::mathematics::object::Interval::Closed(0.0, 360.1), elevationInterval, rangeInterval + ), + ostk::core::error::RuntimeError + ); + } + { + EXPECT_THROW( + VisibilityCriterion::FromAERInterval( + azimuthInterval, ostk::mathematics::object::Interval::Closed(-91.0, 0.0), rangeInterval + ), + ostk::core::error::RuntimeError + ); + EXPECT_THROW( + VisibilityCriterion::FromAERInterval( + azimuthInterval, ostk::mathematics::object::Interval::Closed(-45.0, 91.0), rangeInterval + ), + ostk::core::error::RuntimeError + ); + } + { + EXPECT_THROW( + VisibilityCriterion::FromAERInterval( + azimuthInterval, elevationInterval, ostk::mathematics::object::Interval::Closed(-1.0, 5.0) + ), + ostk::core::error::RuntimeError + ); + } + } } - // Test FromAERMask + // FromAERMask { const Map azimuthElevationMask = {{0.0, 10.0}, {90.0, 15.0}, {180.0, 20.0}}; const Interval rangeInterval = Interval::Closed(0.0, 1e6); @@ -54,13 +111,13 @@ TEST_F(OpenSpaceToolkit_Astrodynamics_Access_VisibilityCriterion, Constructor) VisibilityCriterion::FromAERMask(azimuthElevationMask, rangeInterval);); } - // Test FromLineOfSight + // FromLineOfSight { EXPECT_NO_THROW(VisibilityCriterion visibilityCriterion = VisibilityCriterion::FromLineOfSight(defaultEnvironment_);); } - // Test FromElevationInterval + // FromElevationInterval { const Interval elevationInterval = Interval::Closed(0.0, 90.0); From 0b8caf016d7ddac2ece065800cfe0c6e25918aea Mon Sep 17 00:00:00 2001 From: vishwa shah Date: Sat, 4 Jan 2025 23:02:24 +0000 Subject: [PATCH 19/22] feat: fix tests --- .../test/access/test_visibility_criterion.py | 19 ++++++++++++------- .../solvers/test_temporal_condition_solver.py | 10 +++++++++- bindings/python/test/test_display.py | 14 ++++++++++---- 3 files changed, 31 insertions(+), 12 deletions(-) diff --git a/bindings/python/test/access/test_visibility_criterion.py b/bindings/python/test/access/test_visibility_criterion.py index b69df963e..513bb9de9 100644 --- a/bindings/python/test/access/test_visibility_criterion.py +++ b/bindings/python/test/access/test_visibility_criterion.py @@ -9,7 +9,6 @@ from ostk.physics.time import Instant from ostk.physics.time import DateTime from ostk.physics.time import Scale -from ostk.physics.coordinate.spherical import AER from ostk.astrodynamics.access import VisibilityCriterion @@ -136,11 +135,15 @@ def test_aer_interval_is_satisfied( aer_interval = VisibilityCriterion.AERInterval( azimuth_interval, elevation_interval, range_interval ) - aer = AER(azimuth=np.pi / 4, elevation=np.pi / 8, range=5e6) - assert aer_interval.is_satisfied(aer) is True + assert ( + aer_interval.is_satisfied(azimuth=np.pi / 4, elevation=np.pi / 8, range=5e6) + is True + ) - aer_invalid = AER(azimuth=np.pi, elevation=np.pi / 2, range=1e8) - assert aer_interval.is_satisfied(aer_invalid) is False + assert ( + aer_interval.is_satisfied(azimuth=np.pi, elevation=np.pi / 2, range=1e8) + is False + ) def test_elevation_interval_is_satisfied( self, @@ -159,8 +162,10 @@ def test_line_of_sight_is_satisfied( ): line_of_sight = VisibilityCriterion.LineOfSight(environment) instant = Instant.now() - from_position = np.array([7000e3, 0.0, 0.0]) # 7000 km altitude - to_position = np.array([0.0, 7000e3, 0.0]) # 7000 km altitude + + from_position = np.array([7000e3, 0.0, 0.0]) + to_position = np.array([7005e3, 0.0, 0.0]) + assert line_of_sight.is_satisfied(instant, from_position, to_position) is True def test_visibility_criterion_type_checks( diff --git a/bindings/python/test/solvers/test_temporal_condition_solver.py b/bindings/python/test/solvers/test_temporal_condition_solver.py index dd78db82b..efc052eb5 100644 --- a/bindings/python/test/solvers/test_temporal_condition_solver.py +++ b/bindings/python/test/solvers/test_temporal_condition_solver.py @@ -16,6 +16,8 @@ from ostk.astrodynamics.trajectory.orbit.model.kepler import COE from ostk.astrodynamics.access import Generator from ostk.astrodynamics.solver import TemporalConditionSolver +from ostk.astrodynamics.access import AccessTarget +from ostk.astrodynamics.access import VisibilityCriterion @pytest.fixture @@ -141,9 +143,15 @@ def test_solve_success_using_access_generator( celestial_object=earth, ) + visibility_criterion: VisibilityCriterion = ( + VisibilityCriterion.from_line_of_sight(environment) + ) + + access_target = AccessTarget.from_trajectory(visibility_criterion, trajectory) + solution: list[Interval] = temporal_condition_solver.solve( condition=generator.get_condition_function( - from_trajectory=trajectory, + access_target=access_target, to_trajectory=trajectory, ), interval=interval, diff --git a/bindings/python/test/test_display.py b/bindings/python/test/test_display.py index 5de0b8ab1..1546f23f9 100644 --- a/bindings/python/test/test_display.py +++ b/bindings/python/test/test_display.py @@ -15,12 +15,13 @@ from ostk.physics.unit import Length from ostk.physics.unit import Angle -from ostk.astrodynamics import Trajectory from ostk.astrodynamics import display from ostk.astrodynamics.access import Generator as AccessGenerator from ostk.astrodynamics.trajectory import Orbit from ostk.astrodynamics.trajectory.orbit.model import SGP4 from ostk.astrodynamics.trajectory.orbit.model.sgp4 import TLE +from ostk.astrodynamics.access import AccessTarget +from ostk.astrodynamics.access import VisibilityCriterion class TestDisplay: @@ -52,7 +53,12 @@ def test_accesses_plot(self): environment: Environment = Environment.default() earth: Celestial = environment.access_celestial_object_with_name("Earth") - ground_station_trajectory: Trajectory = Trajectory.position( + visibility_criterion: VisibilityCriterion = ( + VisibilityCriterion.from_line_of_sight(environment) + ) + + access_target: AccessTarget = AccessTarget.from_position( + visibility_criterion, Position.meters( ground_station_lla.to_cartesian( earth.get_equatorial_radius(), @@ -76,7 +82,7 @@ def test_accesses_plot(self): accesses_1 = generator.compute_accesses( interval=search_interval, - from_trajectory=ground_station_trajectory, + access_target=access_target, to_trajectory=orbit_1, ) @@ -84,7 +90,7 @@ def test_accesses_plot(self): accesses_2 = generator.compute_accesses( interval=search_interval, - from_trajectory=ground_station_trajectory, + access_target=access_target, to_trajectory=orbit_2, ) From a107f58b4b037a1892081df263713cde17ca1fde Mon Sep 17 00:00:00 2001 From: vishwa shah Date: Mon, 6 Jan 2025 17:54:22 +0000 Subject: [PATCH 20/22] feat: add more tests --- .../Astrodynamics/Access/Generator.cpp | 4 - .../Access/VisibilityCriterion.test.cpp | 168 ++++++++++++++++++ 2 files changed, 168 insertions(+), 4 deletions(-) diff --git a/src/OpenSpaceToolkit/Astrodynamics/Access/Generator.cpp b/src/OpenSpaceToolkit/Astrodynamics/Access/Generator.cpp index fd420273c..72e4e82b1 100644 --- a/src/OpenSpaceToolkit/Astrodynamics/Access/Generator.cpp +++ b/src/OpenSpaceToolkit/Astrodynamics/Access/Generator.cpp @@ -135,10 +135,6 @@ AccessTarget::AccessTarget( visibilityCriterion_(aVisibilityCriterion), trajectory_(aTrajectory) { - if (!aTrajectory.isDefined()) - { - throw ostk::core::error::runtime::Undefined("Trajectory"); - } } Generator::Generator( diff --git a/test/OpenSpaceToolkit/Astrodynamics/Access/VisibilityCriterion.test.cpp b/test/OpenSpaceToolkit/Astrodynamics/Access/VisibilityCriterion.test.cpp index c4e4694ec..234c1a481 100644 --- a/test/OpenSpaceToolkit/Astrodynamics/Access/VisibilityCriterion.test.cpp +++ b/test/OpenSpaceToolkit/Astrodynamics/Access/VisibilityCriterion.test.cpp @@ -109,6 +109,54 @@ TEST_F(OpenSpaceToolkit_Astrodynamics_Access_VisibilityCriterion, Constructor) EXPECT_NO_THROW(VisibilityCriterion visibilityCriterion = VisibilityCriterion::FromAERMask(azimuthElevationMask, rangeInterval);); + + // Incorrect bounds + { + // empty map + { + const Map azimuthElevationMask = {}; + EXPECT_THROW( + VisibilityCriterion::FromAERMask(azimuthElevationMask, Interval::Undefined()), + ostk::core::error::runtime::Undefined + ); + } + + // azimuth key < 0.0 for first key + { + const Map azimuthElevationMask = {{-1.0, 10.0}, {90.0, 15.0}, {180.0, 20.0}}; + EXPECT_THROW( + VisibilityCriterion::FromAERMask(azimuthElevationMask, rangeInterval), + ostk::core::error::RuntimeError + ); + } + + // azimuth key > 360.0 for last key + { + const Map azimuthElevationMask = {{0.0, 10.0}, {90.0, 15.0}, {361.0, 20.0}}; + EXPECT_THROW( + VisibilityCriterion::FromAERMask(azimuthElevationMask, rangeInterval), + ostk::core::error::RuntimeError + ); + } + + // elevation greater than 90 for a value + { + const Map azimuthElevationMask = {{0.0, 10.0}, {90.0, 15.0}, {180.0, 91.0}}; + EXPECT_THROW( + VisibilityCriterion::FromAERMask(azimuthElevationMask, rangeInterval), + ostk::core::error::RuntimeError + ); + } + + // lower bound of range < 0.0 + { + const Map azimuthElevationMask = {{0.0, 10.0}, {90.0, 15.0}, {180.0, 20.0}}; + EXPECT_THROW( + VisibilityCriterion::FromAERMask(azimuthElevationMask, Interval::Closed(-1.0, 1e6)), + ostk::core::error::RuntimeError + ); + } + } } // FromLineOfSight @@ -123,6 +171,126 @@ TEST_F(OpenSpaceToolkit_Astrodynamics_Access_VisibilityCriterion, Constructor) EXPECT_NO_THROW(VisibilityCriterion visibilityCriterion = VisibilityCriterion::FromElevationInterval(elevationInterval);); + + // Incorrect bounds + { + EXPECT_THROW( + VisibilityCriterion::FromElevationInterval(ostk::mathematics::object::Interval::Closed(-91.0, 0.0) + ), + ostk::core::error::RuntimeError + ); + + EXPECT_THROW( + VisibilityCriterion::FromElevationInterval( + ostk::mathematics::object::Interval::Closed(-45.0, 91.0) + ), + ostk::core::error::RuntimeError + ); + } + } +} + +TEST_F(OpenSpaceToolkit_Astrodynamics_Access_VisibilityCriterion, EqualToOperator) +{ + // AERInterval + { + const Interval azimuthInterval = Interval::Closed(0.0, 360.0); + const Interval elevationInterval = Interval::Closed(0.0, 90.0); + const Interval rangeInterval = Interval::Closed(0.0, 1e6); + + const VisibilityCriterion visibilityCriterion1 = + VisibilityCriterion::FromAERInterval(azimuthInterval, elevationInterval, rangeInterval); + const VisibilityCriterion visibilityCriterion2 = + VisibilityCriterion::FromAERInterval(azimuthInterval, elevationInterval, rangeInterval); + + EXPECT_TRUE(visibilityCriterion1 == visibilityCriterion2); + } + + // AERMask + { + const Map azimuthElevationMask = {{0.0, 10.0}, {90.0, 15.0}, {180.0, 20.0}}; + const Interval rangeInterval = Interval::Closed(0.0, 1e6); + + const VisibilityCriterion visibilityCriterion1 = + VisibilityCriterion::FromAERMask(azimuthElevationMask, rangeInterval); + const VisibilityCriterion visibilityCriterion2 = + VisibilityCriterion::FromAERMask(azimuthElevationMask, rangeInterval); + + EXPECT_TRUE(visibilityCriterion1 == visibilityCriterion2); + } + + // LineOfSight + { + const VisibilityCriterion visibilityCriterion1 = VisibilityCriterion::FromLineOfSight(defaultEnvironment_); + const VisibilityCriterion visibilityCriterion2 = VisibilityCriterion::FromLineOfSight(defaultEnvironment_); + + EXPECT_TRUE(visibilityCriterion1 == visibilityCriterion2); + } + + // ElevationInterval + { + const Interval elevationInterval = Interval::Closed(0.0, 90.0); + + const VisibilityCriterion visibilityCriterion1 = VisibilityCriterion::FromElevationInterval(elevationInterval); + const VisibilityCriterion visibilityCriterion2 = VisibilityCriterion::FromElevationInterval(elevationInterval); + + EXPECT_TRUE(visibilityCriterion1 == visibilityCriterion2); + } +} + +TEST_F(OpenSpaceToolkit_Astrodynamics_Access_VisibilityCriterion, NotEqualToOperator) +{ + // AERInterval + { + const Interval azimuthInterval1 = Interval::Closed(0.0, 360.0); + const Interval elevationInterval1 = Interval::Closed(0.0, 90.0); + const Interval rangeInterval1 = Interval::Closed(0.0, 1e6); + + const Interval azimuthInterval2 = Interval::Closed(0.0, 360.0); + const Interval elevationInterval2 = Interval::Closed(0.0, 90.0); + const Interval rangeInterval2 = Interval::Closed(0.0, 1e6); + + const VisibilityCriterion visibilityCriterion1 = + VisibilityCriterion::FromAERInterval(azimuthInterval1, elevationInterval1, rangeInterval1); + const VisibilityCriterion visibilityCriterion2 = + VisibilityCriterion::FromAERInterval(azimuthInterval2, elevationInterval2, rangeInterval2); + + EXPECT_TRUE(visibilityCriterion1 != visibilityCriterion2); + } + + // AERMask + { + const Map azimuthElevationMask1 = {{0.0, 10.0}, {90.0, 15.0}, {180.0, 20.0}}; + const Interval rangeInterval1 = Interval::Closed(0.0, 1e6); + + const Map azimuthElevationMask2 = {{0.0, 10.0}, {90.0, 15.0}, {180.0, 20.0}}; + const Interval rangeInterval2 = Interval::Closed(0.0, 1e6); + + const VisibilityCriterion visibilityCriterion1 = + VisibilityCriterion::FromAERMask(azimuthElevationMask1, rangeInterval1); + const VisibilityCriterion visibilityCriterion2 = + VisibilityCriterion::FromAERMask(azimuthElevationMask2, rangeInterval2); + + EXPECT_TRUE(visibilityCriterion1 != visibilityCriterion2); + } + + // LineOfSight + { + const VisibilityCriterion visibilityCriterion1 = VisibilityCriterion::FromLineOfSight(defaultEnvironment_); + const VisibilityCriterion visibilityCriterion2 = VisibilityCriterion::FromLineOfSight(defaultEnvironment_); + + EXPECT_TRUE(visibilityCriterion1 != visibilityCriterion2); + } + + // ElevationInterval + { + const Interval elevationInterval1 = Interval::Closed(0.0, 90.0); + const Interval elevationInterval2 = Interval::Closed(0.0, 90.0); + + const VisibilityCriterion visibilityCriterion1 = VisibilityCriterion::FromElevationInterval(elevationInterval1); + const VisibilityCriterion visibilityCriterion2 = VisibilityCriterion::FromElevationInterval(elevationInterval2); + + EXPECT_TRUE(visibilityCriterion1 != visibilityCriterion2); } } From f2b7ff6a5e3516c52f7ae05a1e75ab1e6aaeb842 Mon Sep 17 00:00:00 2001 From: vishwa shah Date: Mon, 6 Jan 2025 19:53:30 +0000 Subject: [PATCH 21/22] fix: tests --- .../Access/VisibilityCriterion.test.cpp | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/test/OpenSpaceToolkit/Astrodynamics/Access/VisibilityCriterion.test.cpp b/test/OpenSpaceToolkit/Astrodynamics/Access/VisibilityCriterion.test.cpp index 234c1a481..a85a2ecb7 100644 --- a/test/OpenSpaceToolkit/Astrodynamics/Access/VisibilityCriterion.test.cpp +++ b/test/OpenSpaceToolkit/Astrodynamics/Access/VisibilityCriterion.test.cpp @@ -104,14 +104,17 @@ TEST_F(OpenSpaceToolkit_Astrodynamics_Access_VisibilityCriterion, Constructor) // FromAERMask { - const Map azimuthElevationMask = {{0.0, 10.0}, {90.0, 15.0}, {180.0, 20.0}}; - const Interval rangeInterval = Interval::Closed(0.0, 1e6); + { + const Map azimuthElevationMask = {{0.0, 10.0}, {90.0, 15.0}, {180.0, 20.0}}; + const Interval rangeInterval = Interval::Closed(0.0, 1e6); - EXPECT_NO_THROW(VisibilityCriterion visibilityCriterion = - VisibilityCriterion::FromAERMask(azimuthElevationMask, rangeInterval);); + EXPECT_NO_THROW(VisibilityCriterion visibilityCriterion = + VisibilityCriterion::FromAERMask(azimuthElevationMask, rangeInterval);); + } // Incorrect bounds { + const Interval rangeInterval = Interval::Closed(0.0, 1e6); // empty map { const Map azimuthElevationMask = {}; @@ -255,7 +258,7 @@ TEST_F(OpenSpaceToolkit_Astrodynamics_Access_VisibilityCriterion, NotEqualToOper const VisibilityCriterion visibilityCriterion2 = VisibilityCriterion::FromAERInterval(azimuthInterval2, elevationInterval2, rangeInterval2); - EXPECT_TRUE(visibilityCriterion1 != visibilityCriterion2); + EXPECT_FALSE(visibilityCriterion1 != visibilityCriterion2); } // AERMask @@ -271,7 +274,7 @@ TEST_F(OpenSpaceToolkit_Astrodynamics_Access_VisibilityCriterion, NotEqualToOper const VisibilityCriterion visibilityCriterion2 = VisibilityCriterion::FromAERMask(azimuthElevationMask2, rangeInterval2); - EXPECT_TRUE(visibilityCriterion1 != visibilityCriterion2); + EXPECT_FALSE(visibilityCriterion1 != visibilityCriterion2); } // LineOfSight @@ -279,7 +282,7 @@ TEST_F(OpenSpaceToolkit_Astrodynamics_Access_VisibilityCriterion, NotEqualToOper const VisibilityCriterion visibilityCriterion1 = VisibilityCriterion::FromLineOfSight(defaultEnvironment_); const VisibilityCriterion visibilityCriterion2 = VisibilityCriterion::FromLineOfSight(defaultEnvironment_); - EXPECT_TRUE(visibilityCriterion1 != visibilityCriterion2); + EXPECT_FALSE(visibilityCriterion1 != visibilityCriterion2); } // ElevationInterval @@ -290,7 +293,7 @@ TEST_F(OpenSpaceToolkit_Astrodynamics_Access_VisibilityCriterion, NotEqualToOper const VisibilityCriterion visibilityCriterion1 = VisibilityCriterion::FromElevationInterval(elevationInterval1); const VisibilityCriterion visibilityCriterion2 = VisibilityCriterion::FromElevationInterval(elevationInterval2); - EXPECT_TRUE(visibilityCriterion1 != visibilityCriterion2); + EXPECT_FALSE(visibilityCriterion1 != visibilityCriterion2); } } From a19d8353e3320d44e0456641ee7eeb4fa432092b Mon Sep 17 00:00:00 2001 From: vishwa shah Date: Mon, 6 Jan 2025 21:15:08 +0000 Subject: [PATCH 22/22] chore: fix bug in interval type --- .../Astrodynamics/Access/VisibilityCriterion/AERInterval.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/OpenSpaceToolkit/Astrodynamics/Access/VisibilityCriterion/AERInterval.cpp b/src/OpenSpaceToolkit/Astrodynamics/Access/VisibilityCriterion/AERInterval.cpp index aff94f3c8..874fb7e91 100644 --- a/src/OpenSpaceToolkit/Astrodynamics/Access/VisibilityCriterion/AERInterval.cpp +++ b/src/OpenSpaceToolkit/Astrodynamics/Access/VisibilityCriterion/AERInterval.cpp @@ -56,7 +56,7 @@ VisibilityCriterion::AERInterval::AERInterval( elevation = Interval( anElevationInterval.getLowerBound() * M_PI / 180.0, anElevationInterval.getUpperBound() * M_PI / 180.0, - anAzimuthInterval.getType() + anElevationInterval.getType() ); }