Repository: open-space-collective/open-space-toolkit-astrodynamics
Pull request #555: feat: target scan trajectory model
Author: login: vishwa2710
Created: 2025-05-15T04:37:26Z | Merged: 2025-05-20T05:12:31Z

similar to the nadir trajectory model, but for line scanning

<!-- This is an auto-generated comment: release notes by coderabbit.ai -->
## Summary by CodeRabbit

- **New Features**
  - Introduced a new TargetScan trajectory model for simulating scanning motion between two locations on a celestial body.
  - Added support for creating TargetScan trajectories using either start/end times or ground speed.
  - Exposed TargetScan functionality and methods to Python bindings.

- **Deprecations**
  - Marked existing GroundStrip trajectory methods as deprecated in favor of the new TargetScan model.

- **Tests**
  - Added comprehensive unit tests for the TargetScan model in both C++ and Python to ensure correct behavior and reliability.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->


Diff:
diff --git a/bindings/python/src/OpenSpaceToolkitAstrodynamicsPy/Trajectory/Model.cpp b/bindings/python/src/OpenSpaceToolkitAstrodynamicsPy/Trajectory/Model.cpp
index 7e7038504..613488ad5 100644
--- a/bindings/python/src/OpenSpaceToolkitAstrodynamicsPy/Trajectory/Model.cpp
+++ b/bindings/python/src/OpenSpaceToolkitAstrodynamicsPy/Trajectory/Model.cpp
@@ -4,6 +4,7 @@
 
 #include <OpenSpaceToolkitAstrodynamicsPy/Trajectory/Model/Nadir.cpp>
 #include <OpenSpaceToolkitAstrodynamicsPy/Trajectory/Model/Tabulated.cpp>
+#include <OpenSpaceToolkitAstrodynamicsPy/Trajectory/Model/TargetScan.cpp>
 
 inline void OpenSpaceToolkitAstrodynamicsPy_Trajectory_Model(pybind11::module &aModule)
 {
@@ -87,4 +88,5 @@ inline void OpenSpaceToolkitAstrodynamicsPy_Trajectory_Model(pybind11::module &a
     // Add objects to python submodule
     OpenSpaceToolkitAstrodynamicsPy_Trajectory_Model_Nadir(model);
     OpenSpaceToolkitAstrodynamicsPy_Trajectory_Model_Tabulated(model);
+    OpenSpaceToolkitAstrodynamicsPy_Trajectory_Model_TargetScan(model);
 }
diff --git a/bindings/python/src/OpenSpaceToolkitAstrodynamicsPy/Trajectory/Model/TargetScan.cpp b/bindings/python/src/OpenSpaceToolkitAstrodynamicsPy/Trajectory/Model/TargetScan.cpp
new file mode 100644
index 000000000..bcae47a6d
--- /dev/null
+++ b/bindings/python/src/OpenSpaceToolkitAstrodynamicsPy/Trajectory/Model/TargetScan.cpp
@@ -0,0 +1,171 @@
+/// Apache License 2.0
+
+#include <OpenSpaceToolkit/Astrodynamics/Trajectory/Model/TargetScan.hpp>
+
+inline void OpenSpaceToolkitAstrodynamicsPy_Trajectory_Model_TargetScan(pybind11::module& aModule)
+{
+    using namespace pybind11;
+
+    using ostk::core::type::Shared;
+
+    using ostk::physics::coordinate::spherical::LLA;
+    using ostk::physics::environment::object::Celestial;
+    using ostk::physics::environment::object::celestial::Earth;
+    using ostk::physics::time::Duration;
+    using ostk::physics::time::Instant;
+
+    using ostk::astrodynamics::trajectory::model::TargetScan;
+    using ostk::astrodynamics::trajectory::State;
+
+    class_<TargetScan, ostk::astrodynamics::trajectory::Model>(
+        aModule,
+        "TargetScan",
+        R"doc(
+            TargetScan trajectory model.
+
+            This model represents a trajectory that scans between two target locations on a celestial body.
+        )doc"
+    )
+
+        .def(
+            init<const LLA&, const LLA&, const Instant&, const Instant&, const Celestial&, const Duration&>(),
+            R"doc(
+                Construct a `TargetScan` object.
+
+                Args:
+                    start_lla (LLA): The starting location.
+                    end_lla (LLA): The ending location.
+                    start_instant (Instant): The starting instant.
+                    end_instant (Instant): The ending instant.
+                    celestial (Celestial): The celestial body. Defaults to Earth.WGS84().
+                    step_size (Duration): The step size for the trajectory. Defaults to 1e-2 seconds.
+
+                Returns:
+                    TargetScan: The `TargetScan` object.
+            )doc",
+            arg("start_lla"),
+            arg("end_lla"),
+            arg("start_instant"),
+            arg("end_instant"),
+            arg("celestial") = Earth::WGS84(),
+            arg("step_size") = Duration::Seconds(1e-2)
+        )
+
+        .def(self == self)
+        .def(self != self)
+
+        .def("__str__", &(shiftToString<TargetScan>))
+        .def("__repr__", &(shiftToString<TargetScan>))
+
+        .def(
+            "is_defined",
+            &TargetScan::isDefined,
+            R"doc(
+                Check if the model is defined.
+
+                Returns:
+                    bool: True if the model is defined, False otherwise.
+            )doc"
+        )
+        .def(
+            "calculate_state_at",
+            &TargetScan::calculateStateAt,
+            R"doc(
+                Calculate the state at a given instant.
+
+                Args:
+                    instant (Instant): The instant.
+
+                Returns:
+                    State: The state at the given instant.
+            )doc",
+            arg("instant")
+        )
+        .def(
+            "get_start_lla",
+            &TargetScan::getStartLLA,
+            R"doc(
+                Get the starting LLA of the target scan.
+
+                Returns:
+                    LLA: The starting LLA.
+            )doc"
+        )
+        .def(
+            "get_end_lla",
+            &TargetScan::getEndLLA,
+            R"doc(
+                Get the ending LLA of the target scan.
+
+                Returns:
+                    LLA: The ending LLA.
+            )doc"
+        )
+        .def(
+            "get_start_instant",
+            &TargetScan::getStartInstant,
+            R"doc(
+                Get the starting instant of the target scan.
+
+                Returns:
+                    Instant: The starting instant.
+            )doc"
+        )
+        .def(
+            "get_end_instant",
+            &TargetScan::getEndInstant,
+            R"doc(
+                Get the ending instant of the target scan.
+
+                Returns:
+                    Instant: The ending instant.
+            )doc"
+        )
+        .def(
+            "get_celestial",
+            &TargetScan::getCelestial,
+            R"doc(
+                Get the celestial object of the target scan.
+
+                Returns:
+                    Celestial: The celestial object.
+            )doc"
+        )
+        .def(
+            "get_step_size",
+            &TargetScan::getStepSize,
+            R"doc(
+                Get the step size of the target scan.
+
+                Returns:
+                    Duration: The step size.
+            )doc"
+        )
+
+        .def_static(
+            "from_ground_speed",
+            &TargetScan::FromGroundSpeed,
+            R"doc(
+                Construct a `TargetScan` object from ground speed.
+
+                Args:
+                    start_lla (LLA): The starting location.
+                    end_lla (LLA): The ending location.
+                    ground_speed (Derived): The ground speed.
+                    start_instant (Instant): The starting instant.
+                    celestial (Celestial): The celestial body.
+                    step_size (Duration): The step size for the trajectory.
+
+                Returns:
+                    TargetScan: The `TargetScan` object.
+            )doc",
+            arg("start_lla"),
+            arg("end_lla"),
+            arg("ground_speed"),
+            arg("start_instant"),
+            arg("celestial") = Earth::WGS84(),
+            arg("step_size") = Duration::Seconds(1e-2)
+        )
+
+        ;
+}
diff --git a/bindings/python/test/Trajectory/Model/test_target_scan.py b/bindings/python/test/Trajectory/Model/test_target_scan.py
new file mode 100644
index 000000000..ca05eef06
--- /dev/null
+++ b/bindings/python/test/Trajectory/Model/test_target_scan.py
@@ -0,0 +1,126 @@
+# Apache License 2.0
+
+import pytest
+
+from ostk.physics.coordinate.spherical import LLA
+from ostk.physics.environment.object.celestial import Earth
+from ostk.physics.time import Instant
+from ostk.physics.time import DateTime
+from ostk.physics.time import Scale
+from ostk.physics.time import Duration
+from ostk.physics.unit import Derived
+from ostk.astrodynamics.trajectory.model import TargetScan
+from ostk.astrodynamics.trajectory import State
+
+
+@pytest.fixture
+def earth() -> Earth:
+    return Earth.WGS84()
+
+
+@pytest.fixture
+def start_lla() -> LLA:
+    return LLA.vector([0.0, 0.0, 0.0])
+
+
+@pytest.fixture
+def end_lla() -> LLA:
+    return LLA.vector([0.0, 1.0, 0.0])
+
+
+@pytest.fixture
+def start_instant() -> Instant:
+    return Instant.date_time(DateTime(2023, 1, 1, 0, 0, 0), Scale.UTC)
+
+
+@pytest.fixture
+def end_instant() -> Instant:
+    return Instant.date_time(DateTime(2023, 1, 1, 0, 10, 0), Scale.UTC)
+
+
+@pytest.fixture
+def target_scan(
+    earth: Earth,
+    start_lla: LLA,
+    end_lla: LLA,
+    start_instant: Instant,
+    end_instant: Instant,
+) -> TargetScan:
+    return TargetScan(
+        start_lla=start_lla,
+        end_lla=end_lla,
+        start_instant=start_instant,
+        end_instant=end_instant,
+        celestial=earth,
+    )
+
+
+class TestTargetScan:
+    def test_constructor(
+        self,
+        target_scan: TargetScan,
+    ):
+        assert target_scan.is_defined()
+
+    def test_calculate_state_at(
+        self,
+        target_scan: TargetScan,
+    ):
+        instant: Instant = Instant.date_time(DateTime(2023, 1, 1, 0, 5, 0), Scale.UTC)
+        state: State = target_scan.calculate_state_at(instant)
+        assert state is not None
+
+    def test_equality_operator(
+        self,
+        target_scan: TargetScan,
+    ):
+        assert target_scan == target_scan
+
+    def test_inequality_operator(
+        self,
+        target_scan: TargetScan,
+        start_lla: LLA,
+        end_lla: LLA,
+        start_instant: Instant,
+        earth: Earth,
+    ):
+        target_scan2 = TargetScan(
+            start_lla=start_lla,
+            end_lla=end_lla,
+            start_instant=start_instant,
+            end_instant=Instant.undefined(),
+            celestial=earth,
+        )
+        assert target_scan != target_scan2
+
+    def test_getters(
+        self,
+        target_scan: TargetScan,
+    ):
+        assert target_scan.get_start_lla() is not None
+        assert target_scan.get_end_lla() is not None
+        assert target_scan.get_start_instant() is not None
+        assert target_scan.get_end_instant() is not None
+        assert target_scan.get_celestial() is not None
+        assert target_scan.get_step_size() is not None
+
+    def test_from_ground_speed(
+        self,
+        start_lla: LLA,
+        end_lla: LLA,
+        start_instant: Instant,
+        earth: Earth,
+    ):
+        ground_speed: Derived = Derived(100.0, Derived.Unit.meter_per_second())
+        step_size: Duration = Duration.seconds(1.0)
+
+        target_scan = TargetScan.from_ground_speed(
+            start_lla=start_lla,
+            end_lla=end_lla,
+            ground_speed=ground_speed,
+            start_instant=start_instant,
+            celestial=earth,
+            step_size=step_size,
+        )
+
+        assert target_scan.is_defined()
diff --git a/include/OpenSpaceToolkit/Astrodynamics/Trajectory.hpp b/include/OpenSpaceToolkit/Astrodynamics/Trajectory.hpp
index cd6f33f6c..651ca3a3f 100644
--- a/include/OpenSpaceToolkit/Astrodynamics/Trajectory.hpp
+++ b/include/OpenSpaceToolkit/Astrodynamics/Trajectory.hpp
@@ -195,12 +195,14 @@ class Trajectory
     ///             duration);
     /// @endcode
     ///
+    /// @deprecated Use Trajectory::TargetScan instead.
     /// @param aStartLLA A start LLA
     /// @param anEndLLA An end LLA
     /// @param aGroundSpeed A ground speed
     /// @param aStartInstant A start instant
     /// @param aCelestial Celestial body
     /// @return GroundStrip trajectory
