Node.py 12 KB

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