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

This commit is contained in:
Sven Czarnian
2021-12-23 09:58:31 +01:00
parent ec9e9e285b
commit d7c41d1941
4 changed files with 153 additions and 273 deletions

View File

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

View File

@@ -71,8 +71,8 @@ PlugIn::PlugIn() :
this->RegisterTagItemType("PTA", static_cast<int>(PlugIn::TagItemElement::PlannedTimeOfArrival)); this->RegisterTagItemType("PTA", static_cast<int>(PlugIn::TagItemElement::PlannedTimeOfArrival));
this->RegisterTagItemType("Delta time", static_cast<int>(PlugIn::TagItemElement::DeltaTime)); this->RegisterTagItemType("Delta time", static_cast<int>(PlugIn::TagItemElement::DeltaTime));
this->RegisterTagItemType("Fixed plan indicator", static_cast<int>(PlugIn::TagItemElement::FixedPlanIndicator)); 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("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); 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; this->m_sweatboxValid = false;
retval = true; 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 == retval) {
if (true == this->m_sweatboxValid) 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); std::transform(destination.begin(), destination.end(), destination.begin(), ::toupper);
#pragma warning(default: 4244) #pragma warning(default: 4244)
bool planExpected = false, forced = false; bool forced = false;
const gsl::span<char, 16> tagString(itemString, 16UL); const gsl::span<char, 16> tagString(itemString, 16UL);
this->m_updateQueueLock.lock(); this->m_updateQueueLock.lock();
@@ -538,9 +564,6 @@ void PlugIn::OnGetTagItem(EuroScopePlugIn::CFlightPlan flightPlan, EuroScopePlug
forced = this->m_forcedToBackendCallsigns.cend() != cIt; 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(); this->m_updateQueueLock.unlock();
std::lock_guard guard(this->m_inboundsQueueLock); std::lock_guard guard(this->m_inboundsQueueLock);
@@ -550,10 +573,16 @@ void PlugIn::OnGetTagItem(EuroScopePlugIn::CFlightPlan flightPlan, EuroScopePlug
switch (static_cast<TagItemElement>(itemCode)) { switch (static_cast<TagItemElement>(itemCode)) {
case TagItemElement::EstimatedTimeOfArrival: case TagItemElement::EstimatedTimeOfArrival:
{ {
if (this->m_inbounds.cend() != it) if (this->m_inbounds.cend() != it) {
message = UtcTime::timeToString(it->second.eta(), "%H:%M"); const auto eta = it->second.eta();
else if (true == forced || true == planExpected) if (UtcTime::Point() == eta)
message = "??:??";
else
message = UtcTime::timeToString(it->second.eta(), "%H:%M");
}
else if (true == forced) {
message = "??:??"; message = "??:??";
}
break; break;
} }
case TagItemElement::PlannedTimeOfArrival: case TagItemElement::PlannedTimeOfArrival:
@@ -563,9 +592,9 @@ void PlugIn::OnGetTagItem(EuroScopePlugIn::CFlightPlan flightPlan, EuroScopePlug
if (UtcTime::Point() == pta) if (UtcTime::Point() == pta)
message = "??:??"; message = "??:??";
else 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 = "??:??"; message = "??:??";
} }
break; break;
@@ -578,7 +607,7 @@ void PlugIn::OnGetTagItem(EuroScopePlugIn::CFlightPlan flightPlan, EuroScopePlug
stream << ttl; stream << ttl;
message = stream.str(); message = stream.str();
} }
else if (true == forced || true == planExpected) { else if (true == forced) {
message = "??"; message = "??";
} }
break; break;
@@ -586,7 +615,7 @@ void PlugIn::OnGetTagItem(EuroScopePlugIn::CFlightPlan flightPlan, EuroScopePlug
case TagItemElement::FixedPlanIndicator: case TagItemElement::FixedPlanIndicator:
if (this->m_inbounds.cend() != it && false == it->second.fixedPlan()) if (this->m_inbounds.cend() != it && false == it->second.fixedPlan())
message = "*"; message = "*";
else if (this->m_inbounds.cend() == it && (true == forced || true == planExpected)) else if (this->m_inbounds.cend() == it && true == forced)
message = "*"; message = "*";
break; break;
default: default:
@@ -603,54 +632,20 @@ void PlugIn::OnFunctionCall(int functionId, const char* itemString, POINT pt, RE
std::ignore = itemString; std::ignore = itemString;
std::ignore = pt; std::ignore = pt;
const auto radarTarget = this->RadarTargetSelectASEL(); auto radarTarget = this->RadarTargetSelectASEL();
if (false == radarTarget.IsValid() || false == radarTarget.GetCorrelatedFlightPlan().IsValid()) if (false == radarTarget.IsValid() || false == radarTarget.GetCorrelatedFlightPlan().IsValid())
return; return;
std::string callsign(radarTarget.GetCallsign()); std::string callsign(radarTarget.GetCallsign());
std::lock_guard guardUpdate(this->m_updateQueueLock);
std::lock_guard guardInbound(this->m_inboundsQueueLock);
switch (static_cast<PlugIn::TagItemFunction>(functionId)) { 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);
}
}
break;
}
case PlugIn::TagItemFunction::RunwaySelectMenu: case PlugIn::TagItemFunction::RunwaySelectMenu:
{ {
std::string destination(radarTarget.GetCorrelatedFlightPlan().GetFlightPlanData().GetDestination()); std::string destination(radarTarget.GetCorrelatedFlightPlan().GetFlightPlanData().GetDestination());
std::transform(destination.begin(), destination.end(), destination.begin(), ::toupper); std::transform(destination.begin(), destination.end(), destination.begin(), ::toupper);
std::lock_guard guard(this->m_updateQueueLock);
auto it = this->m_updateQueue.find(destination); auto it = this->m_updateQueue.find(destination);
if (this->m_updateQueue.cend() != it) { 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: case PlugIn::TagItemFunction::RunwaySelect:
{ {
std::lock_guard guard(this->m_updateQueueLock);
if (0 == std::strcmp(itemString, "Remove")) { if (0 == std::strcmp(itemString, "Remove")) {
auto it = this->m_selectedRunway.find(callsign); auto it = this->m_selectedRunway.find(callsign);
if (this->m_selectedRunway.end() != it) if (this->m_selectedRunway.end() != it)
@@ -681,7 +674,38 @@ void PlugIn::OnFunctionCall(int functionId, const char* itemString, POINT pt, RE
else else
this->m_selectedRunway.insert({ callsign, itemString }); 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; break;
} }
default: default:
@@ -722,13 +746,6 @@ void PlugIn::updateInbound(EuroScopePlugIn::CRadarTarget& radarTarget) {
it->second.update(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) { void PlugIn::OnRadarTargetPositionUpdate(EuroScopePlugIn::CRadarTarget radarTarget) {
/* do nothing if the reporter is not initialized and ignore invalid targets */ /* do nothing if the reporter is not initialized and ignore invalid targets */
if (false == this->m_compatible || false == Backend::instance().initialized() || false == radarTarget.IsValid()) 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) { void PlugIn::OnFlightPlanControllerAssignedDataUpdate(EuroScopePlugIn::CFlightPlan flightPlan, int type) {
/* handle only relevant changes */ auto radarTarget = flightPlan.GetCorrelatedRadarTarget();
if (EuroScopePlugIn::CTR_DATA_TYPE_HEADING != type && EuroScopePlugIn::CTR_DATA_TYPE_DIRECT_TO != type)
if (EuroScopePlugIn::CTR_DATA_TYPE_DIRECT_TO != type || false == radarTarget.IsValid())
return; return;
if (false == flightPlan.GetCorrelatedRadarTarget().IsValid()) std::string waypoint(flightPlan.GetControllerAssignedData().GetDirectToPointName());
return; if (0 != waypoint.length()) {
std::lock_guard guard(this->m_inboundsQueueLock);
this->updateInbound(flightPlan); 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) { void PlugIn::OnFlightPlanDisconnect(EuroScopePlugIn::CFlightPlan flightPlan) {

View File

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

View File

@@ -35,16 +35,11 @@ Inbound::Inbound(EuroScopePlugIn::CRadarTarget& target, const aman::AircraftSche
m_fixedPlan(inbound.fixed()), m_fixedPlan(inbound.fixed()),
m_star(inbound.arrivalroute()), m_star(inbound.arrivalroute()),
m_runway(inbound.arrivalrunway()), m_runway(inbound.arrivalrunway()),
m_nextStarWaypoint(), m_nextStarWaypoint(0),
m_arrivalRoute(), m_arrivalRoute(),
m_timeToLose(), m_timeToLose() {
m_trackmiles(),
m_flighttime(),
m_predictedWaypoint() {
this->createWindTables(wind); this->createWindTables(wind);
this->updatePrediction(target, inbound); this->updatePrediction(target, inbound);
auto flightplan = target.GetCorrelatedFlightPlan();
this->update(flightplan);
} }
void Inbound::createWindTables(const google::protobuf::RepeatedPtrField<aman::WindData>& wind) { void Inbound::createWindTables(const google::protobuf::RepeatedPtrField<aman::WindData>& wind) {
@@ -102,6 +97,11 @@ void Inbound::updatePrediction(EuroScopePlugIn::CRadarTarget& target, const aman
updatedFlightplan = true; 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.clear();
this->m_arrivalRoute.reserve(inbound.waypoints_size()); this->m_arrivalRoute.reserve(inbound.waypoints_size());
auto route = target.GetCorrelatedFlightPlan().GetExtractedRoute(); 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)); 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->createWindTables(wind);
this->updatePrediction(target, inbound); this->updatePrediction(target, inbound);
auto flightplan = target.GetCorrelatedFlightPlan(); this->update(target);
this->update(flightplan);
} }
Velocity Inbound::indicatedAirspeed(const Length& altitude) const noexcept { 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) { void Inbound::predictETA(const EuroScopePlugIn::CRadarTarget& target, const EuroScopePlugIn::CFlightPlanPositionPredictions& predictions) {
if (this->m_arrivalRoute.size() <= this->m_nextStarWaypoint || 1 >= predictions.GetPointsNumber()) { if (this->m_arrivalRoute.size() <= this->m_nextStarWaypoint || 1 >= predictions.GetPointsNumber()) {
this->m_waypointEstimatedTimeOfArrival = UtcTime::Point();
this->m_timeToLose = 0_s; this->m_timeToLose = 0_s;
return; 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); const auto& destination = gsl::at(this->m_arrivalRoute, this->m_nextStarWaypoint);
Length distanceToNextWaypoint = 0_m; 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 */ /* 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; Length altitude = static_cast<float>(target.GetPosition().GetFlightLevel()) * feet;
Angle heading = __convert(predictions.GetPosition(0)).bearingTo(destination.position()); Angle heading = __convert(predictions.GetPosition(0)).bearingTo(destination.position());
Time flightTime = 0_s; Time flightTime = 0_s;
while (0.0_m < distanceToNextWaypoint) { while (0.0_m < distanceToNextWaypoint) {
Length distance = groundSpeed * 1_s; Length distance = this->m_groundSpeed * 1_s;
if (altitude > destination.altitude()) { if (altitude > destination.altitude()) {
/* new descend required based on 3<> glide */ /* new descend required based on 3<> glide */
if (((altitude - destination.altitude()).convert(feet) / 1000.0f * 3.0f) > distanceToNextWaypoint.convert(nauticmile)) { 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); const auto descendRate = oldGS * 1_s * std::sinf(0.0523599f);
altitude -= descendRate; altitude -= descendRate;
const auto newGS = this->groundSpeed(altitude, this->indicatedAirspeed(altitude), heading); 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))); 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 delta = std::chrono::duration_cast<std::chrono::seconds>(destination.plannedArrivalTime() - UtcTime::currentUtc());
const auto plannedFlightTime = static_cast<float>(delta.count()) * second; const auto plannedFlightTime = static_cast<float>(delta.count()) * second;
const auto newTTL = plannedFlightTime - flightTime; this->m_timeToLose = plannedFlightTime - flightTime;
this->m_flighttime = flightTime;
this->m_timeToLose = newTTL;
} }
void Inbound::update(EuroScopePlugIn::CRadarTarget& target) { void Inbound::update(EuroScopePlugIn::CRadarTarget& target) {
/* get the ground speed */
if (0 == target.GetGS())
this->m_groundSpeed = static_cast<float>(target.GetPosition().GetReportedGS()) * knot;
else
this->m_groundSpeed = static_cast<float>(target.GetGS()) * knot;
/* 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 (maxDistance > gsl::at(this->m_arrivalRoute, this->m_nextStarWaypoint).position().distanceTo(__convert(target.GetPosition().GetPosition())))
this->m_nextStarWaypoint += 1;
}
this->predictETA(target, target.GetCorrelatedFlightPlan().GetPositionPredictions()); this->predictETA(target, target.GetCorrelatedFlightPlan().GetPositionPredictions());
} }
std::string Inbound::findNextWaypointOnHeading(EuroScopePlugIn::CFlightPlan& plan) { void Inbound::directTo(EuroScopePlugIn::CRadarTarget& radarTarget, const std::string& waypoint) {
auto target = plan.GetCorrelatedRadarTarget(); std::size_t i = 0;
const auto course(static_cast<float>(target.GetTrackHeading()) * degree);
const GeoCoordinate currentPosition(static_cast<float>(target.GetPosition().GetPosition().m_Longitude) * degree, for (const auto& arrivalPoint : std::as_const(this->m_arrivalRoute)) {
static_cast<float>(target.GetPosition().GetPosition().m_Latitude) * degree); if (arrivalPoint.name() == waypoint) {
const auto projectedPosition = currentPosition.projection(course, 300_nm); this->m_nextStarWaypoint = i;
return;
/* 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;
} }
i += 1;
} }
/* found a good waypoint */
if (this->m_arrivalRoute.size() > waypointIdx)
return gsl::at(this->m_arrivalRoute, waypointIdx).name();
else
return gsl::at(this->m_arrivalRoute, 0).name();
}
void Inbound::update(EuroScopePlugIn::CFlightPlan& plan) {
this->m_nextStarWaypoint = 0; this->m_nextStarWaypoint = 0;
this->update(radarTarget);
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;
}
}
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;
}
if (c < starEntry)
break;
}
}
/* 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;
}
}
if (0 == this->m_nextStarWaypoint) {
directEntry += 1;
if (directEntry >= route.GetPointsNumber()) {
this->m_nextStarWaypoint = this->m_arrivalRoute.size() - 1;
break;
}
direct = std::string(route.GetPointName(directEntry));
}
}
}
const auto target = plan.GetCorrelatedRadarTarget();
this->predictETA(target, plan.GetPositionPredictions());
} }
bool Inbound::fixedPlan() const noexcept { bool Inbound::fixedPlan() const noexcept {
@@ -522,14 +385,15 @@ const Time& Inbound::timeToLose() const noexcept {
return this->m_timeToLose; return this->m_timeToLose;
} }
const Length& Inbound::trackmiles() const noexcept { const std::string& Inbound::nextWaypoint() const noexcept {
return this->m_trackmiles; static std::string __fallback;
if (this->m_nextStarWaypoint < this->m_arrivalRoute.size())
return gsl::at(this->m_arrivalRoute, this->m_nextStarWaypoint).name();
return __fallback;
} }
const Time& Inbound::flighttime() const noexcept { const std::vector<ArrivalWaypoint>& Inbound::arrivalRoute() const noexcept {
return this->m_flighttime; return this->m_arrivalRoute;
}
const std::string& Inbound::predictedWaypoint() const noexcept {
return this->m_predictedWaypoint;
} }