+    [[deprecated("Use Trajectory(TargetScan::FromGroundSpeed(...)) instead.")]]
     static Trajectory GroundStrip(
         const LLA& aStartLLA,
         const LLA& anEndLLA,
@@ -223,11 +225,13 @@ class Trajectory
     ///             Trajectory trajectory = Trajectory::GroundStrip(startLLA, endLLA, instants, earth);
     /// @endcode
     ///
+    /// @deprecated Use Trajectory::TargetScan instead.
     /// @param aStartLLA A start LLA
     /// @param anEndLLA An end LLA
     /// @param anInstantArray An array of instants
     /// @param aCelestial Celestial body
     /// @return GroundStrip trajectory
+    [[deprecated("Use Trajectory(TargetScan(...)) instead.")]]
     static Trajectory GroundStrip(
         const LLA& aStartLLA,
         const LLA& anEndLLA,
diff --git a/include/OpenSpaceToolkit/Astrodynamics/Trajectory/Model/TargetScan.hpp b/include/OpenSpaceToolkit/Astrodynamics/Trajectory/Model/TargetScan.hpp
new file mode 100644
index 000000000..99590a7b7
--- /dev/null
+++ b/include/OpenSpaceToolkit/Astrodynamics/Trajectory/Model/TargetScan.hpp
@@ -0,0 +1,262 @@
+/// Apache License 2.0
+
+#ifndef __OpenSpaceToolkit_Astrodynamics_Trajectory_Model_TargetScan__
+#define __OpenSpaceToolkit_Astrodynamics_Trajectory_Model_TargetScan__
+
+#include <OpenSpaceToolkit/Physics/Environment/Object/Celestial/Earth.hpp>
+#include <OpenSpaceToolkit/Physics/Time/Instant.hpp>
+#include <OpenSpaceToolkit/Physics/Time/Interval.hpp>
+
+#include <OpenSpaceToolkit/Astrodynamics/Trajectory/Model.hpp>
+#include <OpenSpaceToolkit/Astrodynamics/Trajectory/Orbit.hpp>
+#include <OpenSpaceToolkit/Astrodynamics/Trajectory/State.hpp>
+
+namespace ostk
+{
+namespace astrodynamics
+{
+namespace trajectory
+{
+namespace model
+{
+
+using ostk::physics::coordinate::Position;
+using ostk::physics::environment::object::celestial::Earth;
+using ostk::physics::time::Duration;
+using ostk::physics::time::Instant;
+using ostk::physics::time::Interval;
+
+using ostk::astrodynamics::trajectory::Model;
+using ostk::astrodynamics::trajectory::State;
+
+/// @brief Target scanning pointing trajectory model to represent the target scanning of a spacecraft
+class TargetScan : public virtual Model
+{
+   public:
+    /// @brief Constructor
+    ///
+    /// @code{.cpp}
+    ///              LLA startLLA = LLA::Vector({0.0, 0.0, 0.0});
+    ///              LLA endLLA = LLA::Vector({1.0, 1.0, 1.0});
+    ///              Instant startInstant = Instant::DateTime(DateTime::Parse("2020-01-01 00:00:00"), Scale::UTC);
+    ///              Instant endInstant = Instant::DateTime(DateTime::Parse("2020-01-01 00:00:10"), Scale::UTC);
+    ///              TargetScan targetScanModel(startLLA, endLLA, startInstant, endInstant);
+    /// @endcode
+    ///
+    /// @param aStartLLA The starting LLA of the target scan
+    /// @param anEndLLA The ending LLA of the target scan
+    /// @param aStartInstant The starting instant of the target scan
+    /// @param anEndInstant The ending instant of the target scan
+    /// @param aCelestialObject The celestial object (default: Earth::WGS84())
+    /// @param aStepSize The step size for the target scan (default: 1e-2 second)
+    TargetScan(
+        const LLA& aStartLLA,
+        const LLA& anEndLLA,
+        const Instant& aStartInstant,
+        const Instant& anEndInstant,
+        const Celestial& aCelestialObject = Earth::WGS84(),
+        const Duration& aStepSize = Duration::Seconds(1e-2)
+    );
+
+    /// @brief Clone the static model
+    ///
+    /// @code{.cpp}
+    ///              TargetScan targetScanModel = { ... };
+    ///              TargetScan* clonedModel = targetScanModel.clone();
+    /// @endcode
+    ///
+    /// @return A pointer to the cloned static model
+    virtual TargetScan* clone() const override;
+
+    /// @brief Get the starting LLA of the target scan
+    ///
+    /// @code{.cpp}
+    ///              TargetScan targetScanModel = { ... };
+    ///              LLA startLLA = targetScanModel.getStartLLA();
+    /// @endcode
+    ///
+    /// @return The starting LLA of the target scan
+    LLA getStartLLA() const;
+
+    /// @brief Get the ending LLA of the target scan
+    ///
+    /// @code{.cpp}
+    ///              TargetScan targetScanModel = { ... };
+    ///              LLA endLLA = targetScanModel.getEndLLA();
+    /// @endcode
+    ///
+    /// @return The ending LLA of the target scan
+    LLA getEndLLA() const;
+
+    /// @brief Get the starting instant of the target scan
+    ///
+    /// @code{.cpp}
+    ///              TargetScan targetScanModel = { ... };
+    ///              Instant startInstant = targetScanModel.getStartInstant();
+    /// @endcode
+    ///
+    /// @return The starting instant of the target scan
+    Instant getStartInstant() const;
+
+    /// @brief Get the ending instant of the target scan
+    ///
+    /// @code{.cpp}
+    ///              TargetScan targetScanModel = { ... };
+    ///              Instant endInstant = targetScanModel.getEndInstant();
+    /// @endcode
+    ///
+    /// @return The ending instant of the target scan
+    Instant getEndInstant() const;
+
+    /// @brief Get the celestial object of the target scan
+    ///
+    /// @code{.cpp}
+    ///              TargetScan targetScanModel = { ... };
+    ///              Celestial celestialObject = targetScanModel.getCelestial();
+    /// @endcode
+    ///
+    /// @return The celestial object of the target scan
+    Celestial getCelestial() const;
+
+    /// @brief Get the step size of the target scan
+    ///
+    /// @code{.cpp}
+    ///              TargetScan targetScanModel = { ... };
+    ///              Duration stepSize = targetScanModel.getStepSize();
+    /// @endcode
+    ///
+    /// @return The step size of the target scan
+    Duration getStepSize() const;
+
+    /// @brief Equality operator
+    ///
+    /// @code{.cpp}
+    ///              TargetScan targetScanModel1 = { ... };
+    ///              TargetScan targetScanModel2 = { ... };
+    ///              bool isEqual = (targetScanModel1 == targetScanModel2);
+    /// @endcode
+    ///
+    /// @param aTargetScanModel The static model to compare with
+    /// @return True if the models are equal, false otherwise
+    bool operator==(const TargetScan& aTargetScanModel) const;
+
+    /// @brief Inequality operator
+    ///
+    /// @code{.cpp}
+    ///              TargetScan targetScanModel1 = { ... };
+    ///              TargetScan targetScanModel2 = { ... };
+    ///              bool isNotEqual = (targetScanModel1 != targetScanModel2);
+    /// @endcode
+    ///
+    /// @param aTargetScanModel The static model to compare with
+    /// @return True if the models are not equal, false otherwise
+    bool operator!=(const TargetScan& aTargetScanModel) const;
+
+    /// @brief Output stream operator
+    ///
+    /// @code{.cpp}
+    ///              TargetScan targetScanModel = { ... };
+    ///              std::cout << targetScanModel;
+    /// @endcode
+    ///
+    /// @param anOutputStream The output stream
+    /// @param aTargetScanModel The static model to output
+    /// @return The output stream
+    friend std::ostream& operator<<(std::ostream& anOutputStream, const TargetScan& aTargetScanModel);
+
+    /// @brief Check if the static model is defined
+    ///
+    /// @code{.cpp}
+    ///              TargetScan targetScanModel = { ... };
+    ///              bool isDefined = targetScanModel.isDefined();
+    /// @endcode
+    ///
+    /// @return True if the model is defined, false otherwise
+    virtual bool isDefined() const override;
+
+    /// @brief Calculate the state at a given instant
+    ///
+    /// @code{.cpp}
+    ///              TargetScan targetScanModel = { ... };
+    ///              Instant instant = { ... };
+    ///              State state = targetScanModel.calculateStateAt(instant);
+    /// @endcode
+    ///
+    /// @param anInstant The instant at which to calculate the state
+    /// @return The state at the given instant
+    virtual State calculateStateAt(const Instant& anInstant) const override;
+
+    /// @brief Print the static model to an output stream
+    ///
+    /// @code{.cpp}
+    ///              TargetScan targetScanModel = { ... };
+    ///              targetScanModel.print(std::cout, true);
+    /// @endcode
+    ///
+    /// @param anOutputStream The output stream
+    /// @param displayDecorator If true, display decorator
+    virtual void print(std::ostream& anOutputStream, bool displayDecorator = true) const override;
+
+    /// @brief Static method to create a TargetScan from ground speed. Computes the end instant based on the ground
+    /// speed, and the distance between the start and end LLA.
+    ///
+    /// @code{.cpp}
+    ///              TargetScan targetScanModel = TargetScan::FromGroundSpeed(startLLA, endLLA, groundSpeed,
+    ///              startInstant);
+    /// @endcode
+    ///
+    /// @param aStartLLA The starting LLA of the target scan
+    /// @param anEndLLA The ending LLA of the target scan
+    /// @param aGroundSpeed The ground speed of the target scan
+    /// @param aStartInstant The starting instant of the target scan
+    /// @param aCelestialObject The celestial object (default: Earth::WGS84())
+    /// @param aStepSize The step size for the target scan (default: 1e-2 second)
+    static TargetScan FromGroundSpeed(
+        const LLA& aStartLLA,
+        const LLA& anEndLLA,
+        const Derived& aGroundSpeed,
+        const Instant& aStartInstant,
+        const Celestial& aCelestialObject = Earth::WGS84(),
+        const Duration& aStepSize = Duration::Seconds(1e-2)
+    );
+
+   protected:
+    /// @brief Equality operator for Model base class
+    ///
+    /// @code{.cpp}
+    ///              TargetScan targetScanModel = { ... };
+    ///              Model model = { ... };
+    ///              bool isEqual = (targetScanModel == model);
+    /// @endcode
+    ///
+    /// @param aModel The model to compare with
+    /// @return True if the models are equal, false otherwise
+    virtual bool operator==(const Model& aModel) const override;
+
+    /// @brief Inequality operator for Model base class
+    ///
+    /// @code{.cpp}
+    ///              TargetScan targetScanModel = { ... };
+    ///              Model model = { ... };
+    ///              bool isNotEqual = (targetScanModel != model);
+    /// @endcode
+    ///
+    /// @param aModel The model to compare with
+    /// @return True if the models are not equal, false otherwise
+    virtual bool operator!=(const Model& aModel) const override;
+
+   private:
+    LLA startLLA_;
+    LLA endLLA_;
+    Instant startInstant_;
+    Instant endInstant_;
+    Shared<const Celestial> celestialSPtr_;
+    Duration stepSize_;
+};
+
+}  // namespace model
+}  // namespace trajectory
+}  // namespace astrodynamics
+}  // namespace ostk
+
+#endif
diff --git a/src/OpenSpaceToolkit/Astrodynamics/Trajectory.cpp b/src/OpenSpaceToolkit/Astrodynamics/Trajectory.cpp
index f468bf5dc..a998b6f5f 100644
--- a/src/OpenSpaceToolkit/Astrodynamics/Trajectory.cpp
+++ b/src/OpenSpaceToolkit/Astrodynamics/Trajectory.cpp
@@ -13,6 +13,7 @@
 #include <OpenSpaceToolkit/Astrodynamics/Trajectory/Model/Nadir.hpp>
 #include <OpenSpaceToolkit/Astrodynamics/Trajectory/Model/Static.hpp>
 #include <OpenSpaceToolkit/Astrodynamics/Trajectory/Model/Tabulated.hpp>
+#include <OpenSpaceToolkit/Astrodynamics/Trajectory/Model/TargetScan.hpp>
 #include <OpenSpaceToolkit/Astrodynamics/Trajectory/Orbit.hpp>
 
 namespace ostk
@@ -31,6 +32,8 @@ using ostk::physics::time::Duration;
 using ostk::physics::unit::Length;
 
 using ostk::astrodynamics::trajectory::model::Nadir;
+using TargetScanModel = ostk::astrodynamics::trajectory::model::TargetScan;
+using ostk::astrodynamics::trajectory::model::Static;
 using ostk::astrodynamics::trajectory::model::Tabulated;
 using ostk::astrodynamics::trajectory::Orbit;
 
@@ -139,8 +142,6 @@ Trajectory Trajectory::Undefined()
 
 Trajectory Trajectory::Position(const physics::coordinate::Position& aPosition)
 {
-    using ostk::astrodynamics::trajectory::model::Static;
-
     if (!aPosition.isDefined())
     {
         throw ostk::core::error::runtime::Undefined("Position");
diff --git a/src/OpenSpaceToolkit/Astrodynamics/Trajectory/Model/TargetScan.cpp b/src/OpenSpaceToolkit/Astrodynamics/Trajectory/Model/TargetScan.cpp
new file mode 100644
index 000000000..bf6aefcbe
--- /dev/null
+++ b/src/OpenSpaceToolkit/Astrodynamics/Trajectory/Model/TargetScan.cpp
@@ -0,0 +1,297 @@
+/// Apache License 2.0
+
+#include <OpenSpaceToolkit/Core/Error.hpp>
+#include <OpenSpaceToolkit/Core/Utility.hpp>
+
+#include <OpenSpaceToolkit/Mathematics/CurveFitting/Interpolator.hpp>
+#include <OpenSpaceToolkit/Mathematics/Object/Vector.hpp>
+
+#include <OpenSpaceToolkit/Physics/Unit/Derived.hpp>
+
+#include <OpenSpaceToolkit/Astrodynamics/Trajectory/Model/TargetScan.hpp>
+
+namespace ostk
+{
+namespace astrodynamics
+{
+namespace trajectory
+{
+namespace model
+{
+
+using ostk::mathematics::curvefitting::Interpolator;
+using ostk::mathematics::object::MatrixXd;
+using ostk::mathematics::object::Vector3d;
+using ostk::mathematics::object::VectorXd;
+
+using ostk::physics::coordinate::Position;
+using ostk::physics::unit::Derived;
+
+TargetScan::TargetScan(
+    const LLA& aStartLLA,
+    const LLA& anEndLLA,
+    const Instant& aStartInstant,
+    const Instant& anEndInstant,
+    const Celestial& aCelestial,
+    const Duration& aStepSize
+)
+    : Model(),
+      startLLA_(aStartLLA),
+      endLLA_(anEndLLA),
+      startInstant_(aStartInstant),
+      endInstant_(anEndInstant),
+      celestialSPtr_(std::make_shared<const Celestial>(aCelestial)),
+      stepSize_(aStepSize)
+{
+}
+
+TargetScan* TargetScan::clone() const
+{
+    return new TargetScan(*this);
+}
+
+bool TargetScan::operator==(const TargetScan& aTargetScanModel) const
+{
+    if ((!this->isDefined()) || (!aTargetScanModel.isDefined()))
+    {
+        return false;
+    }
+
+    return startLLA_ == aTargetScanModel.startLLA_ && endLLA_ == aTargetScanModel.endLLA_ &&
+           startInstant_ == aTargetScanModel.startInstant_ && endInstant_ == aTargetScanModel.endInstant_ &&
+           celestialSPtr_ == aTargetScanModel.celestialSPtr_ && stepSize_ == aTargetScanModel.stepSize_;
+}
+
+bool TargetScan::operator!=(const TargetScan& aTargetScanModel) const
+{
+    return !((*this) == aTargetScanModel);
+}
+
+std::ostream& operator<<(std::ostream& anOutputStream, const TargetScan& aTargetScanModel)
+{
+    aTargetScanModel.print(anOutputStream);
+
+    return anOutputStream;
+}
+
+bool TargetScan::isDefined() const
+{
+    return startLLA_.isDefined() && endLLA_.isDefined() && startInstant_.isDefined() && endInstant_.isDefined() &&
+           celestialSPtr_ != nullptr && celestialSPtr_->isDefined() && stepSize_.isDefined();
+}
+
+State TargetScan::calculateStateAt(const Instant& anInstant) const
+{
+    if (!anInstant.isDefined())
+    {
+        throw ostk::core::error::runtime::Undefined("Instant");
+    }
+
+    if (!this->isDefined())
+    {
+        throw ostk::core::error::runtime::Undefined("TargetScan");
+    }
+
+    if (anInstant < startInstant_ || anInstant > endInstant_)
+    {
+        throw ostk::core::error::RuntimeError("Instant is outside the interval.");
+    }
+
+    // interpolate the velocity using polynomial interpolation
+
+    const Array<Instant> instants = {
+        anInstant - stepSize_ * 2,
+        anInstant - stepSize_,
+        anInstant,
+        anInstant + stepSize_,
+        anInstant + stepSize_ * 2,
+    };
+
+    VectorXd times(instants.getSize());
+    MatrixXd positionCoordinates(3, instants.getSize());
+
+    Position position = Position::Undefined();
+
+    const Duration intervalDuration = endInstant_ - startInstant_;
+    const Length scanDistance =
+        startLLA_.calculateDistanceTo(endLLA_, celestialSPtr_->getEquatorialRadius(), celestialSPtr_->getFlattening());
+    const Real scanSpeed = scanDistance.inMeters() / intervalDuration.inSeconds();
+
+    const Pair<Angle, Angle> azimuthAngles =
+        startLLA_.calculateAzimuthTo(endLLA_, celestialSPtr_->getEquatorialRadius(), celestialSPtr_->getFlattening());
+
+    Size i = 0;
+    for (const auto& instant : instants)
+    {
+        times(i) = (instant - anInstant).inSeconds();
+
+        const Length forwardDistance = Length::Meters(scanSpeed * (instant - startInstant_).inSeconds());
+
+        const LLA lla = LLA::Forward(
+            startLLA_,
+            azimuthAngles.first,
+            forwardDistance,
+            celestialSPtr_->getEquatorialRadius(),
+            celestialSPtr_->getFlattening()
+        );
+
+        const physics::coordinate::Position positionAtInstant =
+            physics::coordinate::Position::FromLLA(lla, celestialSPtr_).inFrame(Frame::GCRF(), instant);
+
+        if (instant == anInstant)
+        {
+            position = positionAtInstant;
+        }
+
+        positionCoordinates.col(i) = positionAtInstant.getCoordinates();
+
+        ++i;
+    }
+
+    Array<Shared<const Interpolator>> interpolators = {
+        Interpolator::GenerateInterpolator(Interpolator::Type::CubicSpline, times, positionCoordinates.row(0)),
+        Interpolator::GenerateInterpolator(Interpolator::Type::CubicSpline, times, positionCoordinates.row(1)),
+        Interpolator::GenerateInterpolator(Interpolator::Type::CubicSpline, times, positionCoordinates.row(2)),
+    };
+
+    const Vector3d velocityCoordinates = {
+        interpolators[0]->computeDerivative(0.0),
+        interpolators[1]->computeDerivative(0.0),
+        interpolators[2]->computeDerivative(0.0),
+    };
+
+    const Velocity velocity = Velocity::MetersPerSecond(velocityCoordinates, Frame::GCRF());
+
+    return State(anInstant, position, velocity);
+}
+
+LLA TargetScan::getStartLLA() const
+{
+    return startLLA_;
+}
+
+LLA TargetScan::getEndLLA() const
+{
+    return endLLA_;
+}
+
+Instant TargetScan::getStartInstant() const
+{
+    return startInstant_;
+}
+
+Instant TargetScan::getEndInstant() const
+{
+    return endInstant_;
+}
+
+Celestial TargetScan::getCelestial() const
+{
+    return *celestialSPtr_;
+}
+
+Duration TargetScan::getStepSize() const
+{
+    return stepSize_;
+}
+
+void TargetScan::print(std::ostream& anOutputStream, bool displayDecorator) const
+{
+    using ostk::core::type::String;
+
+    displayDecorator ? ostk::core::utils::Print::Header(anOutputStream, "TargetScan") : void();
+
+    ostk::core::utils::Print::Line(anOutputStream)
+        << "Start LLA:" << (startLLA_.isDefined() ? startLLA_.toString() : "Undefined");
+
+    ostk::core::utils::Print::Line(anOutputStream)
+        << "End LLA:" << (endLLA_.isDefined() ? endLLA_.toString() : "Undefined");
+
+    ostk::core::utils::Print::Line(anOutputStream)
+        << "Start Instant:" << (startInstant_.isDefined() ? startInstant_.toString() : "Undefined");
+
+    ostk::core::utils::Print::Line(anOutputStream)
+        << "End Instant:" << (endInstant_.isDefined() ? endInstant_.toString() : "Undefined");
+
+    ostk::core::utils::Print::Line(anOutputStream)
+        << "Celestial:"
+        << (celestialSPtr_ != nullptr && celestialSPtr_->isDefined() ? celestialSPtr_->getName() : "Undefined");
+
+    ostk::core::utils::Print::Line(anOutputStream)
+        << "Step Size:" << (stepSize_.isDefined() ? stepSize_.toString() : "Undefined");
+
+    displayDecorator ? ostk::core::utils::Print::Footer(anOutputStream) : void();
+}
+
+bool TargetScan::operator==(const Model& aModel) const
+{
+    const TargetScan* staticModelPtr = dynamic_cast<const TargetScan*>(&aModel);
+
+    return (staticModelPtr != nullptr) && this->operator==(*staticModelPtr);
+}
+
+bool TargetScan::operator!=(const Model& aModel) const
+{
+    return !((*this) == aModel);
+}
+
+TargetScan TargetScan::FromGroundSpeed(
+    const LLA& aStartLLA,
+    const LLA& anEndLLA,
+    const Derived& aGroundSpeed,
+    const Instant& aStartInstant,
+    const Celestial& aCelestial,
+    const Duration& aStepSize
+)
+{
+    if (!aStartLLA.isDefined())
+    {
+        throw ostk::core::error::runtime::Undefined("Start LLA");
+    }
+
+    if (!anEndLLA.isDefined())
+    {
+        throw ostk::core::error::runtime::Undefined("End LLA");
+    }
+
+    if (!aGroundSpeed.isDefined())
+    {
+        throw ostk::core::error::runtime::Undefined("Ground speed");
+    }
+
+    if (!aStartInstant.isDefined())
+    {
+        throw ostk::core::error::runtime::Undefined("Start instant");
+    }
+
+    if (!aStepSize.isDefined())
+    {
+        throw ostk::core::error::runtime::Undefined("Step size");
+    }
+
+    if (aStepSize.inSeconds() <= 0.0)
+    {
+        throw ostk::core::error::RuntimeError("Step size must be positive.");
+    }
+
+    const Real groundSpeedMps = aGroundSpeed.in(Derived::Unit::MeterPerSecond());
+
+    if (groundSpeedMps <= 0.0)
+    {
+        throw ostk::core::error::RuntimeError("Ground speed must be positive.");
+    }
+
+    const Length distance =
+        aStartLLA.calculateDistanceTo(anEndLLA, aCelestial.getEquatorialRadius(), aCelestial.getFlattening());
+
+    const Duration duration = Duration::Seconds(distance.inMeters() / groundSpeedMps);
+
+    const Instant endInstant = aStartInstant + duration;
+
+    return TargetScan(aStartLLA, anEndLLA, aStartInstant, endInstant, aCelestial, aStepSize);
+}
+
+}  // namespace model
+}  // namespace trajectory
+}  // namespace astrodynamics
+}  // namespace ostk
diff --git a/test/OpenSpaceToolkit/Astrodynamics/Trajectory/Model/TargetScan.test.cpp b/test/OpenSpaceToolkit/Astrodynamics/Trajectory/Model/TargetScan.test.cpp
new file mode 100644
index 000000000..6b99cd0ef
--- /dev/null
+++ b/test/OpenSpaceToolkit/Astrodynamics/Trajectory/Model/TargetScan.test.cpp
@@ -0,0 +1,249 @@
+/// Apache License 2.0
+
+#include <OpenSpaceToolkit/Physics/Coordinate/Spherical/LLA.hpp>
+#include <OpenSpaceToolkit/Physics/Environment/Object/Celestial/Earth.hpp>
+#include <OpenSpaceToolkit/Physics/Time/Instant.hpp>
+
+#include <OpenSpaceToolkit/Astrodynamics/Trajectory/Model/TargetScan.hpp>
+
+#include <Global.test.hpp>
+
+using ostk::core::type::Shared;
+
+using ostk::mathematics::object::Vector6d;
+using ostk::mathematics::object::VectorXd;
+
+using ostk::physics::coordinate::spherical::LLA;
+using ostk::physics::environment::object::celestial::Earth;
+using ostk::physics::time::DateTime;
+using ostk::physics::time::Duration;
+using ostk::physics::time::Instant;
+using ostk::physics::time::Scale;
+using ostk::physics::unit::Derived;
+
+using ostk::astrodynamics::trajectory::model::TargetScan;
+using ostk::astrodynamics::trajectory::State;
+
+class OpenSpaceToolkit_Astrodynamics_Trajectory_Model_TargetScan : public ::testing::Test
+{
+   protected:
+    const Earth earth_ = Earth::WGS84();
+    const LLA startLLA_ = LLA::Vector({0.099457122261122202, -100.3695068214156, 0.0});
+    const LLA endLLA_ = LLA::Vector({0.40715652013782405, -100.43336631325768, 0.0});
+    const Instant startInstant_ = Instant::DateTime(DateTime(2018, 1, 1, 0, 0, 0), Scale::UTC);
+    const Instant endInstant_ = Instant::DateTime(DateTime(2018, 1, 1, 0, 0, 5), Scale::UTC);
+    const Duration stepSize_ = Duration::Seconds(1e-2);
+
+    TargetScan targetScan_ = TargetScan(startLLA_, endLLA_, startInstant_, endInstant_, earth_, stepSize_);
+};
+
+TEST_F(OpenSpaceToolkit_Astrodynamics_Trajectory_Model_TargetScan, Constructor)
+{
+    EXPECT_NO_THROW(TargetScan targetScan(startLLA_, endLLA_, startInstant_, endInstant_, earth_, stepSize_));
+
+    EXPECT_NO_THROW(TargetScan targetScan(startLLA_, endLLA_, startInstant_, endInstant_, earth_));
+
+    EXPECT_NO_THROW(TargetScan targetScan(startLLA_, endLLA_, startInstant_, endInstant_));
+}
+
+TEST_F(OpenSpaceToolkit_Astrodynamics_Trajectory_Model_TargetScan, IsDefined)
+{
+    EXPECT_TRUE(targetScan_.isDefined());
+}
+
+TEST_F(OpenSpaceToolkit_Astrodynamics_Trajectory_Model_TargetScan, CalculateStateAt)
+{
+    // undefined cases
+
+    {
+        {
+            EXPECT_THROW(targetScan_.calculateStateAt(Instant::Undefined()), ostk::core::error::runtime::Undefined);
+        }
+
+        {
+            EXPECT_THROW(
+                targetScan_.calculateStateAt(startInstant_ - Duration::Seconds(1.0)), ostk::core::error::RuntimeError
+            );
+        }
+
+        {
+            EXPECT_THROW(
+                targetScan_.calculateStateAt(endInstant_ + Duration::Seconds(1.0)), ostk::core::error::RuntimeError
+            );
+        }
+
+        {
+            const TargetScan undefinedTargetScan(
+                LLA::Undefined(), LLA::Undefined(), Instant::Undefined(), Instant::Undefined(), Earth::WGS84()
+            );
+            EXPECT_THROW(undefinedTargetScan.calculateStateAt(startInstant_), ostk::core::error::runtime::Undefined);
+        }
+    }
+
+    // values obtained from Orekit by simulating a nadir pointing trajectory
+    // within interval
+    {
+        const Instant instant = startInstant_ + (endInstant_ - startInstant_) / 2.0;
+
+        const State state = targetScan_.calculateStateAt(instant);
+
+        const Vector6d expectedCoordinates = {
+            6378113.46765721,
+            -2392.2706621550024,
+            17005.12534605209,
+            -18.702691992298014,
+            -956.905990523886,
+            6804.66697033946,
+        };
+
+        const VectorXd coordinates = state.getCoordinates();
+
+        EXPECT_VECTORS_ALMOST_EQUAL(coordinates, expectedCoordinates, 1e-1);
+    }
+
+    // boundaries interval
+    // Note: less accurate at boundaries due to polynomial interpolation
+    {
+        {
+            const State state = targetScan_.calculateStateAt(startInstant_);
+
+            const Vector6d expectedCoordinates = {
+                6378136.936099239,
+                2.6122573763132095E-4,
+                -6.584354159011127,
+                -0.07204923298218091,
+                -956.9095709619337,
+                6804.692417661864,
+            };
+
+            const VectorXd coordinates = state.getCoordinates();
+
+            EXPECT_VECTORS_ALMOST_EQUAL(coordinates, expectedCoordinates, 1.5);
+        }
+
+        {
+            const State state = targetScan_.calculateStateAt(endInstant_);
+
+            const Vector6d expectedCoordinates = {
+                6378043.422760435,
+                -4784.523805314675,
+                34016.70863685098,
+                -37.33318936532419,
+                -956.8953467933591,
+                6804.591290206263,
+            };
+
+            const VectorXd coordinates = state.getCoordinates();
+
+            EXPECT_VECTORS_ALMOST_EQUAL(coordinates, expectedCoordinates, 1.5);
+        }
+    }
+}
+
+TEST_F(OpenSpaceToolkit_Astrodynamics_Trajectory_Model_TargetScan, Clone)
+{
+    TargetScan* clonedTargetScan = targetScan_.clone();
+
+    EXPECT_TRUE(clonedTargetScan != nullptr);
+    EXPECT_TRUE(*clonedTargetScan == targetScan_);
+
+    delete clonedTargetScan;
+}
+
+TEST_F(OpenSpaceToolkit_Astrodynamics_Trajectory_Model_TargetScan, EqualityOperator)
+{
+    EXPECT_TRUE(targetScan_ == targetScan_);
+
+    const TargetScan targetScan2(LLA::Vector({1.0, 0.0, 0.0}), endLLA_, startInstant_, endInstant_, earth_);
+    EXPECT_FALSE(targetScan_ == targetScan2);
+}
+
+TEST_F(OpenSpaceToolkit_Astrodynamics_Trajectory_Model_TargetScan, InequalityOperator)
+{
+    EXPECT_FALSE(targetScan_ != targetScan_);
+
+    const TargetScan targetScan2(LLA::Vector({1.0, 0.0, 0.0}), endLLA_, startInstant_, endInstant_, earth_);
+    EXPECT_TRUE(targetScan_ != targetScan2);
+}
+
+TEST_F(OpenSpaceToolkit_Astrodynamics_Trajectory_Model_TargetScan, Print)
+{
+    testing::internal::CaptureStdout();
+
+    EXPECT_NO_THROW(targetScan_.print(std::cout, true));
+    EXPECT_NO_THROW(targetScan_.print(std::cout, false));
+
+    const std::string output = testing::internal::GetCapturedStdout();
+    EXPECT_FALSE(output.empty());
+}
+
+TEST_F(OpenSpaceToolkit_Astrodynamics_Trajectory_Model_TargetScan, StreamOperator)
+{
+    testing::internal::CaptureStdout();
+
+    EXPECT_NO_THROW(std::cout << targetScan_);
+
+    const std::string output = testing::internal::GetCapturedStdout();
+    EXPECT_FALSE(output.empty());
+}
+
+TEST_F(OpenSpaceToolkit_Astrodynamics_Trajectory_Model_TargetScan, Getters)
+{
+    EXPECT_EQ(targetScan_.getStartLLA(), startLLA_);
+    EXPECT_EQ(targetScan_.getEndLLA(), endLLA_);
+    EXPECT_EQ(targetScan_.getStartInstant(), startInstant_);
+    EXPECT_EQ(targetScan_.getEndInstant(), endInstant_);
+    EXPECT_EQ(targetScan_.getCelestial().getName(), earth_.getName());
+    EXPECT_EQ(targetScan_.getStepSize(), stepSize_);
+}
+
+TEST_F(OpenSpaceToolkit_Astrodynamics_Trajectory_Model_TargetScan, FromGroundSpeed)
+{
+    const Derived groundSpeed = Derived(1000.0, Derived::Unit::MeterPerSecond());
+    const Duration stepSize = Duration::Seconds(1.0);
+
+    EXPECT_NO_THROW(TargetScan::FromGroundSpeed(startLLA_, endLLA_, groundSpeed, startInstant_, earth_, stepSize));
+
+    // undefined cases
+
+    {
+        EXPECT_THROW(
+            TargetScan::FromGroundSpeed(LLA::Undefined(), endLLA_, groundSpeed, startInstant_, earth_, stepSize),
+            ostk::core::error::runtime::Undefined
+        );
+
+        EXPECT_THROW(
+            TargetScan::FromGroundSpeed(startLLA_, LLA::Undefined(), groundSpeed, startInstant_, earth_, stepSize),
+            ostk::core::error::runtime::Undefined
+        );
+
+        EXPECT_THROW(
+            TargetScan::FromGroundSpeed(startLLA_, endLLA_, Derived::Undefined(), startInstant_, earth_, stepSize),
+            ostk::core::error::runtime::Undefined
+        );
+
+        EXPECT_THROW(
+            TargetScan::FromGroundSpeed(
+                startLLA_, endLLA_, Derived(-1000.0, Derived::Unit::MeterPerSecond()), startInstant_, earth_, stepSize
+            ),
+            ostk::core::error::RuntimeError
+        );
+
+        EXPECT_THROW(
+            TargetScan::FromGroundSpeed(startLLA_, endLLA_, groundSpeed, Instant::Undefined(), earth_, stepSize),
+            ostk::core::error::runtime::Undefined
+        );
+
+        EXPECT_THROW(
+            TargetScan::FromGroundSpeed(startLLA_, endLLA_, groundSpeed, startInstant_, earth_, Duration::Undefined()),
+            ostk::core::error::runtime::Undefined
+        );
+
+        EXPECT_THROW(
+            TargetScan::FromGroundSpeed(
+                startLLA_, endLLA_, groundSpeed, startInstant_, earth_, Duration::Seconds(-1.0)
+            ),
+            ostk::core::error::RuntimeError
+        );
+    }
+}


Reviews:
user: login: phc1990
body: Only mentioned one but basically same feedback as other MR
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-15T17:32:17Z
user: login: phc1990
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-16T09:33:33Z
user: login: alex-liang3
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-05-16T10:25:01Z
user: login: coderabbitai[bot]
body: **Actionable comments posted: 8**

<details>
<summary>🧹 Nitpick comments (3)</summary><blockquote>

<details>
<summary>bindings/python/src/OpenSpaceToolkitAstrodynamicsPy/Trajectory/Model/TargetScan.cpp (1)</summary><blockquote>

`125-133`: **Returned `Celestial` object is copied – consider using `reference_internal`**

`get_celestial` returns a **copy** of the internal `Celestial`.  
If the user mutates the Python object (e.g. changes name or gravitational parameter) those changes will not propagate back to the C++ instance, and vice-versa. If the intention is to expose the internal object, add an explicit return-value policy:

```cpp
.def(
    "get_celestial",
    &TargetScan::getCelestial,
    py::return_value_policy::reference_internal,   // <- keeps C++ & Python views aligned
    R"doc(
        Get the celestial object of the target scan.
        Returns:
            Celestial: The celestial object.
    )doc"
)
```

Otherwise, clarify in the docstring that a copy is returned.

</blockquote></details>
<details>
<summary>src/OpenSpaceToolkit/Astrodynamics/Trajectory/Model/TargetScan.cpp (1)</summary><blockquote>

`95-108`: **Boundary sampling may step outside the valid interval**

For instants within `2*stepSize_` of `startInstant_` or `endInstant_`, the
generated sampling instants fall outside `[startInstant_, endInstant_]`. This
yields negative `forwardDistance` and violates the documented pre-condition
(trajectory is only defined inside the interval).

Consider clamping or asymmetrically expanding the window near the edges:

```cpp
const Array<Instant> instants = {
    std::max(anInstant - stepSize_ * 2, startInstant_),
    std::max(anInstant - stepSize_,     startInstant_),
    anInstant,
    std::min(anInstant + stepSize_,     endInstant_),
    std::min(anInstant + stepSize_ * 2, endInstant_)
};
```

This avoids extrapolation and keeps all samples meaningful.

</blockquote></details>
<details>
<summary>test/OpenSpaceToolkit/Astrodynamics/Trajectory/Model/TargetScan.test.cpp (1)</summary><blockquote>

`200-207`: **Consider a negative-ground-speed test case**

The factory method is expected to reject non-positive ground speeds, yet no test
covers this branch. Add a negative ground-speed check to guarantee the new
validation logic remains in place.

```cpp
EXPECT_THROW(
    TargetScan::FromGroundSpeed(
        startLLA_, endLLA_, Derived(-10.0, Derived::Unit::MeterPerSecond()),
        startInstant_, earth_, stepSize),
    ostk::core::error::RuntimeError);
```

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: CodeRabbit UI**
**Review profile: CHILL**
**Plan: Pro**
**Cache: Disabled due to data retention organization setting**
**Knowledge Base: Disabled due to data retention organization setting**


<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between 6475d2d43f1f2cf076b2a64e2e57627394fb1b7c and 68326afba31d693d401f9e410de1b1460b0d44ce.

</details>

<details>
<summary>📒 Files selected for processing (9)</summary>

* `bindings/python/src/OpenSpaceToolkitAstrodynamicsPy/Trajectory/Model.cpp` (2 hunks)
* `bindings/python/src/OpenSpaceToolkitAstrodynamicsPy/Trajectory/Model/TargetScan.cpp` (1 hunks)
* `bindings/python/test/Trajectory/Model/test_target_scan.py` (1 hunks)
* `bindings/python/test/test_trajectory.py` (1 hunks)
* `include/OpenSpaceToolkit/Astrodynamics/Trajectory.hpp` (2 hunks)
* `include/OpenSpaceToolkit/Astrodynamics/Trajectory/Model/TargetScan.hpp` (1 hunks)
* `src/OpenSpaceToolkit/Astrodynamics/Trajectory.cpp` (2 hunks)
* `src/OpenSpaceToolkit/Astrodynamics/Trajectory/Model/TargetScan.cpp` (1 hunks)
* `test/OpenSpaceToolkit/Astrodynamics/Trajectory/Model/TargetScan.test.cpp` (1 hunks)

</details>

<details>
<summary>🧰 Additional context used</summary>

<details>
<summary>🧬 Code Graph Analysis (4)</summary>

<details>
<summary>bindings/python/test/test_trajectory.py (1)</summary><blockquote>

<details>
<summary>test/OpenSpaceToolkit/Astrodynamics/Trajectory/Orbit/Message/SpaceX/OPM.test.cpp (1)</summary>

* `DateTime` (31-69)

</details>

</blockquote></details>
<details>
<summary>src/OpenSpaceToolkit/Astrodynamics/Trajectory.cpp (1)</summary><blockquote>

<details>
<summary>include/OpenSpaceToolkit/Astrodynamics/Trajectory/Model/Static.hpp (1)</summary>

* `Static` (40-40)

</details>

</blockquote></details>
<details>
<summary>bindings/python/src/OpenSpaceToolkitAstrodynamicsPy/Trajectory/Model.cpp (1)</summary><blockquote>

<details>
<summary>bindings/python/src/OpenSpaceToolkitAstrodynamicsPy/Trajectory/Model/TargetScan.cpp (2)</summary>

* `OpenSpaceToolkitAstrodynamicsPy_Trajectory_Model_TargetScan` (5-171)
* `OpenSpaceToolkitAstrodynamicsPy_Trajectory_Model_TargetScan` (5-5)

</details>

</blockquote></details>
<details>
<summary>bindings/python/src/OpenSpaceToolkitAstrodynamicsPy/Trajectory/Model/TargetScan.cpp (3)</summary><blockquote>

<details>
<summary>include/OpenSpaceToolkit/Astrodynamics/Trajectory/Model/TargetScan.hpp (1)</summary>

* `TargetScan` (52-59)

</details>
<details>
<summary>src/OpenSpaceToolkit/Astrodynamics/Trajectory/Model/TargetScan.cpp (19)</summary>

* `TargetScan` (30-46)
* `isDefined` (77-81)
* `isDefined` (77-77)
* `calculateStateAt` (83-166)
* `calculateStateAt` (83-83)
* `getStartLLA` (168-171)
* `getStartLLA` (168-168)
* `getEndLLA` (173-176)
* `getEndLLA` (173-173)
* `getStartInstant` (178-181)
* `getStartInstant` (178-178)
* `getEndInstant` (183-186)
* `getEndInstant` (183-183)
* `getCelestial` (188-191)
* `getCelestial` (188-188)
* `getStepSize` (193-196)
* `getStepSize` (193-193)
* `FromGroundSpeed` (238-287)
* `FromGroundSpeed` (238-245)

</details>
<details>
<summary>src/OpenSpaceToolkit/Astrodynamics/Trajectory.cpp (2)</summary>

* `isDefined` (87-90)
* `isDefined` (87-87)

</details>

</blockquote></details>

</details><details>
<summary>🪛 Ruff (0.11.9)</summary>

<details>
<summary>bindings/python/test/test_trajectory.py</summary>

10-10: `ostk.physics.time.DateTime` imported but unused

Remove unused import: `ostk.physics.time.DateTime`

(F401)

---

11-11: `ostk.physics.time.Scale` imported but unused

Remove unused import: `ostk.physics.time.Scale`

(F401)

</details>

</details>

</details>

<details>
<summary>⏰ Context from checks skipped due to timeout of 90000ms (1)</summary>

* GitHub Check: Build Development Image / Build Development Image

</details>

<details>
<summary>🔇 Additional comments (7)</summary><blockquote>

<details>
<summary>src/OpenSpaceToolkit/Astrodynamics/Trajectory.cpp (2)</summary>

`16-16`: **Include for new TargetScan model.**

The include directive for the new `TargetScan.hpp` header is appropriately added to integrate the new trajectory model.

---

`35-36`: **Good code organization.**

The type alias for `TargetScanModel` and the moved `using` directive for `Static` improve code organization and maintainability by keeping all model-related declarations together.

</details>
<details>
<summary>bindings/python/src/OpenSpaceToolkitAstrodynamicsPy/Trajectory/Model.cpp (2)</summary>

`6-6`: **Include for new TargetScan Python bindings.**

Correctly includes the TargetScan.cpp header to access the Python bindings implementation.

---

`88-88`: **Registration of new TargetScan model.**

Properly registers the TargetScan model with the Python module, making it available for use in Python code.

</details>
<details>
<summary>include/OpenSpaceToolkit/Astrodynamics/Trajectory.hpp (2)</summary>

`198-205`: **Clear deprecation notice for GroundStrip method.**

Good use of both comment-based and attribute-based deprecation notices that guide users toward the new TargetScan model.

---

`228-234`: **Clear deprecation notice for overloaded GroundStrip method.**

Appropriate deprecation notice for the second overload, consistent with the first one.

</details>
<details>
<summary>bindings/python/test/Trajectory/Model/test_target_scan.py (1)</summary>

`16-56`: **Well-structured test fixtures.**

These pytest fixtures provide a clean way to set up reusable test data for the TargetScan tests. The Earth, LLA coordinates, and time instants are properly defined.

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-19T21:33:04Z
user: login: coderabbitai[bot]
body: **Actionable comments posted: 2**

<details>
<summary>♻️ Duplicate comments (3)</summary><blockquote>

<details>
<summary>include/OpenSpaceToolkit/Astrodynamics/Trajectory/Model/TargetScan.hpp (3)</summary><blockquote>

`39-43`: **Add complete constructor examples with actual values.**

The example code uses LLA::Vector but doesn't show how to properly instantiate it with coordinate values. Consider providing a more concrete example.

---

`134-136`: **Example code needs concrete values.**

The example code uses placeholder values `{ ... }` instead of showing actual instantiations. This makes it harder for users to understand how to properly use the methods.

---

`146-148`: **Example code needs concrete values.**

Similar to the equality operator, the inequality operator's example code uses placeholder values instead of showing actual instantiations.

</blockquote></details>

</blockquote></details>

<details>
<summary>🧹 Nitpick comments (2)</summary><blockquote>

<details>
<summary>include/OpenSpaceToolkit/Astrodynamics/Trajectory/Model/TargetScan.hpp (2)</summary><blockquote>

`32-33`: **Good class documentation but consider adding more detail about line scanning.**

The class brief description provides a basic understanding, but adding more specific details about target scanning functionality and how it differs from other trajectory models would be helpful for users.

---

`201-205`: **Consider enhancing the static method documentation with units.**

The documentation for `FromGroundSpeed` doesn't specify what units are expected for the `aGroundSpeed` parameter (e.g., m/s, km/h). Adding this information would improve clarity.

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: CodeRabbit UI**
**Review profile: CHILL**
**Plan: Pro**
**Cache: Disabled due to data retention organization setting**
**Knowledge Base: Disabled due to data retention organization setting**


<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between 68326afba31d693d401f9e410de1b1460b0d44ce and 1bacc7e2ea78112f2b1a7480ab18533fc19dd894.

</details>

<details>
<summary>📒 Files selected for processing (3)</summary>

* `include/OpenSpaceToolkit/Astrodynamics/Trajectory/Model/TargetScan.hpp` (1 hunks)
* `src/OpenSpaceToolkit/Astrodynamics/Trajectory/Model/TargetScan.cpp` (1 hunks)
* `test/OpenSpaceToolkit/Astrodynamics/Trajectory/Model/TargetScan.test.cpp` (1 hunks)

</details>

<details>
<summary>🚧 Files skipped from review as they are similar to previous changes (2)</summary>

* test/OpenSpaceToolkit/Astrodynamics/Trajectory/Model/TargetScan.test.cpp
* src/OpenSpaceToolkit/Astrodynamics/Trajectory/Model/TargetScan.cpp

</details>

<details>
<summary>⏰ Context from checks skipped due to timeout of 90000ms (4)</summary>

* GitHub Check: Test / Build Python bindings
* GitHub Check: Test / Check Python Format
* GitHub Check: Test / Check C++ Format
* GitHub Check: Test / Run C++ Unit Tests

</details>

<details>
<summary>🔇 Additional comments (3)</summary><blockquote>

<details>
<summary>include/OpenSpaceToolkit/Astrodynamics/Trajectory/Model/TargetScan.hpp (3)</summary>

`51-52`: **Constructor parameter documentation has been fixed.**

The documentation now correctly states the default step size as 1e-2 second, matching the implementation in the constructor.

---

`199-214`: **Good implementation of the factory method.**

The static `FromGroundSpeed` method provides a convenient way to create a target scan trajectory by specifying ground speed instead of directly calculating the end instant. This is a user-friendly addition.

---

`253-253`: **Good use of shared pointer for celestial object.**

Using a shared pointer (`Shared<const Celestial>`) for the celestial object is appropriate as it manages the lifetime of the object and prevents unnecessary copying.

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-19T22:05:24Z
user: login: coderabbitai[bot]
body: **Actionable comments posted: 4**

<details>
<summary>♻️ Duplicate comments (1)</summary><blockquote>

<details>
<summary>src/OpenSpaceToolkit/Astrodynamics/Trajectory/Model/TargetScan.cpp (1)</summary><blockquote>

`60-62`: _⚠️ Potential issue_

**Fix pointer comparison in equality operator**

The equality operator is comparing shared pointers directly (`celestialSPtr_ == aTargetScanModel.celestialSPtr_`), which only checks if they point to the same memory location rather than comparing the celestial objects' contents. Two logically identical `TargetScan` objects with different but equivalent `Celestial` objects would be considered unequal.



Apply this fix:
```diff
-           celestialSPtr_ == aTargetScanModel.celestialSPtr_ &&
+           (*celestialSPtr_ == *aTargetScanModel.celestialSPtr_) &&
```

</blockquote></details>

</blockquote></details>

<details>
<summary>🧹 Nitpick comments (3)</summary><blockquote>

<details>
<summary>src/OpenSpaceToolkit/Astrodynamics/Trajectory/Model/TargetScan.cpp (3)</summary><blockquote>

`83-98`: **Enhance error message for out-of-range instants**

The error message when an instant is outside the valid interval could be more helpful by including the valid range boundaries.


```diff
-        throw ostk::core::error::RuntimeError("Instant is outside the interval.");
+        throw ostk::core::error::RuntimeError(
+            String::Format("Instant [{0}] is outside the interval [{1}, {2}].", 
+                anInstant.toString(), startInstant_.toString(), endInstant_.toString()));
```

---

`102-108`: **Consider using a consistent method to create time points array**

The code manually defines the array of instants for interpolation. Consider using a loop or vector generation function to make this more maintainable, especially if the number of interpolation points needs to change in the future.


```diff
-    const Array<Instant> instants = {
-        anInstant - stepSize_ * 2,
-        anInstant - stepSize_,
-        anInstant,
-        anInstant + stepSize_,
-        anInstant + stepSize_ * 2,
-    };
+    const int interpolationPoints = 5;
+    const int halfPoints = interpolationPoints / 2;
+    Array<Instant> instants;
+    
+    for (int i = -halfPoints; i <= halfPoints; ++i)
+    {
+        instants.add(anInstant + stepSize_ * i);
+    }
```

---

`122-149`: **Consider optimizing the position calculation loop**

The loop calculates positions for interpolation points but redundantly checks if `instant == anInstant` on each iteration. Since we know `anInstant` is at index 2 (the middle element), we could set `position` directly without the comparison.


```diff
-        if (instant == anInstant)
-        {
-            position = positionAtInstant;
-        }
```

And before the loop:
```diff
+    const Size middleIndex = 2; // anInstant is at index 2 in the instants array
```

Then after the loop:
```diff
+    position = physics::coordinate::Position::FromLLA(
+        LLA::Forward(
+            startLLA_,
+            azimuthAngles.first,
+            Length::Meters(scanSpeed * (anInstant - startInstant_).inSeconds()),
+            celestialSPtr_->getEquatorialRadius(),
+            celestialSPtr_->getFlattening()
+        ), 
+        celestialSPtr_).inFrame(Frame::GCRF(), anInstant);
```

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: CodeRabbit UI**
**Review profile: CHILL**
**Plan: Pro**
**Cache: Disabled due to data retention organization setting**
**Knowledge Base: Disabled due to data retention organization setting**


<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between 1bacc7e2ea78112f2b1a7480ab18533fc19dd894 and af0620b8d2d7282f5464deaf49fd4eb47f456ac9.

</details>

<details>
<summary>📒 Files selected for processing (8)</summary>

* `bindings/python/src/OpenSpaceToolkitAstrodynamicsPy/Trajectory/Model.cpp` (2 hunks)
* `bindings/python/src/OpenSpaceToolkitAstrodynamicsPy/Trajectory/Model/TargetScan.cpp` (1 hunks)
* `bindings/python/test/Trajectory/Model/test_target_scan.py` (1 hunks)
* `include/OpenSpaceToolkit/Astrodynamics/Trajectory.hpp` (2 hunks)
* `include/OpenSpaceToolkit/Astrodynamics/Trajectory/Model/TargetScan.hpp` (1 hunks)
* `src/OpenSpaceToolkit/Astrodynamics/Trajectory.cpp` (2 hunks)
* `src/OpenSpaceToolkit/Astrodynamics/Trajectory/Model/TargetScan.cpp` (1 hunks)
* `test/OpenSpaceToolkit/Astrodynamics/Trajectory/Model/TargetScan.test.cpp` (1 hunks)

</details>

<details>
<summary>🚧 Files skipped from review as they are similar to previous changes (7)</summary>

* bindings/python/src/OpenSpaceToolkitAstrodynamicsPy/Trajectory/Model.cpp
* src/OpenSpaceToolkit/Astrodynamics/Trajectory.cpp
* include/OpenSpaceToolkit/Astrodynamics/Trajectory.hpp
* bindings/python/test/Trajectory/Model/test_target_scan.py
* bindings/python/src/OpenSpaceToolkitAstrodynamicsPy/Trajectory/Model/TargetScan.cpp
* test/OpenSpaceToolkit/Astrodynamics/Trajectory/Model/TargetScan.test.cpp
* include/OpenSpaceToolkit/Astrodynamics/Trajectory/Model/TargetScan.hpp

</details>

<details>
<summary>⏰ Context from checks skipped due to timeout of 90000ms (1)</summary>

* GitHub Check: Build Development Image / Build Development Image

</details>

<details>
<summary>🔇 Additional comments (1)</summary><blockquote>

<details>
<summary>src/OpenSpaceToolkit/Astrodynamics/Trajectory/Model/TargetScan.cpp (1)</summary>

`1-297`: **Overall implementation of TargetScan looks solid**

The implementation provides a well-structured scanning trajectory model with appropriate error handling, parameter validation, and state calculation logic. The code follows the project's style and conventions, and includes comprehensive checks for undefined states.


The design properly separates the trajectory model from the specific calculation methods, making it easy to understand and maintain. The state calculation approach using interpolation is appropriate for this type of trajectory.

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-20T04:21:17Z
user: login: apaletta3
body: :100:
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-20T22:03:41Z

Review comments:
diff_hunk: @@ -265,6 +269,56 @@ class Trajectory
     /// @return Trajectory
     static Trajectory GeodeticNadirGroundTrack(const trajectory::Orbit& anOrbit);
 
+    /// @brief Constructs a trajectory representing a target scan between two locations at the provided instants
+    ///
+    /// @code{.cpp}
+    ///             LLA startLLA = LLA::Vector({ 0.0, 0.0, 0.0 });
+    ///             LLA endLLA = LLA::Vector({ 1.0, 0.0, 0.0 });
+    ///             Instant startInstant = Instant::DateTime(DateTime::Parse("2020-01-01 00:00:00"), Scale::UTC);
+    ///             Instant endInstant = Instant::DateTime(DateTime::Parse("2020-01-01 00:10:00"), Scale::UTC);
+    ///             Earth earth = Earth::WGS84();
+    ///             Trajectory trajectory = Trajectory::TargetScan(startLLA, endLLA, startInstant, endInstant, earth);
+    /// @endcode
+    ///
+    /// @param aStartLLA A start LLA
+    /// @param anEndLLA An end LLA
+    /// @param aStartInstant A start instant
+    /// @param anEndInstant An end instant
+    /// @param aCelestial Celestial body
+    /// @return TargetScan trajectory
+    static Trajectory TargetScan(
+        const LLA& aStartLLA,
+        const LLA& anEndLLA,
+        const Instant& aStartInstant,
+        const Instant& anEndInstant,
+        const Celestial& aCelestial = Earth::WGS84()
+    );
+
+    /// @brief Constructs a trajectory representing a target scan between two locations at the provided instants
path: include/OpenSpaceToolkit/Astrodynamics/Trajectory.hpp
user: login: phc1990
body: > at the provided instants

Might need some more info on how the ground speed plays a role, as we are now only providing a single instant.
created_at: 2025-05-15T17:28:15Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,205 @@
+/// Apache License 2.0
+
+#include <OpenSpaceToolkit/Core/Error.hpp>
+#include <OpenSpaceToolkit/Core/Utility.hpp>
+
+#include <OpenSpaceToolkit/Mathematics/CurveFitting/Interpolator.hpp>
+#include <OpenSpaceToolkit/Mathematics/Object/Vector.hpp>
+
+#include <OpenSpaceToolkit/Astrodynamics/Trajectory/Model/TargetScan.hpp>
+
+namespace ostk
+{
+namespace astrodynamics
+{
+namespace trajectory
+{
+namespace model
+{
+
+using ostk::mathematics::curvefitting::Interpolator;
+using ostk::mathematics::object::MatrixXd;
+using ostk::mathematics::object::Vector3d;
+using ostk::mathematics::object::VectorXd;
+
+using ostk::physics::coordinate::Position;
+
+TargetScan::TargetScan(
+    const LLA& aStartLLA,
+    const LLA& anEndLLA,
+    const Instant& aStartInstant,
+    const Instant& anEndInstant,
+    const Celestial& aCelestial
+)
+    : Model(),
+      startLLA_(aStartLLA),
+      endLLA_(anEndLLA),
+      startInstant_(aStartInstant),
+      endInstant_(anEndInstant),
+      celestialSPtr_(std::make_shared<const Celestial>(aCelestial))
+{
+}
+
+TargetScan* TargetScan::clone() const
+{
+    return new TargetScan(*this);
+}
+
+bool TargetScan::operator==(const TargetScan& aTargetScanModel) const
+{
+    if ((!this->isDefined()) || (!aTargetScanModel.isDefined()))
+    {
+        return false;
+    }
+
+    return startLLA_ == aTargetScanModel.startLLA_ && endLLA_ == aTargetScanModel.endLLA_ &&
+           startInstant_ == aTargetScanModel.startInstant_ && endInstant_ == aTargetScanModel.endInstant_ &&
+           celestialSPtr_ == aTargetScanModel.celestialSPtr_;
+}
+
+bool TargetScan::operator!=(const TargetScan& aTargetScanModel) const
+{
+    return !((*this) == aTargetScanModel);
+}
+
+std::ostream& operator<<(std::ostream& anOutputStream, const TargetScan& aTargetScanModel)
+{
+    aTargetScanModel.print(anOutputStream);
+
+    return anOutputStream;
+}
+
+bool TargetScan::isDefined() const
+{
+    return startLLA_.isDefined() && endLLA_.isDefined() && startInstant_.isDefined() && endInstant_.isDefined() &&
+           celestialSPtr_ != nullptr && celestialSPtr_->isDefined();
+}
+
+State TargetScan::calculateStateAt(const Instant& anInstant) const
+{
+    if (!anInstant.isDefined())
+    {
+        throw ostk::core::error::runtime::Undefined("Instant");
+    }
+
+    if (!this->isDefined())
+    {
+        throw ostk::core::error::runtime::Undefined("TargetScan");
+    }
+
+    if (anInstant < startInstant_ || anInstant > endInstant_)
+    {
+        throw ostk::core::error::RuntimeError("Instant is outside the interval.");
+    }
+
+    // interpolate the velocity using polynomial interpolation
+
+    static const Duration stepSize = Duration::Seconds(1e-2);
+
+    const Array<Instant> instants = {
+        anInstant - stepSize * 2,
+        anInstant - stepSize,
+        anInstant,
+        anInstant + stepSize,
+        anInstant + stepSize * 2,
+    };
+
+    VectorXd times(instants.getSize());
+    MatrixXd positionCoordinates(3, instants.getSize());
+
+    Position position = Position::Undefined();
+
+    const Duration intervalDuration = endInstant_ - startInstant_;
+    const Length scanDistance =
+        startLLA_.calculateDistanceTo(endLLA_, celestialSPtr_->getEquatorialRadius(), celestialSPtr_->getFlattening());
+    const Real scanSpeed = scanDistance.inMeters() / intervalDuration.inSeconds();
+
+    const Pair<Angle, Angle> azimuthAngles =
+        startLLA_.calculateAzimuthTo(endLLA_, celestialSPtr_->getEquatorialRadius(), celestialSPtr_->getFlattening());
+
+    Size i = 0;
+    for (const auto& instant : instants)
+    {
+        times(i) = (instant - anInstant).inSeconds();
+
+        const Length forwardDistance = Length::Meters(scanSpeed * (instant - startInstant_).inSeconds());
+
+        const LLA lla = LLA::Forward(
+            startLLA_,
+            azimuthAngles.first,
+            forwardDistance,
+            celestialSPtr_->getEquatorialRadius(),
+            celestialSPtr_->getFlattening()
+        );
+
+        const physics::coordinate::Position positionAtInstant =
+            physics::coordinate::Position::FromLLA(lla, celestialSPtr_).inFrame(Frame::GCRF(), instant);
path: src/OpenSpaceToolkit/Astrodynamics/Trajectory/Model/TargetScan.cpp
user: login: phc1990
body: Same comment as other MR https://github.com/open-space-collective/open-space-toolkit-astrodynamics/pull/554/files#r2091655298
created_at: 2025-05-15T17:31:45Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,205 @@
+/// Apache License 2.0
+
+#include <OpenSpaceToolkit/Core/Error.hpp>
+#include <OpenSpaceToolkit/Core/Utility.hpp>
+
+#include <OpenSpaceToolkit/Mathematics/CurveFitting/Interpolator.hpp>
+#include <OpenSpaceToolkit/Mathematics/Object/Vector.hpp>
+
+#include <OpenSpaceToolkit/Astrodynamics/Trajectory/Model/TargetScan.hpp>
+
+namespace ostk
+{
+namespace astrodynamics
+{
+namespace trajectory
+{
+namespace model
+{
+
+using ostk::mathematics::curvefitting::Interpolator;
+using ostk::mathematics::object::MatrixXd;
+using ostk::mathematics::object::Vector3d;
+using ostk::mathematics::object::VectorXd;
+
+using ostk::physics::coordinate::Position;
+
+TargetScan::TargetScan(
+    const LLA& aStartLLA,
+    const LLA& anEndLLA,
+    const Instant& aStartInstant,
+    const Instant& anEndInstant,
+    const Celestial& aCelestial
+)
+    : Model(),
+      startLLA_(aStartLLA),
+      endLLA_(anEndLLA),
+      startInstant_(aStartInstant),
+      endInstant_(anEndInstant),
+      celestialSPtr_(std::make_shared<const Celestial>(aCelestial))
+{
+}
+
+TargetScan* TargetScan::clone() const
+{
+    return new TargetScan(*this);
+}
+
+bool TargetScan::operator==(const TargetScan& aTargetScanModel) const
+{
+    if ((!this->isDefined()) || (!aTargetScanModel.isDefined()))
+    {
+        return false;
+    }
+
+    return startLLA_ == aTargetScanModel.startLLA_ && endLLA_ == aTargetScanModel.endLLA_ &&
+           startInstant_ == aTargetScanModel.startInstant_ && endInstant_ == aTargetScanModel.endInstant_ &&
+           celestialSPtr_ == aTargetScanModel.celestialSPtr_;
+}
+
+bool TargetScan::operator!=(const TargetScan& aTargetScanModel) const
+{
+    return !((*this) == aTargetScanModel);
+}
+
+std::ostream& operator<<(std::ostream& anOutputStream, const TargetScan& aTargetScanModel)
+{
+    aTargetScanModel.print(anOutputStream);
+
+    return anOutputStream;
+}
+
+bool TargetScan::isDefined() const
+{
+    return startLLA_.isDefined() && endLLA_.isDefined() && startInstant_.isDefined() && endInstant_.isDefined() &&
+           celestialSPtr_ != nullptr && celestialSPtr_->isDefined();
+}
+
+State TargetScan::calculateStateAt(const Instant& anInstant) const
+{
+    if (!anInstant.isDefined())
+    {
+        throw ostk::core::error::runtime::Undefined("Instant");
+    }
+
+    if (!this->isDefined())
+    {
+        throw ostk::core::error::runtime::Undefined("TargetScan");
+    }
+
+    if (anInstant < startInstant_ || anInstant > endInstant_)
+    {
+        throw ostk::core::error::RuntimeError("Instant is outside the interval.");
+    }
+
+    // interpolate the velocity using polynomial interpolation
+
+    static const Duration stepSize = Duration::Seconds(1e-2);
path: src/OpenSpaceToolkit/Astrodynamics/Trajectory/Model/TargetScan.cpp
user: login: phc1990
body: Same comment, could be part of the const. as optional
created_at: 2025-05-16T09:33:33Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,172 @@
+/// Apache License 2.0
+
+#ifndef __OpenSpaceToolkit_Astrodynamics_Trajectory_Model_TargetScan__
+#define __OpenSpaceToolkit_Astrodynamics_Trajectory_Model_TargetScan__
+
+#include <OpenSpaceToolkit/Physics/Environment/Object/Celestial/Earth.hpp>
+#include <OpenSpaceToolkit/Physics/Time/Instant.hpp>
+#include <OpenSpaceToolkit/Physics/Time/Interval.hpp>
+
+#include <OpenSpaceToolkit/Astrodynamics/Trajectory/Model.hpp>
+#include <OpenSpaceToolkit/Astrodynamics/Trajectory/Orbit.hpp>
+#include <OpenSpaceToolkit/Astrodynamics/Trajectory/State.hpp>
+
+namespace ostk
+{
+namespace astrodynamics
+{
+namespace trajectory
+{
+namespace model
+{
+
+using ostk::physics::coordinate::Position;
+using ostk::physics::environment::object::celestial::Earth;
+using ostk::physics::time::Instant;
+using ostk::physics::time::Interval;
+
+using ostk::astrodynamics::trajectory::Model;
+using ostk::astrodynamics::trajectory::State;
+
+/// @brief Target scanning pointing trajectory model to represent the target scanning of a spacecraft
+class TargetScan : public virtual Model
+{
+   public:
+    /// @brief Constructor
+    ///
+    /// @code{.cpp}
+    ///              TargetScan targetScan = { ... };
path: include/OpenSpaceToolkit/Astrodynamics/Trajectory/Model/TargetScan.hpp
user: login: alex-liang3
body: ```suggestion
```
or add example constructors for each of the inputs
created_at: 2025-05-16T10:13:03Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,172 @@
+/// Apache License 2.0
+
+#ifndef __OpenSpaceToolkit_Astrodynamics_Trajectory_Model_TargetScan__
+#define __OpenSpaceToolkit_Astrodynamics_Trajectory_Model_TargetScan__
+
+#include <OpenSpaceToolkit/Physics/Environment/Object/Celestial/Earth.hpp>
+#include <OpenSpaceToolkit/Physics/Time/Instant.hpp>
+#include <OpenSpaceToolkit/Physics/Time/Interval.hpp>
+
+#include <OpenSpaceToolkit/Astrodynamics/Trajectory/Model.hpp>
+#include <OpenSpaceToolkit/Astrodynamics/Trajectory/Orbit.hpp>
+#include <OpenSpaceToolkit/Astrodynamics/Trajectory/State.hpp>
+
+namespace ostk
+{
+namespace astrodynamics
+{
+namespace trajectory
+{
+namespace model
+{
+
+using ostk::physics::coordinate::Position;
+using ostk::physics::environment::object::celestial::Earth;
+using ostk::physics::time::Instant;
+using ostk::physics::time::Interval;
+
+using ostk::astrodynamics::trajectory::Model;
+using ostk::astrodynamics::trajectory::State;
+
+/// @brief Target scanning pointing trajectory model to represent the target scanning of a spacecraft
+class TargetScan : public virtual Model
+{
+   public:
+    /// @brief Constructor
+    ///
+    /// @code{.cpp}
+    ///              TargetScan targetScan = { ... };
+    ///              TargetScan targetScanModel(startLLA, endLLA, startInstant, endInstant);
+    /// @endcode
+    ///
+    /// @param aStartLLA The starting LLA of the target scan
+    /// @param anEndLLA The ending LLA of the target scan
+    /// @param aStartInstant The starting instant of the target scan
+    /// @param anEndInstant The ending instant of the target scan
+    /// @param aCelestialObject The celestial object (default: Earth::WGS84())
+    TargetScan(
+        const LLA& aStartLLA,
+        const LLA& anEndLLA,
+        const Instant& aStartInstant,
+        const Instant& anEndInstant,
+        const Celestial& aCelestialObject = Earth::WGS84()
+    );
+
+    /// @brief Clone the static model
+    ///
+    /// @code{.cpp}
+    ///              TargetScan targetScanModel = { ... };
+    ///              TargetScan* clonedModel = targetScanModel.clone();
+    /// @endcode
+    ///
+    /// @return A pointer to the cloned static model
+    virtual TargetScan* clone() const override;
+
+    /// @brief Equality operator
+    ///
+    /// @code{.cpp}
+    ///              TargetScan targetScanModel = { ... };
+    ///              TargetScan targetScanModel = { ... };
+    ///              bool isEqual = (targetScanModel == targetScanModel);
path: include/OpenSpaceToolkit/Astrodynamics/Trajectory/Model/TargetScan.hpp
user: login: alex-liang3
body: ```suggestion
    ///              TargetScan targetScanModel1 = { ... };
    ///              TargetScan targetScanModel2 = { ... };
    ///              bool isEqual = (targetScanModel1 == targetScanModel2);
```
copy pasta
created_at: 2025-05-16T10:14:57Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,172 @@
+/// Apache License 2.0
+
+#ifndef __OpenSpaceToolkit_Astrodynamics_Trajectory_Model_TargetScan__
+#define __OpenSpaceToolkit_Astrodynamics_Trajectory_Model_TargetScan__
+
+#include <OpenSpaceToolkit/Physics/Environment/Object/Celestial/Earth.hpp>
+#include <OpenSpaceToolkit/Physics/Time/Instant.hpp>
+#include <OpenSpaceToolkit/Physics/Time/Interval.hpp>
+
+#include <OpenSpaceToolkit/Astrodynamics/Trajectory/Model.hpp>
+#include <OpenSpaceToolkit/Astrodynamics/Trajectory/Orbit.hpp>
+#include <OpenSpaceToolkit/Astrodynamics/Trajectory/State.hpp>
+
+namespace ostk
+{
+namespace astrodynamics
+{
+namespace trajectory
+{
+namespace model
+{
+
+using ostk::physics::coordinate::Position;
+using ostk::physics::environment::object::celestial::Earth;
+using ostk::physics::time::Instant;
+using ostk::physics::time::Interval;
+
+using ostk::astrodynamics::trajectory::Model;
+using ostk::astrodynamics::trajectory::State;
+
+/// @brief Target scanning pointing trajectory model to represent the target scanning of a spacecraft
+class TargetScan : public virtual Model
+{
+   public:
+    /// @brief Constructor
+    ///
+    /// @code{.cpp}
+    ///              TargetScan targetScan = { ... };
+    ///              TargetScan targetScanModel(startLLA, endLLA, startInstant, endInstant);
+    /// @endcode
+    ///
+    /// @param aStartLLA The starting LLA of the target scan
+    /// @param anEndLLA The ending LLA of the target scan
+    /// @param aStartInstant The starting instant of the target scan
+    /// @param anEndInstant The ending instant of the target scan
+    /// @param aCelestialObject The celestial object (default: Earth::WGS84())
+    TargetScan(
+        const LLA& aStartLLA,
+        const LLA& anEndLLA,
+        const Instant& aStartInstant,
+        const Instant& anEndInstant,
+        const Celestial& aCelestialObject = Earth::WGS84()
+    );
+
+    /// @brief Clone the static model
+    ///
+    /// @code{.cpp}
+    ///              TargetScan targetScanModel = { ... };
+    ///              TargetScan* clonedModel = targetScanModel.clone();
+    /// @endcode
+    ///
+    /// @return A pointer to the cloned static model
+    virtual TargetScan* clone() const override;
+
+    /// @brief Equality operator
+    ///
+    /// @code{.cpp}
+    ///              TargetScan targetScanModel = { ... };
+    ///              TargetScan targetScanModel = { ... };
+    ///              bool isEqual = (targetScanModel == targetScanModel);
+    /// @endcode
+    ///
+    /// @param aTargetScanModel The static model to compare with
+    /// @return True if the models are equal, false otherwise
+    bool operator==(const TargetScan& aTargetScanModel) const;
+
+    /// @brief Inequality operator
+    ///
+    /// @code{.cpp}
+    ///              TargetScan targetScanModel = { ... };
+    ///              TargetScan targetScanModel = { ... };
+    ///              bool isNotEqual = (targetScanModel != targetScanModel);
path: include/OpenSpaceToolkit/Astrodynamics/Trajectory/Model/TargetScan.hpp
user: login: alex-liang3
body: copy pasta
```suggestion
    ///              TargetScan targetScanModel1 = { ... };
    ///              TargetScan targetScanModel2 = { ... };
    ///              bool isNotEqual = (targetScanModel1 != targetScanModel2);
```
created_at: 2025-05-16T10:15:22Z
author_association: CONTRIBUTOR
diff_hunk: @@ -7,6 +7,8 @@
 from ostk.physics.coordinate import Position
 from ostk.physics.coordinate import Frame
 from ostk.physics.time import Instant
+from ostk.physics.time import DateTime
+from ostk.physics.time import Scale
path: bindings/python/test/test_trajectory.py
user: login: coderabbitai[bot]
body: _💡 Verification agent_

<details>
<summary>❓ Verification inconclusive</summary>

**Remove unused imports.**

The imports for `DateTime` and `Scale` from `ostk.physics.time` are currently not used in this file. Either remove them if not needed or add a test case that uses them.

---

🏁 Script executed:

```shell
#!/bin/bash
# Check for actual usage of DateTime and Scale in the test_trajectory.py file
grep -n "DateTime\|Scale" bindings/python/test/test_trajectory.py | grep -v "from ostk.physics.time import"
```

Length of output: 109

---



</details>

**Remove unused imports in test_trajectory.py**

The `DateTime` and `Scale` imports on lines 10–11 of `bindings/python/test/test_trajectory.py` are never referenced elsewhere in this file. Please remove them to clean up unused code:

```diff
--- a/bindings/python/test/test_trajectory.py
@@ -10,11 +10,6 @@
 from ostk.physics.coordinate import Frame
 from ostk.physics.time import Duration

-from ostk.physics.time import DateTime
-from ostk.physics.time import Scale

 import pytest
 import numpy as np
```



<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

```suggestion
--- a/bindings/python/test/test_trajectory.py
@@ -10,11 +10,6 @@ from ostk.physics.coordinate import Frame
 from ostk.physics.time import Duration

-import pytest
-import numpy as np

 import pytest
 import numpy as np
```

</details>

<!-- suggestion_end -->

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 Ruff (0.11.9)</summary>

10-10: `ostk.physics.time.DateTime` imported but unused

Remove unused import: `ostk.physics.time.DateTime`

(F401)

---

11-11: `ostk.physics.time.Scale` imported but unused

Remove unused import: `ostk.physics.time.Scale`

(F401)

</details>

</details>

<details>
<summary>🤖 Prompt for AI Agents</summary>

```
In bindings/python/test/test_trajectory.py at lines 10 to 11, the imports of
DateTime and Scale from ostk.physics.time are not used anywhere in the file.
Remove these two import statements to eliminate unused imports and clean up the
code.
```

</details>

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-05-19T21:33:03Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,126 @@
+# Apache License 2.0
+
+import pytest
+
+from ostk.physics.coordinate.spherical import LLA
+from ostk.physics.environment.object.celestial import Earth
+from ostk.physics.time import Instant
+from ostk.physics.time import DateTime
+from ostk.physics.time import Scale
+from ostk.physics.time import Duration
+from ostk.physics.unit import Derived
+from ostk.astrodynamics.trajectory.model import TargetScan
+from ostk.astrodynamics.trajectory import State
+
+
+@pytest.fixture
+def earth() -> Earth:
+    return Earth.WGS84()
+
+
+@pytest.fixture
+def start_lla() -> LLA:
+    return LLA.vector([0.0, 0.0, 0.0])
+
+
+@pytest.fixture
+def end_lla() -> LLA:
+    return LLA.vector([0.0, 1.0, 0.0])
+
+
+@pytest.fixture
+def start_instant() -> Instant:
+    return Instant.date_time(DateTime(2023, 1, 1, 0, 0, 0), Scale.UTC)
+
+
+@pytest.fixture
+def end_instant() -> Instant:
+    return Instant.date_time(DateTime(2023, 1, 1, 0, 10, 0), Scale.UTC)
+
+
+@pytest.fixture
+def target_scan(
+    earth: Earth,
+    start_lla: LLA,
+    end_lla: LLA,
+    start_instant: Instant,
+    end_instant: Instant,
+) -> TargetScan:
+    return TargetScan(
+        start_lla=start_lla,
+        end_lla=end_lla,
+        start_instant=start_instant,
+        end_instant=end_instant,
+        celestial=earth,
+    )
+
+
+class TestTargetScan:
+    def test_constructor(
+        self,
+        target_scan: TargetScan,
+    ):
+        assert target_scan.is_defined()
+
+    def test_calculate_state_at(
+        self,
+        target_scan: TargetScan,
+    ):
+        instant: Instant = Instant.date_time(DateTime(2023, 1, 1, 0, 5, 0), Scale.UTC)
+        state: State = target_scan.calculate_state_at(instant)
+        assert state is not None
+
+    def test_equality_operator(
+        self,
+        target_scan: TargetScan,
+    ):
+        assert target_scan == target_scan
+
+    def test_inequality_operator(
+        self,
+        target_scan: TargetScan,
+        start_lla: LLA,
+        end_lla: LLA,
+        start_instant: Instant,
+        earth: Earth,
+    ):
+        target_scan2 = TargetScan(
+            start_lla=start_lla,
+            end_lla=end_lla,
+            start_instant=start_instant,
+            end_instant=Instant.undefined(),
+            celestial=earth,
+        )
+        assert target_scan != target_scan2
+
+    def test_getters(
+        self,
+        target_scan: TargetScan,
+    ):
+        assert target_scan.get_start_lla() is not None
+        assert target_scan.get_end_lla() is not None
+        assert target_scan.get_start_instant() is not None
+        assert target_scan.get_end_instant() is not None
+        assert target_scan.get_celestial() is not None
+        assert target_scan.get_step_size() is not None
+
path: bindings/python/test/Trajectory/Model/test_target_scan.py
user: login: coderabbitai[bot]
body: _🛠️ Refactor suggestion_

**Improve getter tests with value assertions.**

The current tests only check that getters don't return None. Consider adding assertions to verify the actual values match what was provided in the constructor.


```diff
-    def test_getters(
-        self,
-        target_scan: TargetScan,
-    ):
-        assert target_scan.get_start_lla() is not None
-        assert target_scan.get_end_lla() is not None
-        assert target_scan.get_start_instant() is not None
-        assert target_scan.get_end_instant() is not None
-        assert target_scan.get_celestial() is not None
-        assert target_scan.get_step_size() is not None
+    def test_getters(
+        self,
+        target_scan: TargetScan,
+        start_lla: LLA,
+        end_lla: LLA,
+        start_instant: Instant,
+        end_instant: Instant,
+        earth: Earth,
+    ):
+        assert target_scan.get_start_lla() == start_lla
+        assert target_scan.get_end_lla() == end_lla
+        assert target_scan.get_start_instant() == start_instant
+        assert target_scan.get_end_instant() == end_instant
+        assert target_scan.get_celestial() == earth
+        # Step size is using the default value, so we can at least check it's positive
+        assert target_scan.get_step_size().in_seconds() > 0
```


<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

```suggestion
    def test_getters(
        self,
        target_scan: TargetScan,
        start_lla: LLA,
        end_lla: LLA,
        start_instant: Instant,
        end_instant: Instant,
        earth: Earth,
    ):
        assert target_scan.get_start_lla() == start_lla
        assert target_scan.get_end_lla() == end_lla
        assert target_scan.get_start_instant() == start_instant
        assert target_scan.get_end_instant() == end_instant
        assert target_scan.get_celestial() == earth
        # Step size is using the default value, so we can at least check it's positive
        assert target_scan.get_step_size().in_seconds() > 0
```

</details>

<!-- suggestion_end -->

<details>
<summary>🤖 Prompt for AI Agents</summary>

```
In bindings/python/test/Trajectory/Model/test_target_scan.py around lines 96 to
106, the test_getters method only asserts that getter methods do not return
None. Enhance these tests by adding assertions that verify the returned values
exactly match the expected values passed during the TargetScan object's
construction. Retrieve or define the expected values and compare them with each
getter's output to ensure correctness.
```

</details>

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-05-19T21:33:03Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,126 @@
+# Apache License 2.0
+
+import pytest
+
+from ostk.physics.coordinate.spherical import LLA
+from ostk.physics.environment.object.celestial import Earth
+from ostk.physics.time import Instant
+from ostk.physics.time import DateTime
+from ostk.physics.time import Scale
+from ostk.physics.time import Duration
+from ostk.physics.unit import Derived
+from ostk.astrodynamics.trajectory.model import TargetScan
+from ostk.astrodynamics.trajectory import State
+
+
+@pytest.fixture
+def earth() -> Earth:
+    return Earth.WGS84()
+
+
+@pytest.fixture
+def start_lla() -> LLA:
+    return LLA.vector([0.0, 0.0, 0.0])
+
+
+@pytest.fixture
+def end_lla() -> LLA:
+    return LLA.vector([0.0, 1.0, 0.0])
+
+
+@pytest.fixture
+def start_instant() -> Instant:
+    return Instant.date_time(DateTime(2023, 1, 1, 0, 0, 0), Scale.UTC)
+
+
+@pytest.fixture
+def end_instant() -> Instant:
+    return Instant.date_time(DateTime(2023, 1, 1, 0, 10, 0), Scale.UTC)
+
+
+@pytest.fixture
+def target_scan(
+    earth: Earth,
+    start_lla: LLA,
+    end_lla: LLA,
+    start_instant: Instant,
+    end_instant: Instant,
+) -> TargetScan:
+    return TargetScan(
+        start_lla=start_lla,
+        end_lla=end_lla,
+        start_instant=start_instant,
+        end_instant=end_instant,
+        celestial=earth,
+    )
+
+
+class TestTargetScan:
+    def test_constructor(
+        self,
+        target_scan: TargetScan,
+    ):
+        assert target_scan.is_defined()
+
+    def test_calculate_state_at(
+        self,
+        target_scan: TargetScan,
+    ):
+        instant: Instant = Instant.date_time(DateTime(2023, 1, 1, 0, 5, 0), Scale.UTC)
+        state: State = target_scan.calculate_state_at(instant)
+        assert state is not None
+
path: bindings/python/test/Trajectory/Model/test_target_scan.py
user: login: coderabbitai[bot]
body: _🛠️ Refactor suggestion_

**Enhance state calculation test with more assertions.**

The test verifies that a state is returned but doesn't validate any properties of the state. Consider adding assertions to check the position, velocity, or frame of the returned state.


```diff
-    def test_calculate_state_at(
-        self,
-        target_scan: TargetScan,
-    ):
-        instant: Instant = Instant.date_time(DateTime(2023, 1, 1, 0, 5, 0), Scale.UTC)
-        state: State = target_scan.calculate_state_at(instant)
-        assert state is not None
+    def test_calculate_state_at(
+        self,
+        target_scan: TargetScan,
+    ):
+        instant: Instant = Instant.date_time(DateTime(2023, 1, 1, 0, 5, 0), Scale.UTC)
+        state: State = target_scan.calculate_state_at(instant)
+        assert state is not None
+        assert state.get_instant() == instant
+        # Verify position and velocity are defined
+        assert state.get_position().is_defined()
+        assert state.get_velocity().is_defined()
```


<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

```suggestion
    def test_calculate_state_at(
        self,
        target_scan: TargetScan,
    ):
        instant: Instant = Instant.date_time(DateTime(2023, 1, 1, 0, 5, 0), Scale.UTC)
        state: State = target_scan.calculate_state_at(instant)
        assert state is not None
        assert state.get_instant() == instant
        # Verify position and velocity are defined
        assert state.get_position().is_defined()
        assert state.get_velocity().is_defined()
```

</details>

<!-- suggestion_end -->

<details>
<summary>🤖 Prompt for AI Agents</summary>

```
In bindings/python/test/Trajectory/Model/test_target_scan.py around lines 65 to
72, the test_calculate_state_at method only asserts that the returned state is
not None but does not verify any of its properties. Enhance the test by adding
assertions to check key attributes of the state such as its position
coordinates, velocity vector, and reference frame to ensure the state is
correctly calculated.
```

</details>

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-05-19T21:33:03Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,126 @@
+# Apache License 2.0
+
+import pytest
+
+from ostk.physics.coordinate.spherical import LLA
+from ostk.physics.environment.object.celestial import Earth
+from ostk.physics.time import Instant
+from ostk.physics.time import DateTime
+from ostk.physics.time import Scale
+from ostk.physics.time import Duration
+from ostk.physics.unit import Derived
+from ostk.astrodynamics.trajectory.model import TargetScan
+from ostk.astrodynamics.trajectory import State
+
+
+@pytest.fixture
+def earth() -> Earth:
+    return Earth.WGS84()
+
+
+@pytest.fixture
+def start_lla() -> LLA:
+    return LLA.vector([0.0, 0.0, 0.0])
+
+
+@pytest.fixture
+def end_lla() -> LLA:
+    return LLA.vector([0.0, 1.0, 0.0])
+
+
+@pytest.fixture
+def start_instant() -> Instant:
+    return Instant.date_time(DateTime(2023, 1, 1, 0, 0, 0), Scale.UTC)
+
+
+@pytest.fixture
+def end_instant() -> Instant:
+    return Instant.date_time(DateTime(2023, 1, 1, 0, 10, 0), Scale.UTC)
+
+
+@pytest.fixture
+def target_scan(
+    earth: Earth,
+    start_lla: LLA,
+    end_lla: LLA,
+    start_instant: Instant,
+    end_instant: Instant,
+) -> TargetScan:
+    return TargetScan(
+        start_lla=start_lla,
+        end_lla=end_lla,
+        start_instant=start_instant,
+        end_instant=end_instant,
+        celestial=earth,
+    )
+
+
+class TestTargetScan:
+    def test_constructor(
+        self,
+        target_scan: TargetScan,
+    ):
+        assert target_scan.is_defined()
+
+    def test_calculate_state_at(
+        self,
+        target_scan: TargetScan,
+    ):
+        instant: Instant = Instant.date_time(DateTime(2023, 1, 1, 0, 5, 0), Scale.UTC)
+        state: State = target_scan.calculate_state_at(instant)
+        assert state is not None
+
+    def test_equality_operator(
+        self,
+        target_scan: TargetScan,
+    ):
+        assert target_scan == target_scan
+
+    def test_inequality_operator(
+        self,
+        target_scan: TargetScan,
+        start_lla: LLA,
+        end_lla: LLA,
+        start_instant: Instant,
+        earth: Earth,
+    ):
+        target_scan2 = TargetScan(
+            start_lla=start_lla,
+            end_lla=end_lla,
+            start_instant=start_instant,
+            end_instant=Instant.undefined(),
+            celestial=earth,
+        )
+        assert target_scan != target_scan2
+
+    def test_getters(
+        self,
+        target_scan: TargetScan,
+    ):
+        assert target_scan.get_start_lla() is not None
+        assert target_scan.get_end_lla() is not None
+        assert target_scan.get_start_instant() is not None
+        assert target_scan.get_end_instant() is not None
+        assert target_scan.get_celestial() is not None
+        assert target_scan.get_step_size() is not None
+
+    def test_from_ground_speed(
+        self,
+        start_lla: LLA,
+        end_lla: LLA,
+        start_instant: Instant,
+        earth: Earth,
+    ):
+        ground_speed: Derived = Derived(100.0, Derived.Unit.meter_per_second())
+        step_size: Duration = Duration.seconds(1.0)
+
+        target_scan = TargetScan.from_ground_speed(
+            start_lla=start_lla,
+            end_lla=end_lla,
+            ground_speed=ground_speed,
+            start_instant=start_instant,
+            celestial=earth,
+            step_size=step_size,
+        )
+
+        assert target_scan.is_defined()
path: bindings/python/test/Trajectory/Model/test_target_scan.py
user: login: coderabbitai[bot]
body: _🛠️ Refactor suggestion_

**Enhance factory method test.**

The test for `from_ground_speed` only checks that the created object is defined. Consider verifying key properties of the created object to ensure it's correctly initialized.


```diff
-    def test_from_ground_speed(
-        self,
-        start_lla: LLA,
-        end_lla: LLA,
-        start_instant: Instant,
-        earth: Earth,
-    ):
-        ground_speed: Derived = Derived(100.0, Derived.Unit.meter_per_second())
-        step_size: Duration = Duration.seconds(1.0)
-
-        target_scan = TargetScan.from_ground_speed(
-            start_lla=start_lla,
-            end_lla=end_lla,
-            ground_speed=ground_speed,
-            start_instant=start_instant,
-            celestial=earth,
-            step_size=step_size,
-        )
-
-        assert target_scan.is_defined()
+    def test_from_ground_speed(
+        self,
+        start_lla: LLA,
+        end_lla: LLA,
+        start_instant: Instant,
+        earth: Earth,
+    ):
+        ground_speed: Derived = Derived(100.0, Derived.Unit.meter_per_second())
+        step_size: Duration = Duration.seconds(1.0)
+
+        target_scan = TargetScan.from_ground_speed(
+            start_lla=start_lla,
+            end_lla=end_lla,
+            ground_speed=ground_speed,
+            start_instant=start_instant,
+            celestial=earth,
+            step_size=step_size,
+        )
+
+        assert target_scan.is_defined()
+        assert target_scan.get_start_lla() == start_lla
+        assert target_scan.get_end_lla() == end_lla
+        assert target_scan.get_start_instant() == start_instant
+        assert target_scan.get_celestial() == earth
+        assert target_scan.get_step_size() == step_size
+        
+        # Verify end instant is calculated based on distance and ground speed
+        # (This would require calculating the expected duration, which depends on the Earth model)
+        assert target_scan.get_end_instant() > start_instant
```


<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

```suggestion
    def test_from_ground_speed(
        self,
        start_lla: LLA,
        end_lla: LLA,
        start_instant: Instant,
        earth: Earth,
    ):
        ground_speed: Derived = Derived(100.0, Derived.Unit.meter_per_second())
        step_size: Duration = Duration.seconds(1.0)

        target_scan = TargetScan.from_ground_speed(
            start_lla=start_lla,
            end_lla=end_lla,
            ground_speed=ground_speed,
            start_instant=start_instant,
            celestial=earth,
            step_size=step_size,
        )

        assert target_scan.is_defined()
        assert target_scan.get_start_lla() == start_lla
        assert target_scan.get_end_lla() == end_lla
        assert target_scan.get_start_instant() == start_instant
        assert target_scan.get_celestial() == earth
        assert target_scan.get_step_size() == step_size

        # Verify end instant is calculated based on distance and ground speed
        # (This would require calculating the expected duration, which depends on the Earth model)
        assert target_scan.get_end_instant() > start_instant
```

</details>

<!-- suggestion_end -->

<details>
<summary>🤖 Prompt for AI Agents</summary>

```
In bindings/python/test/Trajectory/Model/test_target_scan.py around lines 107 to
126, the test_from_ground_speed method only asserts that the TargetScan object
is defined. Enhance this test by adding assertions to verify key properties of
the created TargetScan instance, such as checking that the start and end
locations match the inputs, the ground speed is set correctly, and the step size
and celestial body are properly assigned. This will ensure the factory method
initializes the object as expected.
```

</details>

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-05-19T21:33:03Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,171 @@
+/// Apache License 2.0
+
+#include <OpenSpaceToolkit/Astrodynamics/Trajectory/Model/TargetScan.hpp>
+
+inline void OpenSpaceToolkitAstrodynamicsPy_Trajectory_Model_TargetScan(pybind11::module& aModule)
+{
+    using namespace pybind11;
+
+    using ostk::core::type::Shared;
+
+    using ostk::physics::coordinate::spherical::LLA;
+    using ostk::physics::environment::object::Celestial;
+    using ostk::physics::environment::object::celestial::Earth;
+    using ostk::physics::time::Duration;
+    using ostk::physics::time::Instant;
+
+    using ostk::astrodynamics::trajectory::model::TargetScan;
+    using ostk::astrodynamics::trajectory::State;
+
+    class_<TargetScan, ostk::astrodynamics::trajectory::Model>(
+        aModule,
+        "TargetScan",
+        R"doc(
+            TargetScan trajectory model.
+
+            This model represents a trajectory that scans between two target locations on a celestial body.
+        )doc"
+    )
+
+        .def(
+            init<const LLA&, const LLA&, const Instant&, const Instant&, const Celestial&, const Duration&>(),
+            R"doc(
+                Construct a `TargetScan` object.
+
+                Args:
+                    start_lla (LLA): The starting location.
+                    end_lla (LLA): The ending location.
+                    start_instant (Instant): The starting instant.
+                    end_instant (Instant): The ending instant.
+                    celestial (Celestial): The celestial body. Defaults to Earth.WGS84().
+                    step_size (Duration): The step size for the trajectory. Defaults to 1e-2 seconds.
+
+                Returns:
+                    TargetScan: The `TargetScan` object.
+            )doc",
+            arg("start_lla"),
+            arg("end_lla"),
+            arg("start_instant"),
+            arg("end_instant"),
+            arg("celestial") = Earth::WGS84(),
+            arg("step_size") = Duration::Seconds(1e-2)
+        )
+
+        .def(self == self)
+        .def(self != self)
+
+        .def("__str__", &(shiftToString<TargetScan>))
+        .def("__repr__", &(shiftToString<TargetScan>))
path: bindings/python/src/OpenSpaceToolkitAstrodynamicsPy/Trajectory/Model/TargetScan.cpp
user: login: coderabbitai[bot]
body: _⚠️ Potential issue_

**`shiftToString` helper might be unavailable in this translation unit**

The bindings use `shiftToString<TargetScan>` for both `__str__` and `__repr__`.  
Unlike `toString`, the helper `shiftToString` is not included from any header in this file, and it is not guaranteed to be reachable through ADL. A missing declaration will break compilation on some compilers / linkers.

```diff
-        .def("__str__", &(shiftToString<TargetScan>))
-        .def("__repr__", &(shiftToString<TargetScan>))
+        .def("__str__", &ostk::core::utils::toString<TargetScan>)
+        .def("__repr__", &ostk::core::utils::toString<TargetScan>)
```

Please confirm that `shiftToString` is indeed defined, or switch to the project-wide `toString` helper that the other bindings already use.  



<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

```suggestion
        .def("__str__", &ostk::core::utils::toString<TargetScan>)
        .def("__repr__", &ostk::core::utils::toString<TargetScan>)
```

</details>

<!-- suggestion_end -->

<details>
<summary>🤖 Prompt for AI Agents</summary>

```
In
bindings/python/src/OpenSpaceToolkitAstrodynamicsPy/Trajectory/Model/TargetScan.cpp
around lines 57 to 58, the code uses shiftToString<TargetScan> for __str__ and
__repr__, but shiftToString is not included or declared in this file, risking
compilation errors. To fix this, either include the header where shiftToString
is defined to ensure its availability or replace shiftToString<TargetScan> with
the project-wide toString<TargetScan> helper already used in other bindings,
ensuring consistent and reliable string conversion.
```

</details>

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-05-19T21:33:03Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,261 @@
+/// Apache License 2.0
+
+#ifndef __OpenSpaceToolkit_Astrodynamics_Trajectory_Model_TargetScan__
+#define __OpenSpaceToolkit_Astrodynamics_Trajectory_Model_TargetScan__
+
+#include <OpenSpaceToolkit/Physics/Environment/Object/Celestial/Earth.hpp>
+#include <OpenSpaceToolkit/Physics/Time/Instant.hpp>
+#include <OpenSpaceToolkit/Physics/Time/Interval.hpp>
+
+#include <OpenSpaceToolkit/Astrodynamics/Trajectory/Model.hpp>
+#include <OpenSpaceToolkit/Astrodynamics/Trajectory/Orbit.hpp>
+#include <OpenSpaceToolkit/Astrodynamics/Trajectory/State.hpp>
+
+namespace ostk
+{
+namespace astrodynamics
+{
+namespace trajectory
+{
+namespace model
+{
+
+using ostk::physics::coordinate::Position;
+using ostk::physics::environment::object::celestial::Earth;
+using ostk::physics::time::Duration;
+using ostk::physics::time::Instant;
+using ostk::physics::time::Interval;
+
+using ostk::astrodynamics::trajectory::Model;
+using ostk::astrodynamics::trajectory::State;
+
+/// @brief Target scanning pointing trajectory model to represent the target scanning of a spacecraft
+class TargetScan : public virtual Model
+{
+   public:
+    /// @brief Constructor
+    ///
+    /// @code{.cpp}
+    ///              LLA startLLA = LLA::Vector({0.0, 0.0, 0.0});
+    ///              LLA endLLA = LLA::Vector({1.0, 1.0, 1.0});
+    ///              Instant startInstant = Instant::DateTime(DateTime::Parse("2020-01-01 00:00:00"), Scale::UTC);
+    ///              Instant endInstant = Instant::DateTime(DateTime::Parse("2020-01-01 00:00:10"), Scale::UTC);
+    ///              TargetScan targetScanModel(startLLA, endLLA, startInstant, endInstant);
+    /// @endcode
+    ///
+    /// @param aStartLLA The starting LLA of the target scan
+    /// @param anEndLLA The ending LLA of the target scan
+    /// @param aStartInstant The starting instant of the target scan
+    /// @param anEndInstant The ending instant of the target scan
+    /// @param aCelestialObject The celestial object (default: Earth::WGS84())
+    /// @param aStepSize The step size for the target scan (default: 1 second)
+    TargetScan(
path: include/OpenSpaceToolkit/Astrodynamics/Trajectory/Model/TargetScan.hpp
user: login: coderabbitai[bot]
body: _⚠️ Potential issue_

**Documentation/default-value mismatch for `aStepSize`**

Docstring says “default: **1 second**” while the actual default in the signature is `Duration::Seconds(1e-2)` (0.01 s). This inconsistency will confuse users and downstream bindings.

```diff
-/// @param aStepSize The step size for the target scan (default: 1 second)
+/// @param aStepSize The step size for the target scan (default: 1e-2 second)
```

Make sure all occurrences (constructor + static factory + Python docstrings) stay in sync.  



<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

```suggestion
    /// @param aCelestialObject The celestial object (default: Earth::WGS84())
    /// @param aStepSize The step size for the target scan (default: 1e-2 second)
    TargetScan(
```

</details>

<!-- suggestion_end -->

<details>
<summary>🤖 Prompt for AI Agents</summary>

```
In include/OpenSpaceToolkit/Astrodynamics/Trajectory/Model/TargetScan.hpp around
lines 50 to 52, the documentation states the default for aStepSize as 1 second,
but the actual default value in the constructor signature is 0.01 seconds
(Duration::Seconds(1e-2)). Update the docstring to reflect the correct default
value of 0.01 seconds and ensure this change is consistently applied in all
related places such as static factory methods and Python docstrings to avoid
confusion.
```

</details>

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-05-19T21:33:03Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,292 @@
+/// Apache License 2.0
+
+#include <OpenSpaceToolkit/Core/Error.hpp>
+#include <OpenSpaceToolkit/Core/Utility.hpp>
+
+#include <OpenSpaceToolkit/Mathematics/CurveFitting/Interpolator.hpp>
+#include <OpenSpaceToolkit/Mathematics/Object/Vector.hpp>
+
+#include <OpenSpaceToolkit/Physics/Unit/Derived.hpp>
+
+#include <OpenSpaceToolkit/Astrodynamics/Trajectory/Model/TargetScan.hpp>
+
+namespace ostk
+{
+namespace astrodynamics
+{
+namespace trajectory
+{
+namespace model
+{
+
+using ostk::mathematics::curvefitting::Interpolator;
+using ostk::mathematics::object::MatrixXd;
+using ostk::mathematics::object::Vector3d;
+using ostk::mathematics::object::VectorXd;
+
+using ostk::physics::coordinate::Position;
+using ostk::physics::unit::Derived;
+
+TargetScan::TargetScan(
+    const LLA& aStartLLA,
+    const LLA& anEndLLA,
+    const Instant& aStartInstant,
+    const Instant& anEndInstant,
+    const Celestial& aCelestial,
+    const Duration& aStepSize
+)
+    : Model(),
+      startLLA_(aStartLLA),
+      endLLA_(anEndLLA),
+      startInstant_(aStartInstant),
+      endInstant_(anEndInstant),
+      celestialSPtr_(std::make_shared<const Celestial>(aCelestial)),
+      stepSize_(aStepSize)
+{
+}
+
+TargetScan* TargetScan::clone() const
+{
+    return new TargetScan(*this);
+}
+
+bool TargetScan::operator==(const TargetScan& aTargetScanModel) const
+{
+    if ((!this->isDefined()) || (!aTargetScanModel.isDefined()))
+    {
+        return false;
+    }
+
+    return startLLA_ == aTargetScanModel.startLLA_ && endLLA_ == aTargetScanModel.endLLA_ &&
+           startInstant_ == aTargetScanModel.startInstant_ && endInstant_ == aTargetScanModel.endInstant_ &&
+           celestialSPtr_ == aTargetScanModel.celestialSPtr_ && stepSize_ == aTargetScanModel.stepSize_;
path: src/OpenSpaceToolkit/Astrodynamics/Trajectory/Model/TargetScan.cpp
user: login: coderabbitai[bot]
body: _🛠️ Refactor suggestion_

**Equality operator compares pointers instead of celestial contents**

`celestialSPtr_ == aTargetScanModel.celestialSPtr_` succeeds only when both
objects share the *same* `shared_ptr`, not when their `Celestial` objects have
identical values. Two logically identical `TargetScan`s built with separate
copies of `Earth::WGS84()` will therefore compare **unequal**.

```diff
-           celestialSPtr_ == aTargetScanModel.celestialSPtr_ &&
+           (*celestialSPtr_ == *aTargetScanModel.celestialSPtr_) &&
```

(Assuming `Celestial` provides `operator==`; otherwise compare a stable
attribute such as `getName()`.)

Update the corresponding `!=` and the `Model`-level operators as well.



<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

```suggestion
    return startLLA_ == aTargetScanModel.startLLA_ && endLLA_ == aTargetScanModel.endLLA_ &&
           startInstant_ == aTargetScanModel.startInstant_ && endInstant_ == aTargetScanModel.endInstant_ &&
-          celestialSPtr_ == aTargetScanModel.celestialSPtr_ &&
+          (*celestialSPtr_ == *aTargetScanModel.celestialSPtr_) &&
           stepSize_ == aTargetScanModel.stepSize_;
```

</details>

<!-- suggestion_end -->

<details>
<summary>🤖 Prompt for AI Agents</summary>

```
In src/OpenSpaceToolkit/Astrodynamics/Trajectory/Model/TargetScan.cpp around
lines 60 to 62, the equality operator compares the shared_ptr celestialSPtr_
directly, which only checks if they point to the same object rather than
comparing the contents of the Celestial objects. To fix this, dereference the
shared_ptrs and compare the Celestial objects using their operator== or a stable
attribute like getName(). Also update the corresponding inequality operator and
any Model-level operators to use the same content-based comparison for
celestialSPtr_.
```

</details>

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-05-19T21:33:03Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,292 @@
+/// Apache License 2.0
+
+#include <OpenSpaceToolkit/Core/Error.hpp>
+#include <OpenSpaceToolkit/Core/Utility.hpp>
+
+#include <OpenSpaceToolkit/Mathematics/CurveFitting/Interpolator.hpp>
+#include <OpenSpaceToolkit/Mathematics/Object/Vector.hpp>
+
+#include <OpenSpaceToolkit/Physics/Unit/Derived.hpp>
+
+#include <OpenSpaceToolkit/Astrodynamics/Trajectory/Model/TargetScan.hpp>
+
+namespace ostk
+{
+namespace astrodynamics
+{
+namespace trajectory
+{
+namespace model
+{
+
+using ostk::mathematics::curvefitting::Interpolator;
+using ostk::mathematics::object::MatrixXd;
+using ostk::mathematics::object::Vector3d;
+using ostk::mathematics::object::VectorXd;
+
+using ostk::physics::coordinate::Position;
+using ostk::physics::unit::Derived;
+
+TargetScan::TargetScan(
+    const LLA& aStartLLA,
+    const LLA& anEndLLA,
+    const Instant& aStartInstant,
+    const Instant& anEndInstant,
+    const Celestial& aCelestial,
+    const Duration& aStepSize
+)
+    : Model(),
+      startLLA_(aStartLLA),
+      endLLA_(anEndLLA),
+      startInstant_(aStartInstant),
+      endInstant_(anEndInstant),
+      celestialSPtr_(std::make_shared<const Celestial>(aCelestial)),
+      stepSize_(aStepSize)
+{
+}
+
+TargetScan* TargetScan::clone() const
+{
+    return new TargetScan(*this);
+}
+
+bool TargetScan::operator==(const TargetScan& aTargetScanModel) const
+{
+    if ((!this->isDefined()) || (!aTargetScanModel.isDefined()))
+    {
+        return false;
+    }
+
+    return startLLA_ == aTargetScanModel.startLLA_ && endLLA_ == aTargetScanModel.endLLA_ &&
+           startInstant_ == aTargetScanModel.startInstant_ && endInstant_ == aTargetScanModel.endInstant_ &&
+           celestialSPtr_ == aTargetScanModel.celestialSPtr_ && stepSize_ == aTargetScanModel.stepSize_;
+}
+
+bool TargetScan::operator!=(const TargetScan& aTargetScanModel) const
+{
+    return !((*this) == aTargetScanModel);
+}
+
+std::ostream& operator<<(std::ostream& anOutputStream, const TargetScan& aTargetScanModel)
+{
+    aTargetScanModel.print(anOutputStream);
+
+    return anOutputStream;
+}
+
+bool TargetScan::isDefined() const
+{
+    return startLLA_.isDefined() && endLLA_.isDefined() && startInstant_.isDefined() && endInstant_.isDefined() &&
+           celestialSPtr_ != nullptr && celestialSPtr_->isDefined() && stepSize_.isDefined();
+}
+
+State TargetScan::calculateStateAt(const Instant& anInstant) const
+{
+    if (!anInstant.isDefined())
+    {
+        throw ostk::core::error::runtime::Undefined("Instant");
+    }
+
+    if (!this->isDefined())
+    {
+        throw ostk::core::error::runtime::Undefined("TargetScan");
+    }
+
+    if (anInstant < startInstant_ || anInstant > endInstant_)
+    {
+        throw ostk::core::error::RuntimeError("Instant is outside the interval.");
+    }
+
+    // interpolate the velocity using polynomial interpolation
+
+    const Array<Instant> instants = {
+        anInstant - stepSize_ * 2,
+        anInstant - stepSize_,
+        anInstant,
+        anInstant + stepSize_,
+        anInstant + stepSize_ * 2,
+    };
+
+    VectorXd times(instants.getSize());
+    MatrixXd positionCoordinates(3, instants.getSize());
+
+    Position position = Position::Undefined();
+
+    const Duration intervalDuration = endInstant_ - startInstant_;
+    const Length scanDistance =
+        startLLA_.calculateDistanceTo(endLLA_, celestialSPtr_->getEquatorialRadius(), celestialSPtr_->getFlattening());
+    const Real scanSpeed = scanDistance.inMeters() / intervalDuration.inSeconds();
+
+    const Pair<Angle, Angle> azimuthAngles =
+        startLLA_.calculateAzimuthTo(endLLA_, celestialSPtr_->getEquatorialRadius(), celestialSPtr_->getFlattening());
+
+    Size i = 0;
+    for (const auto& instant : instants)
+    {
+        times(i) = (instant - anInstant).inSeconds();
+
+        const Length forwardDistance = Length::Meters(scanSpeed * (instant - startInstant_).inSeconds());
+
+        const LLA lla = LLA::Forward(
+            startLLA_,
+            azimuthAngles.first,
+            forwardDistance,
+            celestialSPtr_->getEquatorialRadius(),
+            celestialSPtr_->getFlattening()
+        );
+
+        const physics::coordinate::Position positionAtInstant =
+            physics::coordinate::Position::FromLLA(lla, celestialSPtr_).inFrame(Frame::GCRF(), instant);
+
+        if (instant == anInstant)
+        {
+            position = positionAtInstant;
+        }
+
+        positionCoordinates.col(i) = positionAtInstant.getCoordinates();
+
+        ++i;
+    }
+
+    Array<Shared<const Interpolator>> interpolators = {
+        Interpolator::GenerateInterpolator(Interpolator::Type::CubicSpline, times, positionCoordinates.row(0)),
+        Interpolator::GenerateInterpolator(Interpolator::Type::CubicSpline, times, positionCoordinates.row(1)),
+        Interpolator::GenerateInterpolator(Interpolator::Type::CubicSpline, times, positionCoordinates.row(2)),
+    };
+
+    const Vector3d velocityCoordinates = {
+        interpolators[0]->computeDerivative(0.0),
+        interpolators[1]->computeDerivative(0.0),
+        interpolators[2]->computeDerivative(0.0),
+    };
+
+    const Velocity velocity = Velocity::MetersPerSecond(velocityCoordinates, Frame::GCRF());
+
+    return State(anInstant, position, velocity);
+}
+
+LLA TargetScan::getStartLLA() const
+{
+    return startLLA_;
+}
+
+LLA TargetScan::getEndLLA() const
+{
+    return endLLA_;
+}
+
+Instant TargetScan::getStartInstant() const
+{
+    return startInstant_;
+}
+
+Instant TargetScan::getEndInstant() const
+{
+    return endInstant_;
+}
+
+Celestial TargetScan::getCelestial() const
+{
+    return *celestialSPtr_;
+}
+
+Duration TargetScan::getStepSize() const
+{
+    return stepSize_;
+}
+
+void TargetScan::print(std::ostream& anOutputStream, bool displayDecorator) const
+{
+    using ostk::core::type::String;
+
+    displayDecorator ? ostk::core::utils::Print::Header(anOutputStream, "TargetScan") : void();
+
+    ostk::core::utils::Print::Line(anOutputStream)
+        << "Start LLA:" << (startLLA_.isDefined() ? startLLA_.toString() : "Undefined");
+
+    ostk::core::utils::Print::Line(anOutputStream)
+        << "End LLA:" << (endLLA_.isDefined() ? endLLA_.toString() : "Undefined");
+
+    ostk::core::utils::Print::Line(anOutputStream)
+        << "Start Instant:" << (startInstant_.isDefined() ? startInstant_.toString() : "Undefined");
+
+    ostk::core::utils::Print::Line(anOutputStream)
+        << "End Instant:" << (endInstant_.isDefined() ? endInstant_.toString() : "Undefined");
+
+    ostk::core::utils::Print::Line(anOutputStream)
+        << "Celestial:"
+        << (celestialSPtr_ != nullptr && celestialSPtr_->isDefined() ? celestialSPtr_->getName() : "Undefined");
+
+    ostk::core::utils::Print::Line(anOutputStream)
+        << "Step Size:" << (stepSize_.isDefined() ? stepSize_.toString() : "Undefined");
+
+    displayDecorator ? ostk::core::utils::Print::Footer(anOutputStream) : void();
+}
+
+bool TargetScan::operator==(const Model& aModel) const
+{
+    const TargetScan* staticModelPtr = dynamic_cast<const TargetScan*>(&aModel);
+
+    return (staticModelPtr != nullptr) && this->operator==(*staticModelPtr);
+}
+
+bool TargetScan::operator!=(const Model& aModel) const
+{
+    return !((*this) == aModel);
+}
+
+TargetScan TargetScan::FromGroundSpeed(
+    const LLA& aStartLLA,
+    const LLA& anEndLLA,
+    const Derived& aGroundSpeed,
+    const Instant& aStartInstant,
+    const Celestial& aCelestial,
+    const Duration& aStepSize
+)
+{
+    if (!aStartLLA.isDefined())
+    {
+        throw ostk::core::error::runtime::Undefined("Start LLA");
+    }
+
+    if (!anEndLLA.isDefined())
+    {
+        throw ostk::core::error::runtime::Undefined("End LLA");
+    }
+
+    if (!aGroundSpeed.isDefined())
+    {
+        throw ostk::core::error::runtime::Undefined("Ground speed");
+    }
+
+    if (!aStartInstant.isDefined())
+    {
+        throw ostk::core::error::runtime::Undefined("Start instant");
+    }
+
+    if (!aStepSize.isDefined())
+    {
+        throw ostk::core::error::runtime::Undefined("Step size");
+    }
+
+    if (aStepSize.inSeconds() <= 0.0)
+    {
+        throw ostk::core::error::RuntimeError("Step size must be positive.");
+    }
+
+    const Real groundSpeedMps = aGroundSpeed.in(Derived::Unit::MeterPerSecond());
+
+    const Length distance =
+        aStartLLA.calculateDistanceTo(anEndLLA, aCelestial.getEquatorialRadius(), aCelestial.getFlattening());
+
+    const Duration duration = Duration::Seconds(distance.inMeters() / groundSpeedMps);
path: src/OpenSpaceToolkit/Astrodynamics/Trajectory/Model/TargetScan.cpp
user: login: coderabbitai[bot]
body: _⚠️ Potential issue_

**`FromGroundSpeed` does not validate positive ground speed**

A zero or negative ground speed produces an infinite/negative duration and an
invalid `endInstant`. Add an explicit check:

```diff
+if (aGroundSpeed.in(Derived::Unit::MeterPerSecond()) <= 0.0)
+{
+    throw ostk::core::error::RuntimeError("Ground speed must be strictly positive.");
+}
```

This mirrors the existing validation for a positive step size.



<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

```suggestion
    if (aGroundSpeed.in(Derived::Unit::MeterPerSecond()) <= 0.0)
    {
        throw ostk::core::error::RuntimeError("Ground speed must be strictly positive.");
    }

    const Real groundSpeedMps = aGroundSpeed.in(Derived::Unit::MeterPerSecond());

    const Length distance =
        aStartLLA.calculateDistanceTo(anEndLLA, aCelestial.getEquatorialRadius(), aCelestial.getFlattening());

    const Duration duration = Duration::Seconds(distance.inMeters() / groundSpeedMps);
```

</details>

<!-- suggestion_end -->

<details>
<summary>🤖 Prompt for AI Agents</summary>

```
In src/OpenSpaceToolkit/Astrodynamics/Trajectory/Model/TargetScan.cpp around
lines 277 to 282, the code does not validate that the ground speed is positive
before calculating duration, which can lead to infinite or negative duration
values. Add an explicit check to ensure that groundSpeedMps is greater than zero
before proceeding with the duration calculation, and handle the case where it is
zero or negative by throwing an exception or returning an error, similar to the
existing validation for positive step size.
```

</details>

<!-- This is an auto-generated comment by CodeRabbit -->

✅ Addressed in commit 1bacc7e
created_at: 2025-05-19T21:33:03Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,262 @@
+/// Apache License 2.0
+
+#ifndef __OpenSpaceToolkit_Astrodynamics_Trajectory_Model_TargetScan__
+#define __OpenSpaceToolkit_Astrodynamics_Trajectory_Model_TargetScan__
+
+#include <OpenSpaceToolkit/Physics/Environment/Object/Celestial/Earth.hpp>
+#include <OpenSpaceToolkit/Physics/Time/Instant.hpp>
+#include <OpenSpaceToolkit/Physics/Time/Interval.hpp>
+
+#include <OpenSpaceToolkit/Astrodynamics/Trajectory/Model.hpp>
+#include <OpenSpaceToolkit/Astrodynamics/Trajectory/Orbit.hpp>
+#include <OpenSpaceToolkit/Astrodynamics/Trajectory/State.hpp>
+
+namespace ostk
+{
+namespace astrodynamics
+{
+namespace trajectory
+{
+namespace model
+{
+
+using ostk::physics::coordinate::Position;
+using ostk::physics::environment::object::celestial::Earth;
+using ostk::physics::time::Duration;
+using ostk::physics::time::Instant;
+using ostk::physics::time::Interval;
+
+using ostk::astrodynamics::trajectory::Model;
+using ostk::astrodynamics::trajectory::State;
+
+/// @brief Target scanning pointing trajectory model to represent the target scanning of a spacecraft
+class TargetScan : public virtual Model
+{
+   public:
+    /// @brief Constructor
+    ///
+    /// @code{.cpp}
+    ///              LLA startLLA = LLA::Vector({0.0, 0.0, 0.0});
+    ///              LLA endLLA = LLA::Vector({1.0, 1.0, 1.0});
+    ///              Instant startInstant = Instant::DateTime(DateTime::Parse("2020-01-01 00:00:00"), Scale::UTC);
+    ///              Instant endInstant = Instant::DateTime(DateTime::Parse("2020-01-01 00:00:10"), Scale::UTC);
+    ///              TargetScan targetScanModel(startLLA, endLLA, startInstant, endInstant);
+    /// @endcode
+    ///
+    /// @param aStartLLA The starting LLA of the target scan
+    /// @param anEndLLA The ending LLA of the target scan
+    /// @param aStartInstant The starting instant of the target scan
+    /// @param anEndInstant The ending instant of the target scan
+    /// @param aCelestialObject The celestial object (default: Earth::WGS84())
+    /// @param aStepSize The step size for the target scan (default: 1e-2 second)
+    TargetScan(
+        const LLA& aStartLLA,
+        const LLA& anEndLLA,
+        const Instant& aStartInstant,
+        const Instant& anEndInstant,
+        const Celestial& aCelestialObject = Earth::WGS84(),
+        const Duration& aStepSize = Duration::Seconds(1e-2)
+    );
+
+    /// @brief Clone the static model
+    ///
+    /// @code{.cpp}
+    ///              TargetScan targetScanModel = { ... };
+    ///              TargetScan* clonedModel = targetScanModel.clone();
+    /// @endcode
+    ///
+    /// @return A pointer to the cloned static model
+    virtual TargetScan* clone() const override;
+
+    /// @brief Get the starting LLA of the target scan
+    ///
+    /// @code{.cpp}
+    ///              TargetScan targetScanModel = { ... };
+    ///              LLA startLLA = targetScanModel.getStartLLA();
+    /// @endcode
+    ///
+    /// @return The starting LLA of the target scan
+    LLA getStartLLA() const;
+
+    /// @brief Get the ending LLA of the target scan
+    ///
+    /// @code{.cpp}
+    ///              TargetScan targetScanModel = { ... };
+    ///              LLA endLLA = targetScanModel.getEndLLA();
+    /// @endcode
+    ///
+    /// @return The ending LLA of the target scan
+    LLA getEndLLA() const;
+
+    /// @brief Get the starting instant of the target scan
+    ///
+    /// @code{.cpp}
+    ///              TargetScan targetScanModel = { ... };
+    ///              Instant startInstant = targetScanModel.getStartInstant();
+    /// @endcode
+    ///
+    /// @return The starting instant of the target scan
+    Instant getStartInstant() const;
+
+    /// @brief Get the ending instant of the target scan
+    ///
+    /// @code{.cpp}
+    ///              TargetScan targetScanModel = { ... };
+    ///              Instant endInstant = targetScanModel.getEndInstant();
+    /// @endcode
+    ///
+    /// @return The ending instant of the target scan
+    Instant getEndInstant() const;
+
+    /// @brief Get the celestial object of the target scan
+    ///
+    /// @code{.cpp}
+    ///              TargetScan targetScanModel = { ... };
+    ///              Celestial celestialObject = targetScanModel.getCelestial();
+    /// @endcode
+    ///
+    /// @return The celestial object of the target scan
+    Celestial getCelestial() const;
+
+    /// @brief Get the step size of the target scan
+    ///
+    /// @code{.cpp}
+    ///              TargetScan targetScanModel = { ... };
+    ///              Duration stepSize = targetScanModel.getStepSize();
+    /// @endcode
+    ///
+    /// @return The step size of the target scan
+    Duration getStepSize() const;
+
+    /// @brief Equality operator
+    ///
+    /// @code{.cpp}
+    ///              TargetScan targetScanModel1 = { ... };
+    ///              TargetScan targetScanModel2 = { ... };
+    ///              bool isEqual = (targetScanModel1 == targetScanModel2);
+    /// @endcode
+    ///
+    /// @param aTargetScanModel The static model to compare with
+    /// @return True if the models are equal, false otherwise
+    bool operator==(const TargetScan& aTargetScanModel) const;
+
+    /// @brief Inequality operator
+    ///
+    /// @code{.cpp}
+    ///              TargetScan targetScanModel1 = { ... };
+    ///              TargetScan targetScanModel2 = { ... };
+    ///              bool isNotEqual = (targetScanModel1 != targetScanModel2);
+    /// @endcode
+    ///
+    /// @param aTargetScanModel The static model to compare with
+    /// @return True if the models are not equal, false otherwise
+    bool operator!=(const TargetScan& aTargetScanModel) const;
+
+    /// @brief Output stream operator
+    ///
+    /// @code{.cpp}
+    ///              TargetScan targetScanModel = { ... };
+    ///              std::cout << targetScanModel;
+    /// @endcode
+    ///
+    /// @param anOutputStream The output stream
+    /// @param aTargetScanModel The static model to output
+    /// @return The output stream
+    friend std::ostream& operator<<(std::ostream& anOutputStream, const TargetScan& aTargetScanModel);
+
+    /// @brief Check if the static model is defined
+    ///
+    /// @code{.cpp}
+    ///              TargetScan targetScanModel = { ... };
+    ///              bool isDefined = targetScanModel.isDefined();
+    /// @endcode
+    ///
+    /// @return True if the model is defined, false otherwise
+    virtual bool isDefined() const override;
+
+    /// @brief Calculate the state at a given instant
+    ///
+    /// @code{.cpp}
+    ///              TargetScan targetScanModel = { ... };
+    ///              Instant instant = { ... };
+    ///              State state = targetScanModel.calculateStateAt(instant);
+    /// @endcode
+    ///
+    /// @param anInstant The instant at which to calculate the state
+    /// @return The state at the given instant
+    virtual State calculateStateAt(const Instant& anInstant) const override;
+
+    /// @brief Print the static model to an output stream
+    ///
+    /// @code{.cpp}
+    ///              TargetScan targetScanModel = { ... };
+    ///              targetScanModel.print(std::cout, true);
+    /// @endcode
+    ///
+    /// @param anOutputStream The output stream
+    /// @param displayDecorator If true, display decorator
+    virtual void print(std::ostream& anOutputStream, bool displayDecorator = true) const override;
+
+    /// @brief Static method to create a TargetScan from ground speed. Computes the end instant based on the ground
+    /// speed, and the distance between the start and end LLA.
+    ///
+    /// @code{.cpp}
+    ///              TargetScan targetScanModel = TargetScan::FromGroundSpeed(startLLA, endLLA, groundSpeed,
+    ///              startInstant);
+    /// @endcode
+    ///
+    /// @param aStartLLA The starting LLA of the target scan
+    /// @param anEndLLA The ending LLA of the target scan
+    /// @param aGroundSpeed The ground speed of the target scan
+    /// @param aStartInstant The starting instant of the target scan
+    /// @param aCelestialObject The celestial object (default: Earth::WGS84())
+    /// @param aStepSize The step size for the target scan (default: 1e-2 second)
+    static TargetScan FromGroundSpeed(
+        const LLA& aStartLLA,
+        const LLA& anEndLLA,
+        const Derived& aGroundSpeed,
+        const Instant& aStartInstant,
+        const Celestial& aCelestialObject = Earth::WGS84(),
+        const Duration& aStepSize = Duration::Seconds(1e-2)
+    );
+
+   protected:
+    /// @brief Equality operator for Model base class
+    ///
+    /// @code{.cpp}
+    ///              TargetScan targetScanModel = { ... };
+    ///              Model model = { ... };
+    ///              bool isEqual = (targetScanModel == model);
+    /// @endcode
+    ///
+    /// @param aModel The model to compare with
+    /// @return True if the models are equal, false otherwise
+    virtual bool operator==(const Model& aModel) const override;
+
+    /// @brief Inequality operator for Model base class
+    ///
+    /// @code{.cpp}
+    ///              TargetScan targetScanModel = { ... };
+    ///              Model model = { ... };
+    ///              bool isNotEqual = (targetScanModel != model);
+    /// @endcode
+    ///
+    /// @param aModel The model to compare with
+    /// @return True if the models are not equal, false otherwise
+    virtual bool operator!=(const Model& aModel) const override;
+
+   private:
+    LLA startLLA_;
+    LLA endLLA_;
+    Instant startInstant_;
+    Instant endInstant_;
+    Shared<const Celestial> celestialSPtr_;
+    Duration stepSize_;
path: include/OpenSpaceToolkit/Astrodynamics/Trajectory/Model/TargetScan.hpp
user: login: coderabbitai[bot]
body: _🛠️ Refactor suggestion_

**Consider adding validation for trajectory parameters.**

The class doesn't appear to validate that the start instant is before the end instant or that the LLA coordinates are valid. Consider adding validation in the constructor and documenting the behavior for invalid inputs.

<details>
<summary>🤖 Prompt for AI Agents</summary>

```
In include/OpenSpaceToolkit/Astrodynamics/Trajectory/Model/TargetScan.hpp at
line 254, add validation logic in the constructor to ensure the start instant is
before the end instant and that the LLA coordinates are within valid ranges.
Implement checks that throw exceptions or handle errors if these conditions are
not met, and update the documentation to clearly describe the expected input
constraints and behavior on invalid inputs.
```

</details>

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-05-19T22:05:23Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,262 @@
+/// Apache License 2.0
+
+#ifndef __OpenSpaceToolkit_Astrodynamics_Trajectory_Model_TargetScan__
+#define __OpenSpaceToolkit_Astrodynamics_Trajectory_Model_TargetScan__
+
+#include <OpenSpaceToolkit/Physics/Environment/Object/Celestial/Earth.hpp>
+#include <OpenSpaceToolkit/Physics/Time/Instant.hpp>
+#include <OpenSpaceToolkit/Physics/Time/Interval.hpp>
+
+#include <OpenSpaceToolkit/Astrodynamics/Trajectory/Model.hpp>
+#include <OpenSpaceToolkit/Astrodynamics/Trajectory/Orbit.hpp>
+#include <OpenSpaceToolkit/Astrodynamics/Trajectory/State.hpp>
+
+namespace ostk
+{
+namespace astrodynamics
+{
+namespace trajectory
+{
+namespace model
+{
+
+using ostk::physics::coordinate::Position;
+using ostk::physics::environment::object::celestial::Earth;
+using ostk::physics::time::Duration;
+using ostk::physics::time::Instant;
+using ostk::physics::time::Interval;
+
+using ostk::astrodynamics::trajectory::Model;
+using ostk::astrodynamics::trajectory::State;
+
+/// @brief Target scanning pointing trajectory model to represent the target scanning of a spacecraft
+class TargetScan : public virtual Model
+{
+   public:
+    /// @brief Constructor
+    ///
+    /// @code{.cpp}
+    ///              LLA startLLA = LLA::Vector({0.0, 0.0, 0.0});
+    ///              LLA endLLA = LLA::Vector({1.0, 1.0, 1.0});
+    ///              Instant startInstant = Instant::DateTime(DateTime::Parse("2020-01-01 00:00:00"), Scale::UTC);
+    ///              Instant endInstant = Instant::DateTime(DateTime::Parse("2020-01-01 00:00:10"), Scale::UTC);
+    ///              TargetScan targetScanModel(startLLA, endLLA, startInstant, endInstant);
+    /// @endcode
+    ///
+    /// @param aStartLLA The starting LLA of the target scan
+    /// @param anEndLLA The ending LLA of the target scan
+    /// @param aStartInstant The starting instant of the target scan
+    /// @param anEndInstant The ending instant of the target scan
+    /// @param aCelestialObject The celestial object (default: Earth::WGS84())
+    /// @param aStepSize The step size for the target scan (default: 1e-2 second)
+    TargetScan(
+        const LLA& aStartLLA,
+        const LLA& anEndLLA,
+        const Instant& aStartInstant,
+        const Instant& anEndInstant,
+        const Celestial& aCelestialObject = Earth::WGS84(),
+        const Duration& aStepSize = Duration::Seconds(1e-2)
+    );
+
+    /// @brief Clone the static model
+    ///
+    /// @code{.cpp}
+    ///              TargetScan targetScanModel = { ... };
+    ///              TargetScan* clonedModel = targetScanModel.clone();
+    /// @endcode
+    ///
+    /// @return A pointer to the cloned static model
+    virtual TargetScan* clone() const override;
+
+    /// @brief Get the starting LLA of the target scan
+    ///
+    /// @code{.cpp}
+    ///              TargetScan targetScanModel = { ... };
+    ///              LLA startLLA = targetScanModel.getStartLLA();
+    /// @endcode
+    ///
+    /// @return The starting LLA of the target scan
+    LLA getStartLLA() const;
+
+    /// @brief Get the ending LLA of the target scan
+    ///
+    /// @code{.cpp}
+    ///              TargetScan targetScanModel = { ... };
+    ///              LLA endLLA = targetScanModel.getEndLLA();
+    /// @endcode
+    ///
+    /// @return The ending LLA of the target scan
+    LLA getEndLLA() const;
+
+    /// @brief Get the starting instant of the target scan
+    ///
+    /// @code{.cpp}
+    ///              TargetScan targetScanModel = { ... };
+    ///              Instant startInstant = targetScanModel.getStartInstant();
+    /// @endcode
+    ///
+    /// @return The starting instant of the target scan
+    Instant getStartInstant() const;
+
+    /// @brief Get the ending instant of the target scan
+    ///
+    /// @code{.cpp}
+    ///              TargetScan targetScanModel = { ... };
+    ///              Instant endInstant = targetScanModel.getEndInstant();
+    /// @endcode
+    ///
+    /// @return The ending instant of the target scan
+    Instant getEndInstant() const;
+
+    /// @brief Get the celestial object of the target scan
+    ///
+    /// @code{.cpp}
+    ///              TargetScan targetScanModel = { ... };
+    ///              Celestial celestialObject = targetScanModel.getCelestial();
+    /// @endcode
+    ///
+    /// @return The celestial object of the target scan
+    Celestial getCelestial() const;
+
+    /// @brief Get the step size of the target scan
+    ///
+    /// @code{.cpp}
+    ///              TargetScan targetScanModel = { ... };
+    ///              Duration stepSize = targetScanModel.getStepSize();
+    /// @endcode
+    ///
+    /// @return The step size of the target scan
+    Duration getStepSize() const;
+
+    /// @brief Equality operator
+    ///
+    /// @code{.cpp}
+    ///              TargetScan targetScanModel1 = { ... };
+    ///              TargetScan targetScanModel2 = { ... };
+    ///              bool isEqual = (targetScanModel1 == targetScanModel2);
+    /// @endcode
+    ///
+    /// @param aTargetScanModel The static model to compare with
+    /// @return True if the models are equal, false otherwise
+    bool operator==(const TargetScan& aTargetScanModel) const;
+
+    /// @brief Inequality operator
+    ///
+    /// @code{.cpp}
+    ///              TargetScan targetScanModel1 = { ... };
+    ///              TargetScan targetScanModel2 = { ... };
+    ///              bool isNotEqual = (targetScanModel1 != targetScanModel2);
+    /// @endcode
+    ///
+    /// @param aTargetScanModel The static model to compare with
+    /// @return True if the models are not equal, false otherwise
+    bool operator!=(const TargetScan& aTargetScanModel) const;
+
+    /// @brief Output stream operator
+    ///
+    /// @code{.cpp}
+    ///              TargetScan targetScanModel = { ... };
+    ///              std::cout << targetScanModel;
+    /// @endcode
+    ///
+    /// @param anOutputStream The output stream
+    /// @param aTargetScanModel The static model to output
+    /// @return The output stream
+    friend std::ostream& operator<<(std::ostream& anOutputStream, const TargetScan& aTargetScanModel);
+
+    /// @brief Check if the static model is defined
+    ///
+    /// @code{.cpp}
+    ///              TargetScan targetScanModel = { ... };
+    ///              bool isDefined = targetScanModel.isDefined();
+    /// @endcode
+    ///
+    /// @return True if the model is defined, false otherwise
+    virtual bool isDefined() const override;
+
+    /// @brief Calculate the state at a given instant
+    ///
+    /// @code{.cpp}
+    ///              TargetScan targetScanModel = { ... };
+    ///              Instant instant = { ... };
+    ///              State state = targetScanModel.calculateStateAt(instant);
+    /// @endcode
+    ///
+    /// @param anInstant The instant at which to calculate the state
+    /// @return The state at the given instant
+    virtual State calculateStateAt(const Instant& anInstant) const override;
+
+    /// @brief Print the static model to an output stream
+    ///
+    /// @code{.cpp}
+    ///              TargetScan targetScanModel = { ... };
+    ///              targetScanModel.print(std::cout, true);
+    /// @endcode
+    ///
+    /// @param anOutputStream The output stream
+    /// @param displayDecorator If true, display decorator
+    virtual void print(std::ostream& anOutputStream, bool displayDecorator = true) const override;
+
+    /// @brief Static method to create a TargetScan from ground speed. Computes the end instant based on the ground
+    /// speed, and the distance between the start and end LLA.
+    ///
+    /// @code{.cpp}
+    ///              TargetScan targetScanModel = TargetScan::FromGroundSpeed(startLLA, endLLA, groundSpeed,
+    ///              startInstant);
+    /// @endcode
+    ///
+    /// @param aStartLLA The starting LLA of the target scan
+    /// @param anEndLLA The ending LLA of the target scan
+    /// @param aGroundSpeed The ground speed of the target scan
+    /// @param aStartInstant The starting instant of the target scan
+    /// @param aCelestialObject The celestial object (default: Earth::WGS84())
+    /// @param aStepSize The step size for the target scan (default: 1e-2 second)
+    static TargetScan FromGroundSpeed(
+        const LLA& aStartLLA,
+        const LLA& anEndLLA,
+        const Derived& aGroundSpeed,
path: include/OpenSpaceToolkit/Astrodynamics/Trajectory/Model/TargetScan.hpp
user: login: coderabbitai[bot]
body: _⚠️ Potential issue_

**Undefined type `Derived` used as parameter.**

The `Derived` type is used for the `aGroundSpeed` parameter but isn't defined in the visible code or included via using statements. This could lead to compilation errors.


```diff
-    static TargetScan FromGroundSpeed(
-        const LLA& aStartLLA,
-        const LLA& anEndLLA,
-        const Derived& aGroundSpeed,
+    static TargetScan FromGroundSpeed(
+        const LLA& aStartLLA,
+        const LLA& anEndLLA,
+        const Real& aGroundSpeed,
```
Alternatively, include the appropriate `using` statement for the `Derived` type if it should be retained.


<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

```suggestion
    static TargetScan FromGroundSpeed(
        const LLA& aStartLLA,
        const LLA& anEndLLA,
        const Real& aGroundSpeed,
```

</details>

<!-- suggestion_end -->

<details>
<summary>🤖 Prompt for AI Agents</summary>

```
In include/OpenSpaceToolkit/Astrodynamics/Trajectory/Model/TargetScan.hpp at
line 217, the parameter type 'Derived' is used but not defined or included,
causing potential compilation errors. To fix this, either define the 'Derived'
type in the current scope or add the necessary 'using' statement or include
directive that brings 'Derived' into scope before its usage.
```

</details>

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-05-19T22:05:23Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,297 @@
+/// Apache License 2.0
+
+#include <OpenSpaceToolkit/Core/Error.hpp>
+#include <OpenSpaceToolkit/Core/Utility.hpp>
+
+#include <OpenSpaceToolkit/Mathematics/CurveFitting/Interpolator.hpp>
+#include <OpenSpaceToolkit/Mathematics/Object/Vector.hpp>
+
+#include <OpenSpaceToolkit/Physics/Unit/Derived.hpp>
+
+#include <OpenSpaceToolkit/Astrodynamics/Trajectory/Model/TargetScan.hpp>
+
+namespace ostk
+{
+namespace astrodynamics
+{
+namespace trajectory
+{
+namespace model
+{
+
+using ostk::mathematics::curvefitting::Interpolator;
+using ostk::mathematics::object::MatrixXd;
+using ostk::mathematics::object::Vector3d;
+using ostk::mathematics::object::VectorXd;
+
+using ostk::physics::coordinate::Position;
+using ostk::physics::unit::Derived;
+
+TargetScan::TargetScan(
+    const LLA& aStartLLA,
+    const LLA& anEndLLA,
+    const Instant& aStartInstant,
+    const Instant& anEndInstant,
+    const Celestial& aCelestial,
+    const Duration& aStepSize
+)
+    : Model(),
+      startLLA_(aStartLLA),
+      endLLA_(anEndLLA),
+      startInstant_(aStartInstant),
+      endInstant_(anEndInstant),
+      celestialSPtr_(std::make_shared<const Celestial>(aCelestial)),
+      stepSize_(aStepSize)
+{
+}
+
+TargetScan* TargetScan::clone() const
+{
+    return new TargetScan(*this);
+}
+
+bool TargetScan::operator==(const TargetScan& aTargetScanModel) const
+{
+    if ((!this->isDefined()) || (!aTargetScanModel.isDefined()))
+    {
+        return false;
+    }
+
+    return startLLA_ == aTargetScanModel.startLLA_ && endLLA_ == aTargetScanModel.endLLA_ &&
+           startInstant_ == aTargetScanModel.startInstant_ && endInstant_ == aTargetScanModel.endInstant_ &&
+           celestialSPtr_ == aTargetScanModel.celestialSPtr_ && stepSize_ == aTargetScanModel.stepSize_;
+}
+
+bool TargetScan::operator!=(const TargetScan& aTargetScanModel) const
+{
+    return !((*this) == aTargetScanModel);
+}
+
+std::ostream& operator<<(std::ostream& anOutputStream, const TargetScan& aTargetScanModel)
+{
+    aTargetScanModel.print(anOutputStream);
+
+    return anOutputStream;
+}
+
+bool TargetScan::isDefined() const
+{
+    return startLLA_.isDefined() && endLLA_.isDefined() && startInstant_.isDefined() && endInstant_.isDefined() &&
+           celestialSPtr_ != nullptr && celestialSPtr_->isDefined() && stepSize_.isDefined();
+}
+
+State TargetScan::calculateStateAt(const Instant& anInstant) const
+{
+    if (!anInstant.isDefined())
+    {
+        throw ostk::core::error::runtime::Undefined("Instant");
+    }
+
+    if (!this->isDefined())
+    {
+        throw ostk::core::error::runtime::Undefined("TargetScan");
+    }
+
+    if (anInstant < startInstant_ || anInstant > endInstant_)
+    {
+        throw ostk::core::error::RuntimeError("Instant is outside the interval.");
+    }
+
+    // interpolate the velocity using polynomial interpolation
+
+    const Array<Instant> instants = {
+        anInstant - stepSize_ * 2,
+        anInstant - stepSize_,
+        anInstant,
+        anInstant + stepSize_,
+        anInstant + stepSize_ * 2,
+    };
+
+    VectorXd times(instants.getSize());
+    MatrixXd positionCoordinates(3, instants.getSize());
+
+    Position position = Position::Undefined();
+
+    const Duration intervalDuration = endInstant_ - startInstant_;
+    const Length scanDistance =
+        startLLA_.calculateDistanceTo(endLLA_, celestialSPtr_->getEquatorialRadius(), celestialSPtr_->getFlattening());
+    const Real scanSpeed = scanDistance.inMeters() / intervalDuration.inSeconds();
+
+    const Pair<Angle, Angle> azimuthAngles =
+        startLLA_.calculateAzimuthTo(endLLA_, celestialSPtr_->getEquatorialRadius(), celestialSPtr_->getFlattening());
+
+    Size i = 0;
+    for (const auto& instant : instants)
+    {
+        times(i) = (instant - anInstant).inSeconds();
+
+        const Length forwardDistance = Length::Meters(scanSpeed * (instant - startInstant_).inSeconds());
+
+        const LLA lla = LLA::Forward(
+            startLLA_,
+            azimuthAngles.first,
+            forwardDistance,
+            celestialSPtr_->getEquatorialRadius(),
+            celestialSPtr_->getFlattening()
+        );
+
+        const physics::coordinate::Position positionAtInstant =
+            physics::coordinate::Position::FromLLA(lla, celestialSPtr_).inFrame(Frame::GCRF(), instant);
+
+        if (instant == anInstant)
+        {
+            position = positionAtInstant;
+        }
+
+        positionCoordinates.col(i) = positionAtInstant.getCoordinates();
+
+        ++i;
+    }
+
+    Array<Shared<const Interpolator>> interpolators = {
+        Interpolator::GenerateInterpolator(Interpolator::Type::CubicSpline, times, positionCoordinates.row(0)),
+        Interpolator::GenerateInterpolator(Interpolator::Type::CubicSpline, times, positionCoordinates.row(1)),
+        Interpolator::GenerateInterpolator(Interpolator::Type::CubicSpline, times, positionCoordinates.row(2)),
+    };
+
+    const Vector3d velocityCoordinates = {
+        interpolators[0]->computeDerivative(0.0),
+        interpolators[1]->computeDerivative(0.0),
+        interpolators[2]->computeDerivative(0.0),
+    };
+
+    const Velocity velocity = Velocity::MetersPerSecond(velocityCoordinates, Frame::GCRF());
+
+    return State(anInstant, position, velocity);
+}
+
+LLA TargetScan::getStartLLA() const
+{
+    return startLLA_;
+}
+
+LLA TargetScan::getEndLLA() const
+{
+    return endLLA_;
+}
+
+Instant TargetScan::getStartInstant() const
+{
+    return startInstant_;
+}
+
+Instant TargetScan::getEndInstant() const
+{
+    return endInstant_;
+}
+
+Celestial TargetScan::getCelestial() const
+{
+    return *celestialSPtr_;
+}
+
+Duration TargetScan::getStepSize() const
+{
+    return stepSize_;
+}
+
+void TargetScan::print(std::ostream& anOutputStream, bool displayDecorator) const
+{
+    using ostk::core::type::String;
+
+    displayDecorator ? ostk::core::utils::Print::Header(anOutputStream, "TargetScan") : void();
+
+    ostk::core::utils::Print::Line(anOutputStream)
+        << "Start LLA:" << (startLLA_.isDefined() ? startLLA_.toString() : "Undefined");
+
+    ostk::core::utils::Print::Line(anOutputStream)
+        << "End LLA:" << (endLLA_.isDefined() ? endLLA_.toString() : "Undefined");
+
+    ostk::core::utils::Print::Line(anOutputStream)
+        << "Start Instant:" << (startInstant_.isDefined() ? startInstant_.toString() : "Undefined");
+
+    ostk::core::utils::Print::Line(anOutputStream)
+        << "End Instant:" << (endInstant_.isDefined() ? endInstant_.toString() : "Undefined");
+
+    ostk::core::utils::Print::Line(anOutputStream)
+        << "Celestial:"
+        << (celestialSPtr_ != nullptr && celestialSPtr_->isDefined() ? celestialSPtr_->getName() : "Undefined");
+
+    ostk::core::utils::Print::Line(anOutputStream)
+        << "Step Size:" << (stepSize_.isDefined() ? stepSize_.toString() : "Undefined");
+
+    displayDecorator ? ostk::core::utils::Print::Footer(anOutputStream) : void();
+}
+
+bool TargetScan::operator==(const Model& aModel) const
+{
+    const TargetScan* staticModelPtr = dynamic_cast<const TargetScan*>(&aModel);
+
+    return (staticModelPtr != nullptr) && this->operator==(*staticModelPtr);
+}
path: src/OpenSpaceToolkit/Astrodynamics/Trajectory/Model/TargetScan.cpp
user: login: coderabbitai[bot]
body: _⚠️ Potential issue_

**Fix Model-level equality operator to compare Celestial objects**

Similar to the TargetScan-specific equality operator, this operator inherits the same issue when comparing celestial objects.


The problem is indirectly inherited from the TargetScan-specific equality operator that's being called. Fixing the issue in the TargetScan-specific operator will address this as well.

<details>
<summary>🤖 Prompt for AI Agents</summary>

```
In src/OpenSpaceToolkit/Astrodynamics/Trajectory/Model/TargetScan.cpp around
lines 226 to 231, the Model-level equality operator calls the
TargetScan-specific equality operator, which has an issue comparing celestial
objects. To fix this, update the TargetScan-specific equality operator to
correctly compare celestial objects, ensuring that the Model-level operator
inherits the correct behavior without further changes.
```

</details>

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-05-20T04:21:16Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,297 @@
+/// Apache License 2.0
+
+#include <OpenSpaceToolkit/Core/Error.hpp>
+#include <OpenSpaceToolkit/Core/Utility.hpp>
+
+#include <OpenSpaceToolkit/Mathematics/CurveFitting/Interpolator.hpp>
+#include <OpenSpaceToolkit/Mathematics/Object/Vector.hpp>
+
+#include <OpenSpaceToolkit/Physics/Unit/Derived.hpp>
+
+#include <OpenSpaceToolkit/Astrodynamics/Trajectory/Model/TargetScan.hpp>
+
+namespace ostk
+{
+namespace astrodynamics
+{
+namespace trajectory
+{
+namespace model
+{
+
+using ostk::mathematics::curvefitting::Interpolator;
+using ostk::mathematics::object::MatrixXd;
+using ostk::mathematics::object::Vector3d;
+using ostk::mathematics::object::VectorXd;
+
+using ostk::physics::coordinate::Position;
+using ostk::physics::unit::Derived;
+
+TargetScan::TargetScan(
+    const LLA& aStartLLA,
+    const LLA& anEndLLA,
+    const Instant& aStartInstant,
+    const Instant& anEndInstant,
+    const Celestial& aCelestial,
+    const Duration& aStepSize
+)
+    : Model(),
+      startLLA_(aStartLLA),
+      endLLA_(anEndLLA),
+      startInstant_(aStartInstant),
+      endInstant_(anEndInstant),
+      celestialSPtr_(std::make_shared<const Celestial>(aCelestial)),
+      stepSize_(aStepSize)
+{
+}
+
+TargetScan* TargetScan::clone() const
+{
+    return new TargetScan(*this);
+}
+
+bool TargetScan::operator==(const TargetScan& aTargetScanModel) const
+{
+    if ((!this->isDefined()) || (!aTargetScanModel.isDefined()))
+    {
+        return false;
+    }
+
+    return startLLA_ == aTargetScanModel.startLLA_ && endLLA_ == aTargetScanModel.endLLA_ &&
+           startInstant_ == aTargetScanModel.startInstant_ && endInstant_ == aTargetScanModel.endInstant_ &&
+           celestialSPtr_ == aTargetScanModel.celestialSPtr_ && stepSize_ == aTargetScanModel.stepSize_;
+}
+
+bool TargetScan::operator!=(const TargetScan& aTargetScanModel) const
+{
+    return !((*this) == aTargetScanModel);
+}
+
+std::ostream& operator<<(std::ostream& anOutputStream, const TargetScan& aTargetScanModel)
+{
+    aTargetScanModel.print(anOutputStream);
+
+    return anOutputStream;
+}
+
+bool TargetScan::isDefined() const
+{
+    return startLLA_.isDefined() && endLLA_.isDefined() && startInstant_.isDefined() && endInstant_.isDefined() &&
+           celestialSPtr_ != nullptr && celestialSPtr_->isDefined() && stepSize_.isDefined();
+}
+
+State TargetScan::calculateStateAt(const Instant& anInstant) const
+{
+    if (!anInstant.isDefined())
+    {
+        throw ostk::core::error::runtime::Undefined("Instant");
+    }
+
+    if (!this->isDefined())
+    {
+        throw ostk::core::error::runtime::Undefined("TargetScan");
+    }
+
+    if (anInstant < startInstant_ || anInstant > endInstant_)
+    {
+        throw ostk::core::error::RuntimeError("Instant is outside the interval.");
+    }
+
+    // interpolate the velocity using polynomial interpolation
+
+    const Array<Instant> instants = {
+        anInstant - stepSize_ * 2,
+        anInstant - stepSize_,
+        anInstant,
+        anInstant + stepSize_,
+        anInstant + stepSize_ * 2,
+    };
+
+    VectorXd times(instants.getSize());
+    MatrixXd positionCoordinates(3, instants.getSize());
+
+    Position position = Position::Undefined();
+
+    const Duration intervalDuration = endInstant_ - startInstant_;
+    const Length scanDistance =
+        startLLA_.calculateDistanceTo(endLLA_, celestialSPtr_->getEquatorialRadius(), celestialSPtr_->getFlattening());
+    const Real scanSpeed = scanDistance.inMeters() / intervalDuration.inSeconds();
+
+    const Pair<Angle, Angle> azimuthAngles =
+        startLLA_.calculateAzimuthTo(endLLA_, celestialSPtr_->getEquatorialRadius(), celestialSPtr_->getFlattening());
+
+    Size i = 0;
+    for (const auto& instant : instants)
+    {
+        times(i) = (instant - anInstant).inSeconds();
+
+        const Length forwardDistance = Length::Meters(scanSpeed * (instant - startInstant_).inSeconds());
+
+        const LLA lla = LLA::Forward(
+            startLLA_,
+            azimuthAngles.first,
+            forwardDistance,
+            celestialSPtr_->getEquatorialRadius(),
+            celestialSPtr_->getFlattening()
+        );
+
+        const physics::coordinate::Position positionAtInstant =
+            physics::coordinate::Position::FromLLA(lla, celestialSPtr_).inFrame(Frame::GCRF(), instant);
+
+        if (instant == anInstant)
+        {
+            position = positionAtInstant;
+        }
+
+        positionCoordinates.col(i) = positionAtInstant.getCoordinates();
+
+        ++i;
+    }
+
+    Array<Shared<const Interpolator>> interpolators = {
+        Interpolator::GenerateInterpolator(Interpolator::Type::CubicSpline, times, positionCoordinates.row(0)),
+        Interpolator::GenerateInterpolator(Interpolator::Type::CubicSpline, times, positionCoordinates.row(1)),
+        Interpolator::GenerateInterpolator(Interpolator::Type::CubicSpline, times, positionCoordinates.row(2)),
+    };
+
+    const Vector3d velocityCoordinates = {
+        interpolators[0]->computeDerivative(0.0),
+        interpolators[1]->computeDerivative(0.0),
+        interpolators[2]->computeDerivative(0.0),
+    };
path: src/OpenSpaceToolkit/Astrodynamics/Trajectory/Model/TargetScan.cpp
user: login: coderabbitai[bot]
body: _💡 Verification agent_

<details>
<summary>❓ Verification inconclusive</summary>

**Consider error handling for interpolation failures**

The code doesn't handle potential failures in the interpolation process. While the CubicSpline interpolator is generally robust, boundary conditions and numerical issues can still cause problems with small datasets.


Add error checking for interpolation results and consider using a more robust method for velocity calculation, especially for edge cases.

---

🏁 Script executed:

```shell
#!/bin/bash

# Check if other models implement error handling for interpolation
rg -A 5 -B 5 "Interpolator::.*Spline" --glob "src/OpenSpaceToolkit/Astrodynamics/Trajectory/Model/*.cpp" | grep -A 10 -B 10 -e "try" -e "catch" -e "error"
```

Length of output: 150

---



</details>

**Implement error handling and validation for interpolators in TargetScan.cpp**

No existing try/catch or error checks were found around spline interpolation in other trajectory models. To prevent silent failures or NaN/infinite derivatives at boundaries, please wrap the interpolator generation and derivative computation in exception handling and validate the results.

• File: src/OpenSpaceToolkit/Astrodynamics/Trajectory/Model/TargetScan.cpp  
  Lines 151–161  
  - Enclose calls to `Interpolator::GenerateInterpolator(…)` in a `try` block.  
  - After each call, verify the returned `Shared<const Interpolator>` is non-null and in a valid state.  
  - Wrap `computeDerivative(0.0)` in the same `try`/`catch` and handle numerical errors (e.g., throw a descriptive exception or fall back to a finite-difference derivative).  
  - Consider logging or propagating errors when interpolation fails, and document any fallback strategy for small or ill-conditioned datasets.

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-05-20T04:21:16Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,297 @@
+/// Apache License 2.0
+
+#include <OpenSpaceToolkit/Core/Error.hpp>
+#include <OpenSpaceToolkit/Core/Utility.hpp>
+
+#include <OpenSpaceToolkit/Mathematics/CurveFitting/Interpolator.hpp>
+#include <OpenSpaceToolkit/Mathematics/Object/Vector.hpp>
+
+#include <OpenSpaceToolkit/Physics/Unit/Derived.hpp>
+
+#include <OpenSpaceToolkit/Astrodynamics/Trajectory/Model/TargetScan.hpp>
+
+namespace ostk
+{
+namespace astrodynamics
+{
+namespace trajectory
+{
+namespace model
+{
+
+using ostk::mathematics::curvefitting::Interpolator;
+using ostk::mathematics::object::MatrixXd;
+using ostk::mathematics::object::Vector3d;
+using ostk::mathematics::object::VectorXd;
+
+using ostk::physics::coordinate::Position;
+using ostk::physics::unit::Derived;
+
+TargetScan::TargetScan(
+    const LLA& aStartLLA,
+    const LLA& anEndLLA,
+    const Instant& aStartInstant,
+    const Instant& anEndInstant,
+    const Celestial& aCelestial,
+    const Duration& aStepSize
+)
+    : Model(),
+      startLLA_(aStartLLA),
+      endLLA_(anEndLLA),
+      startInstant_(aStartInstant),
+      endInstant_(anEndInstant),
+      celestialSPtr_(std::make_shared<const Celestial>(aCelestial)),
+      stepSize_(aStepSize)
+{
+}
+
+TargetScan* TargetScan::clone() const
+{
+    return new TargetScan(*this);
+}
+
+bool TargetScan::operator==(const TargetScan& aTargetScanModel) const
+{
+    if ((!this->isDefined()) || (!aTargetScanModel.isDefined()))
+    {
+        return false;
+    }
+
+    return startLLA_ == aTargetScanModel.startLLA_ && endLLA_ == aTargetScanModel.endLLA_ &&
+           startInstant_ == aTargetScanModel.startInstant_ && endInstant_ == aTargetScanModel.endInstant_ &&
+           celestialSPtr_ == aTargetScanModel.celestialSPtr_ && stepSize_ == aTargetScanModel.stepSize_;
+}
+
+bool TargetScan::operator!=(const TargetScan& aTargetScanModel) const
+{
+    return !((*this) == aTargetScanModel);
+}
+
+std::ostream& operator<<(std::ostream& anOutputStream, const TargetScan& aTargetScanModel)
+{
+    aTargetScanModel.print(anOutputStream);
+
+    return anOutputStream;
+}
+
+bool TargetScan::isDefined() const
+{
+    return startLLA_.isDefined() && endLLA_.isDefined() && startInstant_.isDefined() && endInstant_.isDefined() &&
+           celestialSPtr_ != nullptr && celestialSPtr_->isDefined() && stepSize_.isDefined();
+}
+
+State TargetScan::calculateStateAt(const Instant& anInstant) const
+{
+    if (!anInstant.isDefined())
+    {
+        throw ostk::core::error::runtime::Undefined("Instant");
+    }
+
+    if (!this->isDefined())
+    {
+        throw ostk::core::error::runtime::Undefined("TargetScan");
+    }
+
+    if (anInstant < startInstant_ || anInstant > endInstant_)
+    {
+        throw ostk::core::error::RuntimeError("Instant is outside the interval.");
+    }
+
+    // interpolate the velocity using polynomial interpolation
+
+    const Array<Instant> instants = {
+        anInstant - stepSize_ * 2,
+        anInstant - stepSize_,
+        anInstant,
+        anInstant + stepSize_,
+        anInstant + stepSize_ * 2,
+    };
path: src/OpenSpaceToolkit/Astrodynamics/Trajectory/Model/TargetScan.cpp
user: login: coderabbitai[bot]
body: _💡 Verification agent_

<details>
<summary>🧩 Analysis chain</summary>

**Consider handling edge cases for boundary instants**

When `anInstant` is close to the start or end of the interval, some of the calculated instants for interpolation (`anInstant - stepSize_ * 2`, etc.) might fall outside the valid interval, which could lead to inconsistent results.


Consider adding additional boundary checks and special handling for these cases. This would ensure the interpolation is done using valid data points, especially when the requested instant is near the interval boundaries.

---

🏁 Script executed:

```shell
#!/bin/bash

# Check if the code handles boundary cases in other trajectory models
rg -A 10 -B 10 "calculateStateAt" --glob "src/OpenSpaceToolkit/Astrodynamics/Trajectory/Model/*.cpp" | grep -v "TargetScan" | grep -A 20 "interpolat"
```

Length of output: 145

---

Let’s inspect the surrounding context in TargetScan.cpp to see if any boundary checks are already implemented:

---

🏁 Script executed:

```shell
#!/bin/bash
rg -n "const Array<Instant> instants" -C5 src/OpenSpaceToolkit/Astrodynamics/Trajectory/Model/TargetScan.cpp
```

Length of output: 494

---



</details>

**Add boundary handling for interpolation instants**  

Although there’s a check that throws when `anInstant` is completely outside the overall interval (line 97), the computed instants at `anInstant ± stepSize_` and `±2*stepSize_` can still fall outside the valid range when you’re near the start or end of the interval. Passing those out-of-range instants into the interpolator will lead to undefined or incorrect results.  

Targets:  
- src/OpenSpaceToolkit/Astrodynamics/Trajectory/Model/TargetScan.cpp → lines 102–107  

Suggested approach:  
• Clamp each offset instant to `[start, end]` before building the array  
• Or detect when you’re fewer than 2 steps from an edge and switch to lower-order or one-sided interpolation  

For example:  
```cpp
const Instant start = interval.getStart();
const Instant end   = interval.getEnd();

// Clamp to valid range
const Instant t0 = anInstant - 2*stepSize_ < start ? start : anInstant - 2*stepSize_;
const Instant t1 = anInstant - stepSize_    < start ? start : anInstant - stepSize_;
const Instant t3 = anInstant + stepSize_    > end   ? end   : anInstant + stepSize_;
const Instant t4 = anInstant + 2*stepSize_  > end   ? end   : anInstant + 2*stepSize_;

const Array<Instant> instants = { t0, t1, anInstant, t3, t4 };
```  
Or, if you prefer, detect the edge case and fall back to a 3- or 4-point interpolation.

<details>
<summary>🤖 Prompt for AI Agents</summary>

```
In src/OpenSpaceToolkit/Astrodynamics/Trajectory/Model/TargetScan.cpp around
lines 102 to 107, the interpolation instants calculated as anInstant ± stepSize_
and ±2*stepSize_ can fall outside the valid interval near the boundaries,
causing incorrect interpolation. Fix this by clamping each offset instant to the
interval's start and end bounds before creating the instants array, or detect
when near the edges and switch to a lower-order or one-sided interpolation
method to ensure all instants used are within the valid range.
```

</details>

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-05-20T04:21:16Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,297 @@
+/// Apache License 2.0
+
+#include <OpenSpaceToolkit/Core/Error.hpp>
+#include <OpenSpaceToolkit/Core/Utility.hpp>
+
+#include <OpenSpaceToolkit/Mathematics/CurveFitting/Interpolator.hpp>
+#include <OpenSpaceToolkit/Mathematics/Object/Vector.hpp>
+
+#include <OpenSpaceToolkit/Physics/Unit/Derived.hpp>
+
+#include <OpenSpaceToolkit/Astrodynamics/Trajectory/Model/TargetScan.hpp>
+
+namespace ostk
+{
+namespace astrodynamics
+{
+namespace trajectory
+{
+namespace model
+{
+
+using ostk::mathematics::curvefitting::Interpolator;
+using ostk::mathematics::object::MatrixXd;
+using ostk::mathematics::object::Vector3d;
+using ostk::mathematics::object::VectorXd;
+
+using ostk::physics::coordinate::Position;
+using ostk::physics::unit::Derived;
+
+TargetScan::TargetScan(
+    const LLA& aStartLLA,
+    const LLA& anEndLLA,
+    const Instant& aStartInstant,
+    const Instant& anEndInstant,
+    const Celestial& aCelestial,
+    const Duration& aStepSize
+)
+    : Model(),
+      startLLA_(aStartLLA),
+      endLLA_(anEndLLA),
+      startInstant_(aStartInstant),
+      endInstant_(anEndInstant),
+      celestialSPtr_(std::make_shared<const Celestial>(aCelestial)),
+      stepSize_(aStepSize)
+{
+}
+
+TargetScan* TargetScan::clone() const
+{
+    return new TargetScan(*this);
+}
+
+bool TargetScan::operator==(const TargetScan& aTargetScanModel) const
+{
+    if ((!this->isDefined()) || (!aTargetScanModel.isDefined()))
+    {
+        return false;
+    }
+
+    return startLLA_ == aTargetScanModel.startLLA_ && endLLA_ == aTargetScanModel.endLLA_ &&
+           startInstant_ == aTargetScanModel.startInstant_ && endInstant_ == aTargetScanModel.endInstant_ &&
+           celestialSPtr_ == aTargetScanModel.celestialSPtr_ && stepSize_ == aTargetScanModel.stepSize_;
+}
+
+bool TargetScan::operator!=(const TargetScan& aTargetScanModel) const
+{
+    return !((*this) == aTargetScanModel);
+}
+
+std::ostream& operator<<(std::ostream& anOutputStream, const TargetScan& aTargetScanModel)
+{
+    aTargetScanModel.print(anOutputStream);
+
+    return anOutputStream;
+}
+
+bool TargetScan::isDefined() const
+{
+    return startLLA_.isDefined() && endLLA_.isDefined() && startInstant_.isDefined() && endInstant_.isDefined() &&
+           celestialSPtr_ != nullptr && celestialSPtr_->isDefined() && stepSize_.isDefined();
+}
+
+State TargetScan::calculateStateAt(const Instant& anInstant) const
+{
+    if (!anInstant.isDefined())
+    {
+        throw ostk::core::error::runtime::Undefined("Instant");
+    }
+
+    if (!this->isDefined())
+    {
+        throw ostk::core::error::runtime::Undefined("TargetScan");
+    }
+
+    if (anInstant < startInstant_ || anInstant > endInstant_)
+    {
+        throw ostk::core::error::RuntimeError("Instant is outside the interval.");
+    }
+
+    // interpolate the velocity using polynomial interpolation
+
+    const Array<Instant> instants = {
+        anInstant - stepSize_ * 2,
+        anInstant - stepSize_,
+        anInstant,
+        anInstant + stepSize_,
+        anInstant + stepSize_ * 2,
+    };
+
+    VectorXd times(instants.getSize());
+    MatrixXd positionCoordinates(3, instants.getSize());
+
+    Position position = Position::Undefined();
+
+    const Duration intervalDuration = endInstant_ - startInstant_;
+    const Length scanDistance =
+        startLLA_.calculateDistanceTo(endLLA_, celestialSPtr_->getEquatorialRadius(), celestialSPtr_->getFlattening());
+    const Real scanSpeed = scanDistance.inMeters() / intervalDuration.inSeconds();
+
+    const Pair<Angle, Angle> azimuthAngles =
+        startLLA_.calculateAzimuthTo(endLLA_, celestialSPtr_->getEquatorialRadius(), celestialSPtr_->getFlattening());
+
+    Size i = 0;
+    for (const auto& instant : instants)
+    {
+        times(i) = (instant - anInstant).inSeconds();
+
+        const Length forwardDistance = Length::Meters(scanSpeed * (instant - startInstant_).inSeconds());
+
+        const LLA lla = LLA::Forward(
+            startLLA_,
+            azimuthAngles.first,
+            forwardDistance,
+            celestialSPtr_->getEquatorialRadius(),
+            celestialSPtr_->getFlattening()
+        );
+
+        const physics::coordinate::Position positionAtInstant =
+            physics::coordinate::Position::FromLLA(lla, celestialSPtr_).inFrame(Frame::GCRF(), instant);
+
+        if (instant == anInstant)
+        {
+            position = positionAtInstant;
+        }
+
+        positionCoordinates.col(i) = positionAtInstant.getCoordinates();
+
+        ++i;
+    }
+
+    Array<Shared<const Interpolator>> interpolators = {
+        Interpolator::GenerateInterpolator(Interpolator::Type::CubicSpline, times, positionCoordinates.row(0)),
+        Interpolator::GenerateInterpolator(Interpolator::Type::CubicSpline, times, positionCoordinates.row(1)),
+        Interpolator::GenerateInterpolator(Interpolator::Type::CubicSpline, times, positionCoordinates.row(2)),
+    };
+
+    const Vector3d velocityCoordinates = {
+        interpolators[0]->computeDerivative(0.0),
+        interpolators[1]->computeDerivative(0.0),
+        interpolators[2]->computeDerivative(0.0),
+    };
+
+    const Velocity velocity = Velocity::MetersPerSecond(velocityCoordinates, Frame::GCRF());
+
+    return State(anInstant, position, velocity);
+}
+
+LLA TargetScan::getStartLLA() const
+{
+    return startLLA_;
+}
+
+LLA TargetScan::getEndLLA() const
+{
+    return endLLA_;
+}
+
+Instant TargetScan::getStartInstant() const
+{
+    return startInstant_;
+}
+
+Instant TargetScan::getEndInstant() const
+{
+    return endInstant_;
+}
+
+Celestial TargetScan::getCelestial() const
+{
+    return *celestialSPtr_;
+}
+
+Duration TargetScan::getStepSize() const
+{
+    return stepSize_;
+}
+
+void TargetScan::print(std::ostream& anOutputStream, bool displayDecorator) const
+{
+    using ostk::core::type::String;
+
+    displayDecorator ? ostk::core::utils::Print::Header(anOutputStream, "TargetScan") : void();
+
+    ostk::core::utils::Print::Line(anOutputStream)
+        << "Start LLA:" << (startLLA_.isDefined() ? startLLA_.toString() : "Undefined");
+
+    ostk::core::utils::Print::Line(anOutputStream)
+        << "End LLA:" << (endLLA_.isDefined() ? endLLA_.toString() : "Undefined");
+
+    ostk::core::utils::Print::Line(anOutputStream)
+        << "Start Instant:" << (startInstant_.isDefined() ? startInstant_.toString() : "Undefined");
+
+    ostk::core::utils::Print::Line(anOutputStream)
+        << "End Instant:" << (endInstant_.isDefined() ? endInstant_.toString() : "Undefined");
+
+    ostk::core::utils::Print::Line(anOutputStream)
+        << "Celestial:"
+        << (celestialSPtr_ != nullptr && celestialSPtr_->isDefined() ? celestialSPtr_->getName() : "Undefined");
+
+    ostk::core::utils::Print::Line(anOutputStream)
+        << "Step Size:" << (stepSize_.isDefined() ? stepSize_.toString() : "Undefined");
+
+    displayDecorator ? ostk::core::utils::Print::Footer(anOutputStream) : void();
+}
+
+bool TargetScan::operator==(const Model& aModel) const
+{
+    const TargetScan* staticModelPtr = dynamic_cast<const TargetScan*>(&aModel);
+
+    return (staticModelPtr != nullptr) && this->operator==(*staticModelPtr);
+}
+
+bool TargetScan::operator!=(const Model& aModel) const
+{
+    return !((*this) == aModel);
+}
+
+TargetScan TargetScan::FromGroundSpeed(
+    const LLA& aStartLLA,
+    const LLA& anEndLLA,
+    const Derived& aGroundSpeed,
+    const Instant& aStartInstant,
+    const Celestial& aCelestial,
+    const Duration& aStepSize
+)
+{
+    if (!aStartLLA.isDefined())
+    {
+        throw ostk::core::error::runtime::Undefined("Start LLA");
+    }
+
+    if (!anEndLLA.isDefined())
+    {
+        throw ostk::core::error::runtime::Undefined("End LLA");
+    }
+
+    if (!aGroundSpeed.isDefined())
+    {
+        throw ostk::core::error::runtime::Undefined("Ground speed");
+    }
+
+    if (!aStartInstant.isDefined())
+    {
+        throw ostk::core::error::runtime::Undefined("Start instant");
+    }
+
+    if (!aStepSize.isDefined())
+    {
+        throw ostk::core::error::runtime::Undefined("Step size");
+    }
+
+    if (aStepSize.inSeconds() <= 0.0)
+    {
+        throw ostk::core::error::RuntimeError("Step size must be positive.");
+    }
+
+    const Real groundSpeedMps = aGroundSpeed.in(Derived::Unit::MeterPerSecond());
+
+    if (groundSpeedMps <= 0.0)
+    {
+        throw ostk::core::error::RuntimeError("Ground speed must be positive.");
+    }
+
+    const Length distance =
+        aStartLLA.calculateDistanceTo(anEndLLA, aCelestial.getEquatorialRadius(), aCelestial.getFlattening());
+
+    const Duration duration = Duration::Seconds(distance.inMeters() / groundSpeedMps);
+
+    const Instant endInstant = aStartInstant + duration;
+
+    return TargetScan(aStartLLA, anEndLLA, aStartInstant, endInstant, aCelestial, aStepSize);
+}
path: src/OpenSpaceToolkit/Astrodynamics/Trajectory/Model/TargetScan.cpp
user: login: coderabbitai[bot]
body: _💡 Verification agent_

<details>
<summary>❓ Verification inconclusive</summary>

**Add handling for very small distances between start and end LLA**

The factory method calculates duration based on distance and ground speed but doesn't handle the case where start and end LLA are extremely close or identical, which could result in a very short or zero duration.


Consider adding a minimum distance or duration check to ensure the trajectory is well-defined even for very close points.

---

🏁 Script executed:

```shell
#!/bin/bash

# Check if similar factory methods in other models handle minimum distance or duration
rg -A 20 "FromGroundSpeed|From.*Speed" --glob "src/OpenSpaceToolkit/Astrodynamics/Trajectory/**/*.cpp" | grep -A 5 -B 5 -e "minimum" -e "min" -e "if.*distance" -e "if.*duration"
```

Length of output: 175

---

🏁 Script executed:

```shell
#!/bin/bash
# Locate the TargetScan header file
rg -l "class TargetScan" --glob "src/OpenSpaceToolkit/Astrodynamics/Trajectory/**/*.hpp"

# Display the first 200 lines of the header to inspect constructor validations
HEADER_PATH=$(rg -l "class TargetScan" --glob "src/OpenSpaceToolkit/Astrodynamics/Trajectory/**/*.hpp")
cat "$HEADER_PATH" | sed -n '1,200p'
```

Length of output: 555

---

```bash
#!/bin/bash
# Find TargetScan header in the Model directory
find src/OpenSpaceToolkit/Astrodynamics/Trajectory/Model -type f \( -name "TargetScan.h" -o -name "TargetScan.hpp" -o -name "TargetScan.hh" \) -print

# If found, display its first 200 lines to check for any minimum-distance/duration logic
for HEADER in $(find src/OpenSpaceToolkit/Astrodynamics/Trajectory/Model -type f \( -name "TargetScan.h" -o -name "TargetScan.hpp" -o -name "TargetScan.hh" \)); do
  echo "===== $HEADER ====="
  sed -n '1,200p' "$HEADER"
done
```

---



</details>

**Add minimum distance/duration guard in FromGroundSpeed**

The current implementation computes  
```cpp
const Length distance = aStartLLA.calculateDistanceTo(…);
const Duration duration = Duration::Seconds(distance.inMeters() / groundSpeedMps);
```
without guarding against zero or near-zero values. If `aStartLLA == anEndLLA`, you end up with a zero-length, zero-duration scan, which is likely unintended.

Suggested fixes:

• In src/OpenSpaceToolkit/Astrodynamics/Trajectory/Model/TargetScan.cpp, inside `TargetScan::FromGroundSpeed`, **after**  
```cpp
const Length distance = …;
```  
add:  
```diff
+ if (distance.inMeters() <= 0.0)
+ {
+     throw ostk::core::error::RuntimeError("Scan distance must be positive.");
+ }
```

• Optionally, ensure the resulting duration is meaningful—e.g., at least one step:  
```diff
+ const Duration duration = Duration::Seconds(distance.inMeters() / groundSpeedMps);
+ if (duration.inSeconds() < aStepSize.inSeconds())
+ {
+     throw ostk::core::error::RuntimeError("Computed scan duration shorter than step size.");
+ }
```

These checks prevent zero-length scans and guarantee the scan can execute at least one step.

<details>
<summary>🤖 Prompt for AI Agents</summary>

```
In src/OpenSpaceToolkit/Astrodynamics/Trajectory/Model/TargetScan.cpp around
lines 238 to 292, the FromGroundSpeed method does not handle the case where the
start and end LLA points are extremely close or identical, resulting in zero or
near-zero duration. To fix this, after calculating the distance, add a check to
ensure the distance is above a small positive threshold; if not, set it to that
minimum distance. Then compute the duration from this adjusted distance to
guarantee a meaningful, non-zero duration that allows at least one step in the
trajectory. This prevents creating zero-length or zero-duration scans.
```

</details>

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-05-20T04:21:17Z
author_association: CONTRIBUTOR

PR comments:
user: login: coderabbitai[bot]
created_at: 2025-05-15T04:37:32Z
body: <!-- This is an auto-generated comment: summarize by coderabbit.ai -->
<!-- walkthrough_start -->

## Walkthrough

The changes introduce a new `TargetScan` trajectory model class to the OpenSpaceToolkit Astrodynamics library with full C++ implementation, Python bindings, and comprehensive unit tests. The model represents a scanning trajectory between two geodetic points on a celestial body over a time interval. Deprecated methods redirect users to the new model.

## Changes

| Files                                                                                     | Change Summary                                                                                                    |
|-------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------|
| `include/OpenSpaceToolkit/Astrodynamics/Trajectory/Model/TargetScan.hpp`                  | Added `TargetScan` class declaration with constructors, methods, operators, and static factory method.           |
| `src/OpenSpaceToolkit/Astrodynamics/Trajectory/Model/TargetScan.cpp`                      | Implemented `TargetScan` class including state calculation, interpolation, and error handling.                   |
| `bindings/python/src/OpenSpaceToolkitAstrodynamicsPy/Trajectory/Model/TargetScan.cpp`     | Added Python bindings for `TargetScan` class exposing constructors, methods, operators, and static factory.      |
| `bindings/python/src/OpenSpaceToolkitAstrodynamicsPy/Trajectory/Model.cpp`                | Registered `TargetScan` Python submodule in the existing trajectory model module.                                |
| `bindings/python/test/Trajectory/Model/test_target_scan.py`                              | Added pytest-based tests for `TargetScan` covering construction, state calculation, comparison, and getters.    |
| `test/OpenSpaceToolkit/Astrodynamics/Trajectory/Model/TargetScan.test.cpp`                | Added Google Test unit tests validating `TargetScan` class functionality, error cases, and output formatting.   |
| `include/OpenSpaceToolkit/Astrodynamics/Trajectory.hpp`                                  | Marked two `Trajectory::GroundStrip` static methods as deprecated, pointing to use of `TargetScan` instead.     |
| `src/OpenSpaceToolkit/Astrodynamics/Trajectory.cpp`                                      | Added include and alias for `TargetScan` model; moved existing using directive for `Static` model.               |

## Sequence Diagram(s)

```mermaid
sequenceDiagram
    participant Python as Python User
    participant PyBind as Pybind11 Bindings
    participant TargetScan as TargetScan Model (C++)

    Python->>PyBind: Create TargetScan(startLLA, endLLA, startInstant, endInstant, celestial, stepSize)
    PyBind->>TargetScan: Call TargetScan constructor
    TargetScan-->>PyBind: Return TargetScan instance
    PyBind-->>Python: Return Python TargetScan object

    Python->>PyBind: Call calculateStateAt(instant)
    PyBind->>TargetScan: calculateStateAt(instant)
    TargetScan-->>PyBind: Return State (position, velocity)
    PyBind-->>Python: Return State object
```

## Poem

> 🐇  
> A scan between points, so grand and neat,  
> From Earth’s surface, a trajectory sweet.  
> With Python and C++ in a dance so fine,  
> TargetScan’s ready, the stars align!  
> Step by step, it charts the way,  
> In tests and code, it’s here to stay!  
> 🚀✨

<!-- walkthrough_end -->


---

<details>
<summary>📜 Recent review details</summary>

**Configuration used: CodeRabbit UI**
**Review profile: CHILL**
**Plan: Pro**
**Cache: Disabled due to data retention organization setting**
**Knowledge Base: Disabled due to data retention organization setting**


<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between af0620b8d2d7282f5464deaf49fd4eb47f456ac9 and 972546f9efd8db0a3485240954c0834f0d339e44.

</details>

<details>
<summary>📒 Files selected for processing (1)</summary>

* `test/OpenSpaceToolkit/Astrodynamics/Trajectory/Model/TargetScan.test.cpp` (1 hunks)

</details>

<details>
<summary>🚧 Files skipped from review as they are similar to previous changes (1)</summary>

* test/OpenSpaceToolkit/Astrodynamics/Trajectory/Model/TargetScan.test.cpp

</details>

<details>
<summary>⏰ Context from checks skipped due to timeout of 90000ms (4)</summary>

* GitHub Check: Test / Check Python Format
* GitHub Check: Test / Build Python bindings
* GitHub Check: Test / Check C++ Format
* GitHub Check: Test / Run C++ Unit Tests

</details>

</details>
<!-- internal state start -->


<!-- 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= -->

<!-- internal state end -->
<!-- finishing_touch_checkbox_start -->

<details open="true">
<summary>✨ Finishing Touches</summary>

- [ ] <!-- {"checkboxId": "7962f53c-55bc-4827-bfbf-6a18da830691"} --> 📝 Generate Docstrings

</details>

<!-- finishing_touch_checkbox_end -->
<!-- tips_start -->

---

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

<details>
<summary>❤️ Share</summary>

- [X](https://twitter.com/intent/tweet?text=I%20just%20used%20%40coderabbitai%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20the%20proprietary%20code.%20Check%20it%20out%3A&url=https%3A//coderabbit.ai)
- [Mastodon](https://mastodon.social/share?text=I%20just%20used%20%40coderabbitai%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20the%20proprietary%20code.%20Check%20it%20out%3A%20https%3A%2F%2Fcoderabbit.ai)
- [Reddit](https://www.reddit.com/submit?title=Great%20tool%20for%20code%20review%20-%20CodeRabbit&text=I%20just%20used%20CodeRabbit%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20proprietary%20code.%20Check%20it%20out%3A%20https%3A//coderabbit.ai)
- [LinkedIn](https://www.linkedin.com/sharing/share-offsite/?url=https%3A%2F%2Fcoderabbit.ai&mini=true&title=Great%20tool%20for%20code%20review%20-%20CodeRabbit&summary=I%20just%20used%20CodeRabbit%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20proprietary%20code)

</details>

<details>
<summary>🪧 Tips</summary>

### Chat

There are 3 ways to chat with [CodeRabbit](https://coderabbit.ai?utm_source=oss&utm_medium=github&utm_campaign=open-space-collective/open-space-toolkit-astrodynamics&utm_content=555):

- Review comments: Directly reply to a review comment made by CodeRabbit. Example:
  - `I pushed a fix in commit <commit_id>, please review it.`
  - `Explain this complex logic.`
  - `Open a follow-up GitHub issue for this discussion.`
- Files and specific lines of code (under the "Files changed" tab): Tag `@coderabbitai` in a new review comment at the desired location with your query. Examples:
  - `@coderabbitai explain this code block.`
  -	`@coderabbitai modularize this function.`
- PR comments: Tag `@coderabbitai` in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
  - `@coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.`
  - `@coderabbitai read src/utils.ts and explain its main purpose.`
  - `@coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.`
  - `@coderabbitai help me debug CodeRabbit configuration file.`

### Support

Need help? Create a ticket on our [support page](https://www.coderabbit.ai/contact-us/support) for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

### CodeRabbit Commands (Invoked using PR comments)

- `@coderabbitai pause` to pause the reviews on a PR.
- `@coderabbitai resume` to resume the paused reviews.
- `@coderabbitai review` to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
- `@coderabbitai full review` to do a full review from scratch and review all the files again.
- `@coderabbitai summary` to regenerate the summary of the PR.
- `@coderabbitai generate docstrings` to [generate docstrings](https://docs.coderabbit.ai/finishing-touches/docstrings) for this PR.
- `@coderabbitai generate sequence diagram` to generate a sequence diagram of the changes in this PR.
- `@coderabbitai resolve` resolve all the CodeRabbit review comments.
- `@coderabbitai configuration` to show the current CodeRabbit configuration for the repository.
- `@coderabbitai help` to get help.

### Other keywords and placeholders

- Add `@coderabbitai ignore` anywhere in the PR description to prevent this PR from being reviewed.
- Add `@coderabbitai summary` to generate the high-level summary at a specific location in the PR description.
- Add `@coderabbitai` anywhere in the PR title to generate the title automatically.

### CodeRabbit Configuration File (`.coderabbit.yaml`)

- You can programmatically configure CodeRabbit by adding a `.coderabbit.yaml` file to the root of your repository.
- Please see the [configuration documentation](https://docs.coderabbit.ai/guides/configure-coderabbit) for more information.
- If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: `# yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json`

### Documentation and Community

- Visit our [Documentation](https://docs.coderabbit.ai) for detailed information on how to use CodeRabbit.
- Join our [Discord Community](http://discord.gg/coderabbit) to get help, request features, and share feedback.
- Follow us on [X/Twitter](https://twitter.com/coderabbitai) for updates and announcements.

</details>

<!-- tips_end -->
author_association: CONTRIBUTOR
user: login: codecov[bot]
created_at: 2025-05-15T04:52:29Z
body: ## [Codecov](https://app.codecov.io/gh/open-space-collective/open-space-toolkit-astrodynamics/pull/555?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=open-space-collective) Report
Attention: Patch coverage is `92.80000%` with `9 lines` in your changes missing coverage. Please review.
> Project coverage is 91.82%. Comparing base [(`a429cc1`)](https://app.codecov.io/gh/open-space-collective/open-space-toolkit-astrodynamics/commit/a429cc199ebc5dd67fd3bfbc69ea2ba67099ee65?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=open-space-collective) to head [(`972546f`)](https://app.codecov.io/gh/open-space-collective/open-space-toolkit-astrodynamics/commit/972546f9efd8db0a3485240954c0834f0d339e44?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=open-space-collective).
> Report is 1 commits behind head on main.

| [Files with missing lines](https://app.codecov.io/gh/open-space-collective/open-space-toolkit-astrodynamics/pull/555?dropdown=coverage&src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=open-space-collective) | Patch % | Lines |
|---|---|---|
| [...lkit/Astrodynamics/Trajectory/Model/TargetScan.cpp](https://app.codecov.io/gh/open-space-collective/open-space-toolkit-astrodynamics/pull/555?src=pr&el=tree&filepath=src%2FOpenSpaceToolkit%2FAstrodynamics%2FTrajectory%2FModel%2FTargetScan.cpp&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=open-space-collective#diff-c3JjL09wZW5TcGFjZVRvb2xraXQvQXN0cm9keW5hbWljcy9UcmFqZWN0b3J5L01vZGVsL1RhcmdldFNjYW4uY3Bw) | 92.74% | [9 Missing :warning: ](https://app.codecov.io/gh/open-space-collective/open-space-toolkit-astrodynamics/pull/555?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=open-space-collective) |

<details><summary>Additional details and impacted files</summary>


```diff
@@            Coverage Diff             @@
##             main     #555      +/-   ##
==========================================
+ Coverage   91.80%   91.82%   +0.01%     
==========================================
  Files         104      106       +2     
  Lines        9890    10015     +125     
==========================================
+ Hits         9080     9196     +116     
- Misses        810      819       +9     
```

</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/open-space-collective/open-space-toolkit-astrodynamics/pull/555?dropdown=coverage&src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=open-space-collective).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=open-space-collective).

<details><summary> :rocket: New features to boost your workflow: </summary>

- :snowflake: [Test Analytics](https://docs.codecov.com/docs/test-analytics): Detect flaky tests, report on failures, and find test suite problems.
</details>
author_association: NONE