Node.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210
  1. #!/usr/bin/env python
  2. import math
  3. import sys
  4. from datetime import datetime, timedelta
  5. from aman.config.AirportSequencing import AirportSequencing
  6. from aman.formats.SctEseFormat import SctEseFormat
  7. from aman.sys.WeatherModel import WeatherModel
  8. from aman.types.ArrivalData import ArrivalData
  9. from aman.types.ArrivalRoute import ArrivalRoute
  10. from aman.types.ArrivalWaypoint import ArrivalWaypoint
  11. from aman.types.Runway import Runway
  12. from aman.types.Inbound import Inbound
  13. from aman.types.Waypoint import Waypoint
  14. class Node:
  15. def findArrivalRoute(iaf : str, runway : Runway, navData : SctEseFormat):
  16. for arrivalRunway in navData.ArrivalRoutes:
  17. if arrivalRunway == runway.Name:
  18. stars = navData.ArrivalRoutes[arrivalRunway]
  19. for star in stars:
  20. if 0 != len(star.Route) and iaf == star.Iaf.Name:
  21. return star
  22. return None
  23. def updateArrivalWaypoint(self, arrivalRoute, flightTime, altitude, indicatedAirspeed, groundSpeed):
  24. arrivalRoute[-1].FlightTime = timedelta(seconds = flightTime)
  25. arrivalRoute[-1].ETA = self.PredictionTime + arrivalRoute[-1].FlightTime
  26. arrivalRoute[-1].PTA = arrivalRoute[-1].ETA
  27. arrivalRoute[-1].Altitude = altitude
  28. arrivalRoute[-1].IndicatedAirspeed = indicatedAirspeed
  29. arrivalRoute[-1].GroundSpeed = groundSpeed
  30. def arrivalEstimation(self, runway : Runway, star : ArrivalRoute, weather : WeatherModel):
  31. # calculate remaining trackmiles
  32. trackmiles = self.PredictedDistanceToIAF
  33. start = star.Route[0]
  34. turnIndices = [ -1, -1 ]
  35. constraints = []
  36. for i in range(0, len(star.Route)):
  37. # identified the base turn
  38. if True == star.Route[i].BaseTurn:
  39. turnIndices[0] = i
  40. # identified the final turn
  41. elif -1 != turnIndices[0] and True == star.Route[i].FinalTurn:
  42. turnIndices[1] = i
  43. # skip waypoints until the final turn point is found
  44. elif -1 != turnIndices[0] and -1 == turnIndices[1]:
  45. continue
  46. trackmiles += start.haversine(star.Route[i])
  47. # check if a new constraint is defined
  48. altitude = -1
  49. speed = -1
  50. if None != star.Route[i].Altitude:
  51. altitude = star.Route[i].Altitude
  52. if None != star.Route[i].Speed:
  53. speed = star.Route[i].Speed
  54. if -1 != altitude or -1 != speed:
  55. constraints.append([ trackmiles, altitude, speed ])
  56. start = star.Route[i]
  57. # add the remaining distance from the last waypoint to the runway threshold
  58. trackmiles += start.haversine(runway.Start)
  59. if turnIndices[0] > turnIndices[1] or (-1 == turnIndices[1] and -1 != turnIndices[0]):
  60. sys.stderr.write('Invalid constraint definition found for ' + star.Name)
  61. sys.exit(-1)
  62. # calculate descend profile
  63. currentHeading = Waypoint(latitude = self.Inbound.Report.position.latitude, longitude = self.Inbound.Report.position.longitude).bearing(star.Route[0])
  64. currentIAS = self.Inbound.PerformanceData.ias(self.Inbound.Report.dynamics.altitude, trackmiles)
  65. currentPosition = [ self.Inbound.Report.dynamics.altitude, self.Inbound.Report.dynamics.groundSpeed ]
  66. distanceToWaypoint = self.PredictedDistanceToIAF
  67. flightTimeSeconds = 0
  68. nextWaypointIndex = 0
  69. flownDistance = 0.0
  70. arrivalRoute = [ ArrivalWaypoint(waypoint = star.Route[0], trackmiles = distanceToWaypoint) ]
  71. while True:
  72. # check if a constraint cleanup is needed and if a speed-update is needed
  73. if 0 != len(constraints) and flownDistance >= constraints[0][0]:
  74. if -1 != constraints[0][2]:
  75. currentIAS = min(constraints[0][2], self.Inbound.PerformanceData.ias(self.Inbound.Report.dynamics.altitude, trackmiles - flownDistance))
  76. currentPosition[1] = min(weather.calculateGS(currentPosition[0], currentIAS, currentHeading), currentPosition[1])
  77. constraints.pop(0)
  78. # search next altitude constraint
  79. altitudeDistance = 0
  80. nextAltitude = 0
  81. for constraint in constraints:
  82. if -1 != constraint[1]:
  83. altitudeDistance = constraint[0]
  84. nextAltitude = constraint[1]
  85. break
  86. # check if update of altitude and speed is needed on 3° glide
  87. if currentPosition[0] > nextAltitude and ((currentPosition[0] - nextAltitude) / 1000 * 3) > (altitudeDistance - flownDistance):
  88. oldGroundspeed = currentPosition[1]
  89. descendRate = (currentPosition[1] / 60) / 3 * 1000 / 6
  90. newAltitude = currentPosition[0] - descendRate
  91. if 0 > newAltitude:
  92. newAltitude = 0
  93. currentPosition = [ newAltitude, min(weather.calculateGS(newAltitude, currentIAS, currentHeading), currentPosition[1]) ]
  94. distance = (currentPosition[1] + oldGroundspeed) / 2 / 60 / 6
  95. else:
  96. distance = currentPosition[1] / 60 / 6
  97. # update the statistics
  98. distanceToWaypoint -= distance
  99. flownDistance += distance
  100. newIAS = min(currentIAS, self.Inbound.PerformanceData.ias(currentPosition[0], trackmiles - flownDistance))
  101. if newIAS < currentIAS:
  102. currentPosition[1] = min(weather.calculateGS(currentPosition[0], newIAS, currentHeading), currentPosition[1])
  103. currentIAS = newIAS
  104. flightTimeSeconds += 10
  105. if flownDistance >= trackmiles:
  106. self.updateArrivalWaypoint(arrivalRoute, flightTimeSeconds, currentPosition[0], currentIAS, currentPosition[1])
  107. break
  108. # check if we follow a new waypoint pair
  109. if 0 >= distanceToWaypoint:
  110. lastWaypointIndex = nextWaypointIndex
  111. nextWaypointIndex += 1
  112. self.updateArrivalWaypoint(arrivalRoute, flightTimeSeconds, currentPosition[0], currentIAS, currentPosition[1])
  113. # check if a skip from base to final turn waypoints is needed
  114. if -1 != turnIndices[0] and nextWaypointIndex > turnIndices[0] and nextWaypointIndex < turnIndices[1]:
  115. nextWaypointIndex = turnIndices[1]
  116. # update the statistics
  117. if nextWaypointIndex < len(star.Route):
  118. distanceToWaypoint = star.Route[lastWaypointIndex].haversine(star.Route[nextWaypointIndex])
  119. currentHeading = star.Route[lastWaypointIndex].bearing(star.Route[nextWaypointIndex])
  120. currentPosition[1] = min(weather.calculateGS(currentPosition[0], currentIAS, currentHeading), currentPosition[1])
  121. arrivalRoute.append(ArrivalWaypoint(waypoint = star.Route[nextWaypointIndex], trackmiles = arrivalRoute[-1].Trackmiles + distanceToWaypoint))
  122. return timedelta(seconds = flightTimeSeconds), trackmiles, arrivalRoute
  123. def __init__(self, inbound : Inbound, referenceTime : datetime, weatherModel : WeatherModel,
  124. navData : SctEseFormat, sequencingConfig : AirportSequencing):
  125. self.PredictedDistanceToIAF = inbound.Report.distanceToIAF
  126. self.PredictedCoordinate = [ inbound.CurrentPosition.latitude, inbound.CurrentPosition.longitude ]
  127. self.PredictionTime = referenceTime
  128. self.ArrivalCandidates = {}
  129. self.Inbound = inbound
  130. if None == referenceTime or None == sequencingConfig:
  131. return
  132. # predict the distance to IAF
  133. timePrediction = (referenceTime - inbound.ReportTime).total_seconds()
  134. if 0 != timePrediction and 0 != len(sequencingConfig.ActiveArrivalRunways):
  135. # calculate current motion information
  136. course = weatherModel.estimateCourse(inbound.Report.dynamics.altitude, inbound.Report.dynamics.groundSpeed, inbound.Report.dynamics.heading)
  137. tempWaypoint = Waypoint(longitude = inbound.CurrentPosition.longitude, latitude = inbound.CurrentPosition.latitude)
  138. gs = inbound.Report.dynamics.groundSpeed * 0.514444 # ground speed in m/s
  139. distance = gs * timePrediction
  140. self.PredictedCoordinate = tempWaypoint.project(course, distance)
  141. # calculate the bearing between the current position and the IAF
  142. star = Node.findArrivalRoute(inbound.Report.initialApproachFix, sequencingConfig.ActiveArrivalRunways[0].Runway, navData)
  143. # calculate the distance based on the flown distance and update the predicted distance
  144. self.PredictedDistanceToIAF = Waypoint(longitude = self.PredictedCoordinate[1], latitude = self.PredictedCoordinate[0]).haversine(star.Route[0])
  145. if 0.0 > self.PredictedDistanceToIAF:
  146. self.PredictedDistanceToIAF = 0.0
  147. # calculate the timings for the different arrival runways
  148. for identifier in sequencingConfig.ActiveArrivalRunways:
  149. star = Node.findArrivalRoute(self.Inbound.Report.initialApproachFix, identifier.Runway, navData)
  150. if None != star:
  151. flightTime, trackmiles, arrivalRoute = self.arrivalEstimation(identifier.Runway, star, weatherModel)
  152. # calculate average speed gain
  153. avgSpeed = trackmiles / (flightTime.total_seconds() / 3600.0)
  154. avgSpeedDecrease = avgSpeed * 0.80
  155. avgSpeedIncrease = avgSpeed * 1.05
  156. decreasedSpeedFlighttime = (trackmiles / avgSpeedDecrease) * 3600.0 # given in seconds
  157. # calculate shortcut gain and add 15 miles for final and base turn
  158. currentPosition = Waypoint(latitude = self.PredictedCoordinate[0], longitude = self.PredictedCoordinate[1])
  159. shortcutDistance = currentPosition.haversine(identifier.Runway.Start) + 15.0
  160. shortcutFlighttime = (shortcutDistance / avgSpeedIncrease) * 3600.0
  161. if shortcutFlighttime > flightTime.total_seconds():
  162. shortcutFlighttime = flightTime.total_seconds()
  163. # the best TTG is the shortest path with the fastest speed
  164. ttg = timedelta(seconds = flightTime.total_seconds() - shortcutFlighttime)
  165. # the best TTL is the longest path with the slowest speed
  166. ttl = timedelta(seconds = decreasedSpeedFlighttime - flightTime.total_seconds())
  167. ita = self.Inbound.ReportTime + flightTime
  168. earliest = ita - ttg
  169. latest = ita + ttl
  170. self.ArrivalCandidates[identifier.Runway.Name] = ArrivalData(ttg = ttg, star = star, ita = ita, earliest = earliest,
  171. ttl = ttl, latest = latest, route = arrivalRoute,
  172. trackmiles = trackmiles)
  173. if None == self.Inbound.InitialArrivalTime:
  174. self.Inbound.InitialArrivalTime = self.ArrivalCandidates[identifier.Runway.Name].InitialArrivalTime