Ver código fonte

give the controller the information he needs and that he can configure the tags

Sven Czarnian 3 anos atrás
pai
commit
d7c41d1941
4 arquivos alterados com 149 adições e 269 exclusões
  1. 4 8
      include/aman/types/Inbound.h
  2. 85 64
      src/PlugIn.cpp
  3. 3 4
      src/PlugIn.h
  4. 57 193
      src/types/Inbound.cpp

+ 4 - 8
include/aman/types/Inbound.h

@@ -33,34 +33,30 @@ namespace aman {
         bool                         m_fixedPlan;
         std::string                  m_star;
         std::string                  m_runway;
+        Velocity                     m_groundSpeed;
         std::size_t                  m_nextStarWaypoint;
         std::vector<ArrivalWaypoint> m_arrivalRoute;
         Time                         m_timeToLose;
         UtcTime::Point               m_waypointEstimatedTimeOfArrival;
-        Length                       m_trackmiles;
-        Time                         m_flighttime;
-        std::string                  m_predictedWaypoint;
 
         void updatePrediction(EuroScopePlugIn::CRadarTarget& target, const aman::AircraftSchedule& inbound);
         Velocity indicatedAirspeed(const Length& altitude) const noexcept;
         Velocity groundSpeed(const Length& altitude, const Velocity& ias, const Angle& heading);
         void createWindTables(const google::protobuf::RepeatedPtrField<aman::WindData>& wind);
         void predictETA(const EuroScopePlugIn::CRadarTarget& target, const EuroScopePlugIn::CFlightPlanPositionPredictions& predictions);
-        std::string findNextWaypointOnHeading(EuroScopePlugIn::CFlightPlan& plan);
 
     public:
         Inbound(EuroScopePlugIn::CRadarTarget& target, const aman::AircraftSchedule& inbound, const google::protobuf::RepeatedPtrField<aman::WindData>& wind);
         void update(EuroScopePlugIn::CRadarTarget& target, const aman::AircraftSchedule& inbound,
                     const google::protobuf::RepeatedPtrField<aman::WindData>& wind);
         void update(EuroScopePlugIn::CRadarTarget& target);
-        void update(EuroScopePlugIn::CFlightPlan& plan);
+        void directTo(EuroScopePlugIn::CRadarTarget& radarTarget, const std::string& waypoint);
         bool fixedPlan() const noexcept;
         UtcTime::Point eta() const;
         UtcTime::Point pta() const;
         const Time& timeToLose() const noexcept;
-        const Length& trackmiles() const noexcept;
-        const Time& flighttime() const noexcept;
-        const std::string& predictedWaypoint() const noexcept;
+        const std::string& nextWaypoint() const noexcept;
+        const std::vector<ArrivalWaypoint>& arrivalRoute() const noexcept;
 
         static int matchToPredictedPath(const EuroScopePlugIn::CFlightPlanPositionPredictions& predictions, const GeoCoordinate& position,
                                         const Velocity& groundspeed, Length& trackmiles);

+ 85 - 64
src/PlugIn.cpp

@@ -71,8 +71,8 @@ PlugIn::PlugIn() :
     this->RegisterTagItemType("PTA", static_cast<int>(PlugIn::TagItemElement::PlannedTimeOfArrival));
     this->RegisterTagItemType("Delta time", static_cast<int>(PlugIn::TagItemElement::DeltaTime));
     this->RegisterTagItemType("Fixed plan indicator", static_cast<int>(PlugIn::TagItemElement::FixedPlanIndicator));
-    this->RegisterTagItemFunction("Force planning", static_cast<int>(PlugIn::TagItemFunction::ForceToBackendMenu));
     this->RegisterTagItemFunction("Runway selection", static_cast<int>(PlugIn::TagItemFunction::RunwaySelectMenu));
+    this->RegisterTagItemFunction("Direct to", static_cast<int>(PlugIn::TagItemFunction::DirectToMenu));
 
     this->DisplayUserMessage(PLUGIN_NAME, "INFO", (std::string("Loaded ") + PLUGIN_NAME + " " + PLUGIN_VERSION).c_str(), true, true, false, false, false);
 
@@ -498,6 +498,32 @@ bool PlugIn::OnCompileCommand(const char* cmdline) {
             this->m_sweatboxValid = false;
         retval = true;
     }
+    else if (std::string::npos != message.find("FORCE")) {
+        const auto elements = String::splitString(message, " ");
+
+        if (3 <= elements.size()) {
+            const auto callsign = elements[2];
+
+            const auto radarTarget = this->RadarTargetSelect(callsign.c_str());
+
+            if (true == radarTarget.IsValid()) {
+                std::string destination(radarTarget.GetCorrelatedFlightPlan().GetFlightPlanData().GetDestination());
+                std::transform(destination.begin(), destination.end(), destination.begin(), ::toupper);
+                std::lock_guard updateGuard(this->m_updateQueueLock);
+
+                auto it = this->m_updateQueue.find(destination);
+
+                if (this->m_updateQueue.end() != it) {
+                    auto cIt = std::find(this->m_forcedToBackendCallsigns.cbegin(), this->m_forcedToBackendCallsigns.cend(), callsign);
+
+                    if (this->m_forcedToBackendCallsigns.cend() == cIt)
+                        this->m_forcedToBackendCallsigns.push_back(callsign);
+                    else
+                        this->m_forcedToBackendCallsigns.erase(cIt);
+                }
+            }
+        }
+    }
 
     if (true == retval) {
         if (true == this->m_sweatboxValid)
@@ -527,7 +553,7 @@ void PlugIn::OnGetTagItem(EuroScopePlugIn::CFlightPlan flightPlan, EuroScopePlug
     std::transform(destination.begin(), destination.end(), destination.begin(), ::toupper);
 #pragma warning(default: 4244)
 
-    bool planExpected = false, forced = false;
+    bool forced = false;
     const gsl::span<char, 16> tagString(itemString, 16UL);
 
     this->m_updateQueueLock.lock();
@@ -538,9 +564,6 @@ void PlugIn::OnGetTagItem(EuroScopePlugIn::CFlightPlan flightPlan, EuroScopePlug
         forced = this->m_forcedToBackendCallsigns.cend() != cIt;
     }
 
-    /* check if the inbound is expected to be planned */
-    planExpected = this->m_updateQueue.cend() != this->m_updateQueue.find(destination);
-
     this->m_updateQueueLock.unlock();
 
     std::lock_guard guard(this->m_inboundsQueueLock);
@@ -550,10 +573,16 @@ void PlugIn::OnGetTagItem(EuroScopePlugIn::CFlightPlan flightPlan, EuroScopePlug
     switch (static_cast<TagItemElement>(itemCode)) {
     case TagItemElement::EstimatedTimeOfArrival:
     {
-        if (this->m_inbounds.cend() != it)
-            message = UtcTime::timeToString(it->second.eta(), "%H:%M");
-        else if (true == forced || true == planExpected)
+        if (this->m_inbounds.cend() != it) {
+            const auto eta = it->second.eta();
+            if (UtcTime::Point() == eta)
+                message = "??:??";
+            else
+                message = UtcTime::timeToString(it->second.eta(), "%H:%M");
+        }
+        else if (true == forced) {
             message = "??:??";
+        }
         break;
     }
     case TagItemElement::PlannedTimeOfArrival:
@@ -563,9 +592,9 @@ void PlugIn::OnGetTagItem(EuroScopePlugIn::CFlightPlan flightPlan, EuroScopePlug
             if (UtcTime::Point() == pta)
                 message = "??:??";
             else
-                message = UtcTime::timeToString(it->second.pta(), "%H:%M");
+                message = UtcTime::timeToString(pta, "%H:%M");
         }
-        else if (true == forced || true == planExpected) {
+        else if (true == forced) {
             message = "??:??";
         }
         break;
@@ -578,7 +607,7 @@ void PlugIn::OnGetTagItem(EuroScopePlugIn::CFlightPlan flightPlan, EuroScopePlug
             stream << ttl;
             message = stream.str();
         }
-        else if (true == forced || true == planExpected) {
+        else if (true == forced) {
             message = "??";
         }
         break;
@@ -586,7 +615,7 @@ void PlugIn::OnGetTagItem(EuroScopePlugIn::CFlightPlan flightPlan, EuroScopePlug
     case TagItemElement::FixedPlanIndicator:
         if (this->m_inbounds.cend() != it && false == it->second.fixedPlan())
             message = "*";
-        else if (this->m_inbounds.cend() == it && (true == forced || true == planExpected))
+        else if (this->m_inbounds.cend() == it && true == forced)
             message = "*";
         break;
     default:
@@ -603,54 +632,20 @@ void PlugIn::OnFunctionCall(int functionId, const char* itemString, POINT pt, RE
     std::ignore = itemString;
     std::ignore = pt;
 
-    const auto radarTarget = this->RadarTargetSelectASEL();
+    auto radarTarget = this->RadarTargetSelectASEL();
     if (false == radarTarget.IsValid() || false == radarTarget.GetCorrelatedFlightPlan().IsValid())
         return;
 
     std::string callsign(radarTarget.GetCallsign());
 
-    switch (static_cast<PlugIn::TagItemFunction>(functionId)) {
-    case PlugIn::TagItemFunction::ForceToBackendMenu:
-    {
-        this->OpenPopupList(area, "AMAN", 1);
-        bool inList = this->m_forcedToBackendCallsigns.cend() != std::find(this->m_forcedToBackendCallsigns.cbegin(), this->m_forcedToBackendCallsigns.cend(), callsign);
-        this->AddPopupListElement("Add to AMAN", "", static_cast<int>(PlugIn::TagItemFunction::ForceToBackendSelect),
-            false, EuroScopePlugIn::POPUP_ELEMENT_NO_CHECKBOX, true == inList);
-        this->AddPopupListElement("Remove from AMAN", "", static_cast<int>(PlugIn::TagItemFunction::ForceToBackendSelect),
-            false, EuroScopePlugIn::POPUP_ELEMENT_NO_CHECKBOX, false == inList);
-        break;
-    }
-    case PlugIn::TagItemFunction::ForceToBackendSelect:
-    {
-        std::string destination(radarTarget.GetCorrelatedFlightPlan().GetFlightPlanData().GetDestination());
-        std::transform(destination.begin(), destination.end(), destination.begin(), ::toupper);
-
-        std::lock_guard guard(this->m_updateQueueLock);
-        auto it = this->m_updateQueue.find(destination);
-
-        if (0 == std::strcmp(itemString, "Add to AMAN")) {
-            if (this->m_updateQueue.end() != it) {
-                auto cIt = std::find(this->m_forcedToBackendCallsigns.cbegin(), this->m_forcedToBackendCallsigns.cend(), callsign);
-                if (this->m_forcedToBackendCallsigns.cend() == cIt)
-                    this->m_forcedToBackendCallsigns.push_back(callsign);
-            }
-        }
-        else {
-            if (this->m_updateQueue.end() != it) {
-                auto cIt = std::find(this->m_forcedToBackendCallsigns.begin(), this->m_forcedToBackendCallsigns.end(), callsign);
-                if (this->m_forcedToBackendCallsigns.cend() != cIt)
-                    this->m_forcedToBackendCallsigns.erase(cIt);
-            }
-        }
+    std::lock_guard guardUpdate(this->m_updateQueueLock);
+    std::lock_guard guardInbound(this->m_inboundsQueueLock);
 
-        break;
-    }
+    switch (static_cast<PlugIn::TagItemFunction>(functionId)) {
     case PlugIn::TagItemFunction::RunwaySelectMenu:
     {
         std::string destination(radarTarget.GetCorrelatedFlightPlan().GetFlightPlanData().GetDestination());
         std::transform(destination.begin(), destination.end(), destination.begin(), ::toupper);
-
-        std::lock_guard guard(this->m_updateQueueLock);
         auto it = this->m_updateQueue.find(destination);
 
         if (this->m_updateQueue.cend() != it) {
@@ -667,8 +662,6 @@ void PlugIn::OnFunctionCall(int functionId, const char* itemString, POINT pt, RE
     }
     case PlugIn::TagItemFunction::RunwaySelect:
     {
-        std::lock_guard guard(this->m_updateQueueLock);
-
         if (0 == std::strcmp(itemString, "Remove")) {
             auto it = this->m_selectedRunway.find(callsign);
             if (this->m_selectedRunway.end() != it)
@@ -681,9 +674,40 @@ void PlugIn::OnFunctionCall(int functionId, const char* itemString, POINT pt, RE
             else
                 this->m_selectedRunway.insert({ callsign, itemString });
         }
+        break;
+    }
+    case PlugIn::TagItemFunction::DirectToMenu:
+    {
+        auto it = this->m_inbounds.find(callsign);
+
+        if (this->m_inbounds.cend() != it && 0 != it->second.arrivalRoute().size()) {
+            this->OpenPopupList(area, "Direct To", 2);
+
+            for (const auto& waypoint : std::as_const(it->second.arrivalRoute())) {
+                std::string message;
+                
+                if (it->second.nextWaypoint() == waypoint.name())
+                    message = "-> ";
+                message += waypoint.name().c_str();
+
+                this->AddPopupListElement(message.c_str(), UtcTime::timeToString(waypoint.plannedArrivalTime(), "%H:%M").c_str(),
+                                          static_cast<int>(PlugIn::TagItemFunction::DirectTo));
+            }
+        }
 
         break;
     }
+    case PlugIn::TagItemFunction::DirectTo:
+    {
+        std::string_view message(itemString);
+        if (0 != message.rfind("-> ", 0)) {
+            auto it = this->m_inbounds.find(callsign);
+
+            if (this->m_inbounds.end() != it)
+                it->second.directTo(radarTarget, itemString);
+        }
+        break;
+    }
     default:
         break;
     }
@@ -722,13 +746,6 @@ void PlugIn::updateInbound(EuroScopePlugIn::CRadarTarget& radarTarget) {
         it->second.update(radarTarget);
 }
 
-void PlugIn::updateInbound(EuroScopePlugIn::CFlightPlan& plan) {
-    std::lock_guard guard(this->m_inboundsQueueLock);
-    auto it = this->m_inbounds.find(plan.GetCorrelatedRadarTarget().GetCallsign());
-    if (this->m_inbounds.end() != it)
-        it->second.update(plan);
-}
-
 void PlugIn::OnRadarTargetPositionUpdate(EuroScopePlugIn::CRadarTarget radarTarget) {
     /* do nothing if the reporter is not initialized and ignore invalid targets */
     if (false == this->m_compatible || false == Backend::instance().initialized() || false == radarTarget.IsValid())
@@ -746,14 +763,18 @@ void PlugIn::OnRadarTargetPositionUpdate(EuroScopePlugIn::CRadarTarget radarTarg
 }
 
 void PlugIn::OnFlightPlanControllerAssignedDataUpdate(EuroScopePlugIn::CFlightPlan flightPlan, int type) {
-    /* handle only relevant changes */
-    if (EuroScopePlugIn::CTR_DATA_TYPE_HEADING != type && EuroScopePlugIn::CTR_DATA_TYPE_DIRECT_TO != type)
-        return;
+    auto radarTarget = flightPlan.GetCorrelatedRadarTarget();
 
-    if (false == flightPlan.GetCorrelatedRadarTarget().IsValid())
+    if (EuroScopePlugIn::CTR_DATA_TYPE_DIRECT_TO != type || false == radarTarget.IsValid())
         return;
 
-    this->updateInbound(flightPlan);
+    std::string waypoint(flightPlan.GetControllerAssignedData().GetDirectToPointName());
+    if (0 != waypoint.length()) {
+        std::lock_guard guard(this->m_inboundsQueueLock);
+        auto it = this->m_inbounds.find(flightPlan.GetCorrelatedRadarTarget().GetCallsign());
+        if (this->m_inbounds.end() != it)
+            it->second.directTo(radarTarget, waypoint);
+    }
 }
 
 void PlugIn::OnFlightPlanDisconnect(EuroScopePlugIn::CFlightPlan flightPlan) {

+ 3 - 4
src/PlugIn.h

@@ -36,10 +36,10 @@ namespace aman {
          * @brief Defines the different internal and external tag functions
          */
         enum class TagItemFunction {
-            ForceToBackendMenu   = 0, /**< Opens the menu to force tracks */
-            ForceToBackendSelect = 1, /**< Reacts on controller selection */
             RunwaySelectMenu     = 2, /**< Opens the runway selection menu */
-            RunwaySelect         = 3  /**< Selects the runway */
+            RunwaySelect         = 3, /**< Selects the runway */
+            DirectToMenu         = 4, /**< Opens the direct to menu */
+            DirectTo             = 5  /**< Selects the direct to */
         };
 
     private:
@@ -60,7 +60,6 @@ namespace aman {
         void generateAircraftReportMessage(EuroScopePlugIn::CRadarTarget& radarTarget, aman::AircraftReport* report);
         void addUpdateQueue(EuroScopePlugIn::CRadarTarget& radarTarget);
         void updateInbound(EuroScopePlugIn::CRadarTarget& radarTarget);
-        void updateInbound(EuroScopePlugIn::CFlightPlan& plan);
         void updateSequence(std::shared_ptr<aman::AircraftSequence>& sequence);
         static void distanceToPredictedIaf(const EuroScopePlugIn::CRadarTarget& radarTarget, const EuroScopePlugIn::CFlightPlan& flightPlan,
                                            const EuroScopePlugIn::CPosition& iafPosition, aman::AircraftReport* report);

+ 57 - 193
src/types/Inbound.cpp

@@ -35,16 +35,11 @@ Inbound::Inbound(EuroScopePlugIn::CRadarTarget& target, const aman::AircraftSche
         m_fixedPlan(inbound.fixed()),
         m_star(inbound.arrivalroute()),
         m_runway(inbound.arrivalrunway()),
-        m_nextStarWaypoint(),
+        m_nextStarWaypoint(0),
         m_arrivalRoute(),
-        m_timeToLose(),
-        m_trackmiles(),
-        m_flighttime(),
-        m_predictedWaypoint() {
+        m_timeToLose() {
     this->createWindTables(wind);
     this->updatePrediction(target, inbound);
-    auto flightplan = target.GetCorrelatedFlightPlan();
-    this->update(flightplan);
 }
 
 void Inbound::createWindTables(const google::protobuf::RepeatedPtrField<aman::WindData>& wind) {
@@ -102,6 +97,11 @@ void Inbound::updatePrediction(EuroScopePlugIn::CRadarTarget& target, const aman
         updatedFlightplan = true;
     }
 
+    /* try to restore the direct to, if possible */
+    std::string directTo;
+    if (this->m_nextStarWaypoint < this->m_arrivalRoute.size())
+        directTo = gsl::at(this->m_arrivalRoute, this->m_nextStarWaypoint).name();
+
     this->m_arrivalRoute.clear();
     this->m_arrivalRoute.reserve(inbound.waypoints_size());
     auto route = target.GetCorrelatedFlightPlan().GetExtractedRoute();
@@ -125,8 +125,15 @@ void Inbound::updatePrediction(EuroScopePlugIn::CRadarTarget& target, const aman
             }
         }
 
-        if (true == found)
+        if (true == found) {
             this->m_arrivalRoute.push_back(ArrivalWaypoint(plannedPoint.name(), coordinate, altitude, ias, pta));
+
+            /* restore the direct to */
+            if (0 != directTo.length() && plannedPoint.name() == directTo) {
+                this->m_nextStarWaypoint = this->m_arrivalRoute.size() - 1;
+                directTo.clear();
+            }
+        }
     }
 }
 
@@ -138,8 +145,7 @@ void Inbound::update(EuroScopePlugIn::CRadarTarget& target, const aman::Aircraft
     this->createWindTables(wind);
 
     this->updatePrediction(target, inbound);
-    auto flightplan = target.GetCorrelatedFlightPlan();
-    this->update(flightplan);
+    this->update(target);
 }
 
 Velocity Inbound::indicatedAirspeed(const Length& altitude) const noexcept {
@@ -281,42 +287,40 @@ int Inbound::matchToPredictedPath(const EuroScopePlugIn::CFlightPlanPositionPred
 
 void Inbound::predictETA(const EuroScopePlugIn::CRadarTarget& target, const EuroScopePlugIn::CFlightPlanPositionPredictions& predictions) {
     if (this->m_arrivalRoute.size() <= this->m_nextStarWaypoint || 1 >= predictions.GetPointsNumber()) {
+        this->m_waypointEstimatedTimeOfArrival = UtcTime::Point();
         this->m_timeToLose = 0_s;
         return;
     }
 
-    /* get the ground speed */
-    Velocity groundspeed;
-    if (0 == target.GetGS())
-        groundspeed = static_cast<float>(target.GetPosition().GetReportedGS()) * knot;
-    else
-        groundspeed = static_cast<float>(target.GetGS()) * knot;
-
     const auto& destination = gsl::at(this->m_arrivalRoute, this->m_nextStarWaypoint);
     Length distanceToNextWaypoint = 0_m;
-    Inbound::matchToPredictedPath(predictions, destination.position(), groundspeed, distanceToNextWaypoint);
-    this->m_trackmiles = distanceToNextWaypoint;
+
+    auto idx = Inbound::matchToPredictedPath(predictions, destination.position(), this->m_groundSpeed, distanceToNextWaypoint);
+    if (idx >= predictions.GetPointsNumber()) {
+        this->m_waypointEstimatedTimeOfArrival = UtcTime::Point();
+        this->m_timeToLose = 0_s;
+    }
 
     /* predict the flight and the descend */
-    Velocity groundSpeed = static_cast<float>(target.GetPosition().GetReportedGS()) * knot;
+    Velocity currentGroundSpeed = static_cast<float>(target.GetPosition().GetReportedGS()) * knot;
     Length altitude = static_cast<float>(target.GetPosition().GetFlightLevel()) * feet;
     Angle heading = __convert(predictions.GetPosition(0)).bearingTo(destination.position());
 
     Time flightTime = 0_s;
     while (0.0_m < distanceToNextWaypoint) {
-        Length distance = groundSpeed * 1_s;
+        Length distance = this->m_groundSpeed * 1_s;
 
         if (altitude > destination.altitude()) {
             /* new descend required based on 3° glide */
             if (((altitude - destination.altitude()).convert(feet) / 1000.0f * 3.0f) > distanceToNextWaypoint.convert(nauticmile)) {
-                const auto oldGS = groundSpeed;
+                const auto oldGS = currentGroundSpeed;
                 const auto descendRate = oldGS * 1_s * std::sinf(0.0523599f);
                 altitude -= descendRate;
 
                 const auto newGS = this->groundSpeed(altitude, this->indicatedAirspeed(altitude), heading);
-                groundSpeed = std::min(groundSpeed, newGS);
+                currentGroundSpeed = std::min(currentGroundSpeed, newGS);
 
-                distance = (groundSpeed + oldGS) * 0.5f * 1_s;
+                distance = (currentGroundSpeed + oldGS) * 0.5f * 1_s;
             }
         }
 
@@ -327,181 +331,40 @@ void Inbound::predictETA(const EuroScopePlugIn::CRadarTarget& target, const Euro
     this->m_waypointEstimatedTimeOfArrival = UtcTime::currentUtc() + std::chrono::seconds(static_cast<long long>(flightTime.convert(second)));
     const auto delta = std::chrono::duration_cast<std::chrono::seconds>(destination.plannedArrivalTime() - UtcTime::currentUtc());
     const auto plannedFlightTime = static_cast<float>(delta.count()) * second;
-    const auto newTTL = plannedFlightTime - flightTime;
-    this->m_flighttime = flightTime;
-
-    this->m_timeToLose = newTTL;
+    this->m_timeToLose = plannedFlightTime - flightTime;
 }
 
 void Inbound::update(EuroScopePlugIn::CRadarTarget& target) {
-    this->predictETA(target, target.GetCorrelatedFlightPlan().GetPositionPredictions());
-}
-
-std::string Inbound::findNextWaypointOnHeading(EuroScopePlugIn::CFlightPlan& plan) {
-    auto target = plan.GetCorrelatedRadarTarget();
-    const auto course(static_cast<float>(target.GetTrackHeading()) * degree);
-
-    const GeoCoordinate currentPosition(static_cast<float>(target.GetPosition().GetPosition().m_Longitude) * degree,
-                                        static_cast<float>(target.GetPosition().GetPosition().m_Latitude) * degree);
-    const auto projectedPosition = currentPosition.projection(course, 300_nm);
-
-    /* calculate the average to get a good cartesian projection point */
-    Angle avgLongitude = currentPosition.longitude(), avgLatitude = currentPosition.latitude();
-    avgLongitude += projectedPosition.longitude();
-    avgLatitude += projectedPosition.latitude();
-
-    for (const auto& waypoint : std::as_const(this->m_arrivalRoute)) {
-        avgLongitude += waypoint.position().longitude();
-        avgLatitude += waypoint.position().latitude();
-    }
-
-    avgLongitude = avgLongitude / static_cast<float>(this->m_arrivalRoute.size() + 2);
-    avgLatitude = avgLatitude / static_cast<float>(this->m_arrivalRoute.size() + 2);
-
-    GeographicLib::Gnomonic projection(GeographicLib::Geodesic::WGS84());
-    Eigen::Vector2f start, end;
-
-    /* create the flight line to find the intersections */
-    projection.Forward(avgLatitude.convert(degree), avgLongitude.convert(degree),
-                       currentPosition.latitude().convert(degree), currentPosition.longitude().convert(degree),
-                       start[0], start[1]);
-    projection.Forward(avgLatitude.convert(degree), avgLongitude.convert(degree),
-                       projectedPosition.latitude().convert(degree), projectedPosition.longitude().convert(degree),
-                       end[0], end[1]);
-
-    Angle offset = 380.0_deg;
-    std::size_t waypointIdx = this->m_arrivalRoute.size();
-    const auto flightLine = Eigen::Hyperplane<float, 2>::Through(start, end);
-    for (std::size_t i = 0; i < this->m_arrivalRoute.size() - 1; ++i) {
-        const auto& waypointStart = gsl::at(this->m_arrivalRoute, i);
-        const auto& waypointEnd = gsl::at(this->m_arrivalRoute, i + 1);
-
-        projection.Forward(avgLatitude.convert(degree), avgLongitude.convert(degree),
-                           waypointStart.position().latitude().convert(degree), waypointStart.position().longitude().convert(degree),
-                           start[0], start[1]);
-        projection.Forward(avgLatitude.convert(degree), avgLongitude.convert(degree),
-                           waypointEnd.position().latitude().convert(degree), waypointEnd.position().longitude().convert(degree),
-                           end[0], end[1]);
-
-        /* get the intersection point */
-        const auto segment = Eigen::Hyperplane<float, 2>::Through(start, end);
-        const auto intersection = flightLine.intersection(segment);
-
-        /*
-         * - check if the intersection is inside the segment
-         * idea:
-         *   - the controller can use wrong headings due to weather and maybe misinterpretations
-         *   - allow an error of 20% for the line to find the best waypoint
-         */
-        const auto segmentLength = (start - end).norm() * 1.2f;
-        const auto intersectionStartLength = (start - intersection).norm();
-        const auto intersectionEndLength = (end - intersection).norm();
-
-        /* outside the segment */
-        if (intersectionStartLength > segmentLength || intersectionEndLength > segmentLength)
-            continue;
-
-        /* check if the offset is better than the last one */
-        const auto deltaAngle = __normalize(waypointStart.position().bearingTo(waypointEnd.position()) - course).abs();
-        if (deltaAngle < offset) {
-            offset = deltaAngle;
-
-            /* find the closer waypoint */
-            if (intersectionStartLength < intersectionEndLength)
-                waypointIdx = i;
-            else
-                waypointIdx = i + 1;
-        }
-    }
-
-    /* found a good waypoint */
-    if (this->m_arrivalRoute.size() > waypointIdx)
-        return gsl::at(this->m_arrivalRoute, waypointIdx).name();
+    /* get the ground speed */
+    if (0 == target.GetGS())
+        this->m_groundSpeed = static_cast<float>(target.GetPosition().GetReportedGS()) * knot;
     else
-        return gsl::at(this->m_arrivalRoute, 0).name();
-}
-
-void Inbound::update(EuroScopePlugIn::CFlightPlan& plan) {
-    this->m_nextStarWaypoint = 0;
-
-    if (0 == this->m_arrivalRoute.size()) {
-        this->m_timeToLose = 0_s;
-        return;
-    }
-
-    /* find the point on the route */
-    std::string direct(plan.GetControllerAssignedData().GetDirectToPointName());
-    if (0 == direct.length())
-        direct = this->findNextWaypointOnHeading(plan);
-    this->m_predictedWaypoint = direct;
-
-    auto route = plan.GetExtractedRoute();
-    int starEntry = route.GetPointsNumber(), directEntry = route.GetPointsNumber();
-
-    /* find the star entry on the route */
-    for (int c = 0; c < route.GetPointsNumber(); ++c) {
-        std::string_view waypointName(route.GetPointName(c));
-        if (waypointName == this->m_arrivalRoute.front().name()) {
-            starEntry = c;
-            break;
-        }
-    }
+        this->m_groundSpeed = static_cast<float>(target.GetGS()) * knot;
 
-    if (0 != direct.length()) {
-        /* find the DIRECT waypoint */
-        for (int c = 0; c < route.GetPointsNumber(); ++c) {
-            std::string_view waypointName(route.GetPointName(c));
-            if (waypointName == direct) {
-                directEntry = c;
-                break;
-            }
-        }
-    }
-    else {
-        const auto& acPosition = __convert(plan.GetCorrelatedRadarTarget().GetPosition().GetPosition());
-        Length minDistance(10000_nm);
-
-        /* search the next waypoint on the route */
-        for (int c = route.GetPointsNumber() - 1; c >= 0; --c) {
-            const auto& position = __convert(route.GetPointPosition(c));
-            const auto distance = position.distanceTo(acPosition);
-
-            if (distance < minDistance) {
-                direct = route.GetPointName(c);
-                minDistance = distance;
-                directEntry = c;
-            }
+    /* check distance to current waypoint and use next one, if needed */
+    if (this->m_nextStarWaypoint < this->m_arrivalRoute.size()) {
+        const auto maxDistance = this->m_groundSpeed * 60_s;
 
-            if (c < starEntry)
-                break;
-        }
+        if (maxDistance > gsl::at(this->m_arrivalRoute, this->m_nextStarWaypoint).position().distanceTo(__convert(target.GetPosition().GetPosition())))
+            this->m_nextStarWaypoint += 1;
     }
 
-    /* search the direct to entry */
-    if (directEntry > starEntry && directEntry < route.GetPointsNumber()) {
-        /* try to find the closest next waypoint */
-        while (0 == this->m_nextStarWaypoint) {
-            for (std::size_t i = 0; i < this->m_arrivalRoute.size(); ++i) {
-                if (direct == gsl::at(this->m_arrivalRoute, i).name()) {
-                    this->m_nextStarWaypoint = i;
-                    break;
-                }
-            }
+    this->predictETA(target, target.GetCorrelatedFlightPlan().GetPositionPredictions());
+}
 
-            if (0 == this->m_nextStarWaypoint) {
-                directEntry += 1;
-                if (directEntry >= route.GetPointsNumber()) {
-                    this->m_nextStarWaypoint = this->m_arrivalRoute.size() - 1;
-                    break;
-                }
+void Inbound::directTo(EuroScopePlugIn::CRadarTarget& radarTarget, const std::string& waypoint) {
+    std::size_t i = 0;
 
-                direct = std::string(route.GetPointName(directEntry));
-            }
+    for (const auto& arrivalPoint : std::as_const(this->m_arrivalRoute)) {
+        if (arrivalPoint.name() == waypoint) {
+            this->m_nextStarWaypoint = i;
+            return;
         }
+        i += 1;
     }
 
-    const auto target = plan.GetCorrelatedRadarTarget();
-    this->predictETA(target, plan.GetPositionPredictions());
+    this->m_nextStarWaypoint = 0;
+    this->update(radarTarget);
 }
 
 bool Inbound::fixedPlan() const noexcept {
@@ -522,14 +385,15 @@ const Time& Inbound::timeToLose() const noexcept {
     return this->m_timeToLose;
 }
 
-const Length& Inbound::trackmiles() const noexcept {
-    return this->m_trackmiles;
-}
+const std::string& Inbound::nextWaypoint() const noexcept {
+    static std::string __fallback;
+
+    if (this->m_nextStarWaypoint < this->m_arrivalRoute.size())
+        return gsl::at(this->m_arrivalRoute, this->m_nextStarWaypoint).name();
 
-const Time& Inbound::flighttime() const noexcept {
-    return this->m_flighttime;
+    return __fallback;
 }
 
-const std::string& Inbound::predictedWaypoint() const noexcept {
-    return this->m_predictedWaypoint;
+const std::vector<ArrivalWaypoint>& Inbound::arrivalRoute() const noexcept {
+    return this->m_arrivalRoute;
 }