give the controller the information he needs and that he can configure the tags
This commit is contained in:
@@ -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);
|
||||
|
||||
151
src/PlugIn.cpp
151
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());
|
||||
|
||||
std::lock_guard guardUpdate(this->m_updateQueueLock);
|
||||
std::lock_guard guardInbound(this->m_inboundsQueueLock);
|
||||
|
||||
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:
|
||||
{
|
||||
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,7 +674,38 @@ 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:
|
||||
@@ -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)
|
||||
auto radarTarget = flightPlan.GetCorrelatedRadarTarget();
|
||||
|
||||
if (EuroScopePlugIn::CTR_DATA_TYPE_DIRECT_TO != type || false == radarTarget.IsValid())
|
||||
return;
|
||||
|
||||
if (false == flightPlan.GetCorrelatedRadarTarget().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) {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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) {
|
||||
/* 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());
|
||||
}
|
||||
|
||||
std::string Inbound::findNextWaypointOnHeading(EuroScopePlugIn::CFlightPlan& plan) {
|
||||
auto target = plan.GetCorrelatedRadarTarget();
|
||||
const auto course(static_cast<float>(target.GetTrackHeading()) * degree);
|
||||
void Inbound::directTo(EuroScopePlugIn::CRadarTarget& radarTarget, const std::string& waypoint) {
|
||||
std::size_t i = 0;
|
||||
|
||||
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;
|
||||
for (const auto& arrivalPoint : std::as_const(this->m_arrivalRoute)) {
|
||||
if (arrivalPoint.name() == waypoint) {
|
||||
this->m_nextStarWaypoint = i;
|
||||
return;
|
||||
}
|
||||
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;
|
||||
|
||||
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());
|
||||
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();
|
||||
|
||||
return __fallback;
|
||||
}
|
||||
|
||||
const Time& Inbound::flighttime() const noexcept {
|
||||
return this->m_flighttime;
|
||||
}
|
||||
|
||||
const std::string& Inbound::predictedWaypoint() const noexcept {
|
||||
return this->m_predictedWaypoint;
|
||||
const std::vector<ArrivalWaypoint>& Inbound::arrivalRoute() const noexcept {
|
||||
return this->m_arrivalRoute;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user