|
@@ -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;
|
|
|
}
|