Node.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212
  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. flightTimeOnStarSeconds = 0
  69. nextWaypointIndex = 0
  70. flownDistance = 0.0
  71. arrivalRoute = [ ArrivalWaypoint(waypoint = star.Route[0], trackmiles = distanceToWaypoint) ]
  72. while True:
  73. # check if a constraint cleanup is needed and if a speed-update is needed
  74. if 0 != len(constraints) and flownDistance >= constraints[0][0]:
  75. if -1 != constraints[0][2]:
  76. currentIAS = min(constraints[0][2], self.Inbound.PerformanceData.ias(self.Inbound.Report.dynamics.altitude, trackmiles - flownDistance))
  77. currentPosition[1] = min(weather.calculateGS(currentPosition[0], currentIAS, currentHeading), currentPosition[1])
  78. constraints.pop(0)
  79. # search next altitude constraint
  80. altitudeDistance = 0
  81. nextAltitude = 0
  82. for constraint in constraints:
  83. if -1 != constraint[1]:
  84. altitudeDistance = constraint[0]
  85. nextAltitude = constraint[1]
  86. break
  87. # check if update of altitude and speed is needed on 3° glide
  88. if currentPosition[0] > nextAltitude and ((currentPosition[0] - nextAltitude) / 1000 * 3) > (altitudeDistance - flownDistance):
  89. oldGroundspeed = currentPosition[1]
  90. descendRate = (currentPosition[1] / 60) / 3 * 1000 / 6
  91. newAltitude = currentPosition[0] - descendRate
  92. if 0 > newAltitude:
  93. newAltitude = 0
  94. currentPosition = [ newAltitude, min(weather.calculateGS(newAltitude, currentIAS, currentHeading), currentPosition[1]) ]
  95. distance = (currentPosition[1] + oldGroundspeed) / 2 / 60 / 6
  96. else:
  97. distance = currentPosition[1] / 60 / 6
  98. # update the statistics
  99. distanceToWaypoint -= distance
  100. flownDistance += distance
  101. newIAS = min(currentIAS, self.Inbound.PerformanceData.ias(currentPosition[0], trackmiles - flownDistance))
  102. if newIAS < currentIAS:
  103. currentPosition[1] = min(weather.calculateGS(currentPosition[0], newIAS, currentHeading), currentPosition[1])
  104. currentIAS = newIAS
  105. flightTimeSeconds += 10
  106. if flownDistance >= self.PredictedDistanceToIAF:
  107. flightTimeOnStarSeconds += 10
  108. if flownDistance >= trackmiles:
  109. self.updateArrivalWaypoint(arrivalRoute, flightTimeSeconds, currentPosition[0], currentIAS, currentPosition[1])
  110. break
  111. # check if we follow a new waypoint pair
  112. if 0 >= distanceToWaypoint:
  113. lastWaypointIndex = nextWaypointIndex
  114. nextWaypointIndex += 1
  115. self.updateArrivalWaypoint(arrivalRoute, flightTimeSeconds, currentPosition[0], currentIAS, currentPosition[1])
  116. # check if a skip from base to final turn waypoints is needed
  117. if -1 != turnIndices[0] and nextWaypointIndex > turnIndices[0] and nextWaypointIndex < turnIndices[1]:
  118. nextWaypointIndex = turnIndices[1]
  119. # update the statistics
  120. if nextWaypointIndex < len(star.Route):
  121. distanceToWaypoint = star.Route[lastWaypointIndex].haversine(star.Route[nextWaypointIndex])
  122. currentHeading = star.Route[lastWaypointIndex].bearing(star.Route[nextWaypointIndex])
  123. currentPosition[1] = min(weather.calculateGS(currentPosition[0], currentIAS, currentHeading), currentPosition[1])
  124. arrivalRoute.append(ArrivalWaypoint(waypoint = star.Route[nextWaypointIndex], trackmiles = arrivalRoute[-1].Trackmiles + distanceToWaypoint))
  125. return timedelta(seconds = flightTimeSeconds), trackmiles, arrivalRoute, timedelta(seconds = flightTimeOnStarSeconds)
  126. def __init__(self, inbound : Inbound, referenceTime : datetime, weatherModel : WeatherModel,
  127. navData : SctEseFormat, sequencingConfig : AirportSequencing):
  128. self.PredictedDistanceToIAF = inbound.Report.distanceToIAF
  129. self.PredictedCoordinate = [ inbound.CurrentPosition.latitude, inbound.CurrentPosition.longitude ]
  130. self.PredictionTime = referenceTime
  131. self.ArrivalCandidates = {}
  132. self.Inbound = inbound
  133. if None == referenceTime or None == sequencingConfig:
  134. return
  135. # predict the distance to IAF
  136. timePrediction = (referenceTime - inbound.ReportTime).total_seconds()
  137. if 0 != timePrediction and 0 != len(sequencingConfig.ActiveArrivalRunways):
  138. # calculate current motion information
  139. course = weatherModel.estimateCourse(inbound.Report.dynamics.altitude, inbound.Report.dynamics.groundSpeed, inbound.Report.dynamics.heading)
  140. tempWaypoint = Waypoint(longitude = inbound.CurrentPosition.longitude, latitude = inbound.CurrentPosition.latitude)
  141. gs = inbound.Report.dynamics.groundSpeed * 0.514444 # ground speed in m/s
  142. distance = gs * timePrediction
  143. self.PredictedCoordinate = tempWaypoint.project(course, distance)
  144. # calculate the bearing between the current position and the IAF
  145. star = Node.findArrivalRoute(inbound.Report.initialApproachFix, sequencingConfig.ActiveArrivalRunways[0].Runway, navData)
  146. # calculate the distance based on the flown distance and update the predicted distance
  147. self.PredictedDistanceToIAF = Waypoint(longitude = self.PredictedCoordinate[1], latitude = self.PredictedCoordinate[0]).haversine(star.Route[0])
  148. if 0.0 > self.PredictedDistanceToIAF:
  149. self.PredictedDistanceToIAF = 0.0
  150. # calculate the timings for the different arrival runways
  151. for identifier in sequencingConfig.ActiveArrivalRunways:
  152. star = Node.findArrivalRoute(self.Inbound.Report.initialApproachFix, identifier.Runway, navData)
  153. if None != star:
  154. flightTime, trackmiles, arrivalRoute, flightTimeOnStar = self.arrivalEstimation(identifier.Runway, star, weatherModel)
  155. # calculate average speed gain
  156. avgSpeed = trackmiles / (flightTime.total_seconds() / 3600.0)
  157. avgSpeedDecrease = avgSpeed * 0.80
  158. decreasedSpeedFlighttime = (trackmiles / avgSpeedDecrease) * 3600.0 # given in seconds
  159. # calculate shortcut gain and add 15 miles for final and base turn
  160. currentPosition = Waypoint(latitude = self.PredictedCoordinate[0], longitude = self.PredictedCoordinate[1])
  161. shortcutDistance = currentPosition.haversine(identifier.Runway.Start) + 25.0
  162. shortcutFlighttime = (shortcutDistance / avgSpeed) * 3600.0
  163. if shortcutFlighttime > flightTime.total_seconds():
  164. shortcutFlighttime = flightTime.total_seconds()
  165. # the best TTG is the shortest path with the fastest speed
  166. ttg = timedelta(seconds = flightTime.total_seconds() - shortcutFlighttime)
  167. # the best TTL is the longest path with the slowest speed
  168. ttl = timedelta(seconds = decreasedSpeedFlighttime - flightTime.total_seconds())
  169. ita = self.Inbound.ReportTime + flightTime
  170. earliest = ita - ttg
  171. latest = ita + ttl
  172. self.ArrivalCandidates[identifier.Runway.Name] = ArrivalData(ttg = ttg, star = star, ita = ita, earliest = earliest,
  173. ttl = ttl, latest = latest, route = arrivalRoute,
  174. trackmiles = trackmiles)
  175. if None == self.Inbound.InitialArrivalTime:
  176. self.Inbound.InitialArrivalTime = self.ArrivalCandidates[identifier.Runway.Name].InitialArrivalTime