Inbound.cpp 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363
  1. /*
  2. * Author:
  3. * Sven Czarnian <devel@svcz.de>
  4. * Brief:
  5. * Implements the inbound
  6. * Copyright:
  7. * 2021 Sven Czarnian
  8. * License:
  9. * GNU General Public License v3 (GPLv3)
  10. */
  11. #include <Windows.h>
  12. #include <gsl/gsl>
  13. #include <aman/helper/String.h>
  14. #include <aman/types/Inbound.h>
  15. using namespace aman;
  16. static __inline GeoCoordinate __convert(const EuroScopePlugIn::CPosition& position) {
  17. return GeoCoordinate(static_cast<float>(position.m_Longitude) * degree, static_cast<float>(position.m_Latitude) * degree);
  18. }
  19. Inbound::Inbound(EuroScopePlugIn::CRadarTarget& target, const aman::AircraftSchedule& inbound,
  20. const google::protobuf::RepeatedPtrField<aman::WindData>& wind) :
  21. m_windLevels(),
  22. m_windDirections(),
  23. m_windSpeeds(),
  24. m_performanceData(),
  25. m_fixedPlan(inbound.fixed()),
  26. m_star(inbound.arrivalroute()),
  27. m_runway(inbound.arrivalrunway()),
  28. m_nextStarWaypoint(),
  29. m_arrivalRoute(),
  30. m_timeToLose() {
  31. this->createWindTables(wind);
  32. this->updatePrediction(target, inbound, true);
  33. auto flightplan = target.GetCorrelatedFlightPlan();
  34. this->update(flightplan);
  35. }
  36. void Inbound::createWindTables(const google::protobuf::RepeatedPtrField<aman::WindData>& wind) {
  37. this->m_windLevels.clear();
  38. this->m_windDirections.clear();
  39. this->m_windSpeeds.clear();
  40. this->m_windLevels.reserve(static_cast<std::size_t>(wind.size()));
  41. this->m_windDirections.reserve(static_cast<std::size_t>(wind.size()));
  42. this->m_windSpeeds.reserve(static_cast<std::size_t>(wind.size()));
  43. for (int i = 0; i < wind.size(); ++i) {
  44. const auto& level = wind.Get(i);
  45. this->m_windLevels.push_back(static_cast<float>(level.altitude()));
  46. this->m_windDirections.push_back(static_cast<float>(level.direction()));
  47. this->m_windSpeeds.push_back(static_cast<float>(level.speed()));
  48. }
  49. }
  50. void Inbound::updatePrediction(EuroScopePlugIn::CRadarTarget& target, const aman::AircraftSchedule& inbound, bool forceUpdate) {
  51. bool updatedFlightplan = false;
  52. this->m_performanceData.speedAboveFL240 = static_cast<float>(inbound.performance().iasabovefl240()) * knot;
  53. this->m_performanceData.speedAboveFL100 = static_cast<float>(inbound.performance().iasabovefl100()) * knot;
  54. this->m_performanceData.speedBelowFL100 = static_cast<float>(inbound.performance().iasbelowfl100()) * knot;
  55. this->m_performanceData.speedApproach = static_cast<float>(inbound.performance().iasapproach()) * knot;
  56. if (true == forceUpdate || this->m_star != target.GetCorrelatedFlightPlan().GetFlightPlanData().GetStarName() || this->m_runway != target.GetCorrelatedFlightPlan().GetFlightPlanData().GetArrivalRwy()) {
  57. std::string route(target.GetCorrelatedFlightPlan().GetFlightPlanData().GetRoute());
  58. std::string arrival = this->m_star + "/" + this->m_runway;
  59. auto split = String::splitString(route, " ");
  60. std::string newRoute;
  61. /* create the new route */
  62. if (1 < split.size()) {
  63. for (std::size_t i = 0; i < split.size() - 1; ++i)
  64. newRoute += gsl::at(split, i) + " ";
  65. }
  66. /* check if the last entry is the arrival route */
  67. const auto& lastEntry = gsl::at(split, split.size() - 1);
  68. if (lastEntry.cend() == std::find_if(lastEntry.cbegin(), lastEntry.cend(), ::isdigit))
  69. newRoute += gsl::at(split, split.size() - 1) + " ";
  70. /* add the arrival route */
  71. newRoute += arrival;
  72. /* write into the flight plan */
  73. target.GetCorrelatedFlightPlan().GetFlightPlanData().SetRoute(newRoute.c_str());
  74. target.GetCorrelatedFlightPlan().GetFlightPlanData().AmendFlightPlan();
  75. updatedFlightplan = true;
  76. }
  77. if (true == updatedFlightplan) {
  78. this->m_arrivalRoute.clear();
  79. this->m_arrivalRoute.reserve(inbound.waypoints_size());
  80. auto route = target.GetCorrelatedFlightPlan().GetExtractedRoute();
  81. int lastExtractedIndex = 0;
  82. for (int i = 0; i < inbound.waypoints_size(); ++i) {
  83. const auto& plannedPoint = inbound.waypoints(i);
  84. const auto pta = UtcTime::stringToTime(plannedPoint.pta());
  85. const auto altitude = static_cast<float>(plannedPoint.altitude()) * feet;
  86. const auto ias = static_cast<float>(plannedPoint.indicatedairspeed()) * knot;
  87. GeoCoordinate coordinate;
  88. bool found = false;
  89. for (int c = lastExtractedIndex; c < route.GetPointsNumber(); ++c) {
  90. if (route.GetPointName(c) == plannedPoint.name()) {
  91. coordinate = __convert(route.GetPointPosition(c));
  92. lastExtractedIndex = c;
  93. found = true;
  94. break;
  95. }
  96. }
  97. if (true == found)
  98. this->m_arrivalRoute.push_back(ArrivalWaypoint(plannedPoint.name(), coordinate, altitude, ias, pta));
  99. }
  100. }
  101. }
  102. void Inbound::update(EuroScopePlugIn::CRadarTarget& target, const aman::AircraftSchedule& inbound,
  103. const google::protobuf::RepeatedPtrField<aman::WindData>& wind) {
  104. this->m_fixedPlan = inbound.fixed();
  105. this->m_star = inbound.arrivalroute();
  106. this->m_runway = inbound.arrivalrunway();
  107. this->createWindTables(wind);
  108. this->updatePrediction(target, inbound, false);
  109. auto flightplan = target.GetCorrelatedFlightPlan();
  110. this->update(flightplan);
  111. }
  112. Velocity Inbound::indicatedAirspeed(const Length& altitude) const noexcept {
  113. if (24000_ft <= altitude)
  114. return this->m_performanceData.speedAboveFL240;
  115. else if (10000_ft <= altitude)
  116. return this->m_performanceData.speedAboveFL100;
  117. else if (1000_ft < altitude)
  118. return this->m_performanceData.speedBelowFL100;
  119. else
  120. return this->m_performanceData.speedApproach;
  121. }
  122. template <typename T, typename U>
  123. static __inline U __interpolate(const std::vector<T>& xAxis, const std::vector<U>& yAxis, const T& xValue, bool limit) {
  124. bool inverse = gsl::at(xAxis, 0) > gsl::at(xAxis, xAxis.size() - 1);
  125. /* define the search value */
  126. T value = xValue;
  127. if (true == limit) {
  128. if (true == inverse) {
  129. if (value > gsl::at(xAxis, 0))
  130. value = gsl::at(xAxis, 0);
  131. else if (value < gsl::at(xAxis, xAxis.size() - 1))
  132. value = gsl::at(xAxis, xAxis.size() - 1);
  133. }
  134. else {
  135. if (value < gsl::at(xAxis, 0))
  136. value = gsl::at(xAxis, 0);
  137. else if (value > gsl::at(xAxis, xAxis.size() - 1))
  138. value = gsl::at(xAxis, xAxis.size() - 1);
  139. }
  140. }
  141. /* search the correct value */
  142. for (std::size_t i = 0; i < xAxis.size() - 1; ++i) {
  143. const auto& prevX = gsl::at(xAxis, i);
  144. const auto& nextX = gsl::at(xAxis, i + 1);
  145. bool inside;
  146. if (true == inverse)
  147. inside = prevX >= xValue && nextX <= xValue;
  148. else
  149. inside = prevX <= xValue && nextX >= xValue;
  150. if (true == inside) {
  151. auto ratio = (xValue - prevX) / (nextX - prevX);
  152. const auto& prevY = gsl::at(yAxis, i);
  153. const auto& nextY = gsl::at(yAxis, i + 1);
  154. return prevY + ratio * (nextY - prevY);
  155. }
  156. }
  157. return U();
  158. }
  159. Velocity Inbound::groundSpeed(const Length& altitude, const Velocity& ias, const Angle& heading) {
  160. static std::vector <float> levels = {
  161. 50000.0f, 45000.0f, 40000.0f, 38000.0f, 36000.0f, 34000.0f, 32000.0f, 30000.0f, 28000.0f,
  162. 26000.0f, 24000.0f, 22000.0f, 20000.0f, 18000.0f, 16000.0f, 15000.0f, 14000.0f, 13000.0f,
  163. 12000.0f, 11000.0f, 10000.0f, 9000.0f, 8000.0f, 7000.0f, 6000.0f, 5000.0f, 4000.0f,
  164. 3000.0f, 2000.0f, 1000.0f, 0.0f
  165. };
  166. static std::vector<float> densities = {
  167. 0.18648f, 0.23714f, 0.24617f, 0.33199f, 0.36518f, 0.39444f, 0.42546f, 0.45831f, 0.402506f, 0.432497f, 0.464169f,
  168. 0.60954f, 0.65269f, 0.69815f, 0.74598f, 0.77082f, 0.79628f, 0.82238f, 0.84914f, 0.87655f, 0.90464f, 0.93341f,
  169. 0.96287f, 0.99304f, 1.02393f, 1.05555f, 1.08791f, 1.12102f, 1.1549f, 1.18955f, 1.225f
  170. };
  171. const auto density = __interpolate(levels, densities, altitude.convert(feet), true);
  172. const auto tas = ias * std::sqrtf(1.225f / density);
  173. Angle windDirection(0.0_deg);
  174. Velocity windSpeed(0.0_mps);
  175. if (0 != this->m_windLevels.size()) {
  176. windDirection = __interpolate(this->m_windLevels, this->m_windDirections, altitude.convert(feet), true) * degree;
  177. windSpeed = __interpolate(this->m_windLevels, this->m_windSpeeds, altitude.convert(feet), true) * knot;
  178. }
  179. return tas + windSpeed * std::cosf(windDirection.convert(radian) - heading.convert(radian));
  180. }
  181. static __inline Angle __normalize(const Angle& angle) {
  182. auto retval(angle);
  183. while (-1.0f * 180_deg > retval)
  184. retval += 360_deg;
  185. while (180_deg < retval)
  186. retval -= 360_deg;
  187. return retval;
  188. }
  189. int Inbound::findIndexInPredictedPath(const EuroScopePlugIn::CFlightPlanPositionPredictions& predictions, const GeoCoordinate& position) {
  190. if (0 == predictions.GetPointsNumber())
  191. return 0;
  192. GeoCoordinate lastPosition(__convert(predictions.GetPosition(0)));
  193. for (int i = 1; i < predictions.GetPointsNumber(); ++i) {
  194. GeoCoordinate coordinate(__convert(predictions.GetPosition(i)));
  195. const auto prev = lastPosition.bearingTo(position);
  196. const auto next = coordinate.bearingTo(position);
  197. const auto delta = __normalize(prev - next);
  198. if (100_deg <= delta.abs())
  199. return i;
  200. lastPosition = coordinate;
  201. }
  202. return predictions.GetPointsNumber();
  203. }
  204. void Inbound::update(EuroScopePlugIn::CRadarTarget& target) {
  205. if (this->m_arrivalRoute.size() <= this->m_nextStarWaypoint) {
  206. this->m_timeToLose = 0_s;
  207. return;
  208. }
  209. const auto& destination = gsl::at(this->m_arrivalRoute, this->m_nextStarWaypoint);
  210. const auto& predictions = target.GetCorrelatedFlightPlan().GetPositionPredictions();
  211. GeoCoordinate lastPosition(__convert(target.GetPosition().GetPosition()));
  212. Length distanceToNextWaypoint = 0_m;
  213. /* calculate the distance to the correct waypoint */
  214. for (int i = 0; i < predictions.GetPointsNumber(); ++i) {
  215. GeoCoordinate coordinate(__convert(predictions.GetPosition(i)));
  216. const auto prev = lastPosition.bearingTo(destination.position());
  217. const auto next = coordinate.bearingTo(destination.position());
  218. const auto delta = __normalize(prev - next);
  219. if (100_deg <= delta.abs())
  220. break;
  221. distanceToNextWaypoint += coordinate.distanceTo(lastPosition);
  222. lastPosition = coordinate;
  223. }
  224. /* predict the flight and the descend */
  225. Velocity groundSpeed = static_cast<float>(target.GetPosition().GetReportedGS()) * knot;
  226. Length altitude = static_cast<float>(target.GetPosition().GetFlightLevel()) * feet;
  227. Angle heading = __convert(predictions.GetPosition(0)).bearingTo(destination.position());
  228. Time flightTime = 0_s;
  229. while (0.0_m < distanceToNextWaypoint) {
  230. Length distance = groundSpeed * 1_s;
  231. if (altitude > destination.altitude()) {
  232. /* new descend required based on 3° glide */
  233. if (((altitude - destination.altitude()).convert(feet) / 1000.0f * 3.0f) > distanceToNextWaypoint.convert(nauticmile)) {
  234. const auto oldGS = groundSpeed;
  235. const auto descendRate = oldGS * 1_s * std::sinf(0.0523599f);
  236. altitude -= descendRate;
  237. const auto newGS = this->groundSpeed(altitude, this->indicatedAirspeed(altitude), heading);
  238. groundSpeed = std::min(groundSpeed, newGS);
  239. distance = (groundSpeed + oldGS) * 0.5f * 11_s;
  240. }
  241. }
  242. distanceToNextWaypoint -= distance;
  243. flightTime += 1_s;
  244. }
  245. auto currentUtc = UtcTime::currentUtc();
  246. auto pta = UtcTime::timeToString(destination.plannedArrivalTime());
  247. auto estimated = UtcTime::timeToString(currentUtc + std::chrono::seconds(static_cast<int>(flightTime.convert(second))));
  248. auto delta = std::chrono::duration_cast<std::chrono::seconds>(destination.plannedArrivalTime() - currentUtc);
  249. auto plannedFlightTime = static_cast<float>(delta.count()) * second;
  250. this->m_timeToLose = plannedFlightTime - flightTime;
  251. }
  252. void Inbound::update(EuroScopePlugIn::CFlightPlan& plan) {
  253. this->m_nextStarWaypoint = 0;
  254. if (0 == this->m_arrivalRoute.size())
  255. return;
  256. /* find the point on the route */
  257. std::string_view direct(plan.GetControllerAssignedData().GetDirectToPointName());
  258. auto route = plan.GetExtractedRoute();
  259. int starEntry = route.GetPointsNumber(), directEntry = route.GetPointsNumber();
  260. /* TODO search point if direct is empty */
  261. for (int c = 0; c < route.GetPointsNumber(); ++c) {
  262. std::string_view waypointName(route.GetPointName(c));
  263. if (waypointName == this->m_arrivalRoute.front().name())
  264. starEntry = c;
  265. else if (waypointName == direct)
  266. directEntry = c;
  267. }
  268. /* search the direct to entry */
  269. if (directEntry > starEntry && directEntry < route.GetPointsNumber()) {
  270. /* try to find the closest next waypoint */
  271. while (0 == this->m_nextStarWaypoint) {
  272. for (std::size_t i = 0; i < this->m_arrivalRoute.size(); ++i) {
  273. if (direct == gsl::at(this->m_arrivalRoute, i).name()) {
  274. this->m_nextStarWaypoint = i;
  275. break;
  276. }
  277. }
  278. if (0 == this->m_nextStarWaypoint) {
  279. directEntry += 1;
  280. if (directEntry >= route.GetPointsNumber()) {
  281. this->m_nextStarWaypoint = this->m_arrivalRoute.size() - 1;
  282. break;
  283. }
  284. direct = std::string_view(route.GetPointName(directEntry));
  285. }
  286. }
  287. }
  288. EuroScopePlugIn::CRadarTarget target = plan.GetCorrelatedRadarTarget();
  289. this->update(target);
  290. }
  291. bool Inbound::fixedPlan() const noexcept {
  292. return this->m_fixedPlan;
  293. }
  294. const Time& Inbound::timeToLose() const noexcept {
  295. return this->m_timeToLose;
  296. }