Inbound.py 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183
  1. #!/usr/bin/env python
  2. import pytz
  3. import sys
  4. from datetime import datetime, timedelta
  5. from aman.com import AircraftReport_pb2
  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.PerformanceData import PerformanceData
  10. from aman.types.ArrivalRoute import ArrivalRoute
  11. from aman.types.ArrivalTime import ArrivalTime
  12. from aman.types.Runway import Runway
  13. from aman.types.Waypoint import Waypoint
  14. class Inbound:
  15. def findArrivalRoute(self, 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 self.Report.initialApproachFix == star.Iaf.Name:
  21. return star
  22. return None
  23. def __init__(self, report : AircraftReport_pb2.AircraftReport, sequencingConfig : AirportSequencing, navData : SctEseFormat,
  24. performanceData : PerformanceData, weatherModel : WeatherModel):
  25. self.Report = report
  26. self.CurrentPosition = report.position
  27. self.ReportTime = datetime.strptime(report.reportTime + '+0000', '%Y%m%d%H%M%S%z').replace(tzinfo = pytz.UTC)
  28. self.InitialArrivalTime = None
  29. self.EarliestArrivalTime = None
  30. self.PlannedArrivalTime = None
  31. self.EstimatedStarEntryTime = None
  32. self.PlannedRunway = None
  33. self.PlannedStar = None
  34. self.ArrivalCandidates = {}
  35. # search performance data -> fallback to A320
  36. if self.Report.aircraft.type in performanceData.Aircrafts:
  37. self.PerformanceData = performanceData.Aircrafts[self.Report.aircraft.type]
  38. if None == self.PerformanceData:
  39. self.PerformanceData = performanceData.Aircrafts['A320']
  40. # calculate the timings for the different arrival runways
  41. for identifier in sequencingConfig.ActiveArrivalRunways:
  42. star = self.findArrivalRoute(identifier.Runway, navData)
  43. if None != star:
  44. flightTime, flightTimeUntilIaf, trackmiles = self.arrivalEstimation(identifier.Runway, star, weatherModel)
  45. avgSpeed = trackmiles / (float(flightTime.seconds) / 3600.0)
  46. ttg = flightTime - timedelta(minutes = (trackmiles / (avgSpeed * 1.1)) * 60)
  47. ita = self.ReportTime + flightTime
  48. earliest = ita - ttg
  49. self.ArrivalCandidates[identifier.Runway.Name] = ArrivalTime(ttg = ttg, star = star, ita = ita, earliest = earliest,
  50. entry = flightTimeUntilIaf, touchdown = flightTime)
  51. # calculate the first values for later plannings
  52. for candidate in self.ArrivalCandidates:
  53. if None == self.EarliestArrivalTime or self.ArrivalCandidates[candidate].EarliestArrivalTime < self.EarliestArrivalTime:
  54. self.InitialArrivalTime = self.ArrivalCandidates[candidate].InitialArrivalTime
  55. self.EarliestArrivalTime = self.ArrivalCandidates[candidate].EarliestArrivalTime
  56. self.EstimatedStarEntryTime = self.ReportTime + self.ArrivalCandidates[candidate].FlightTimeUntilIaf
  57. self.PlannedStar = self.ArrivalCandidates[candidate].Star
  58. if None != self.PlannedStar:
  59. for runway in navData.Runways[self.Report.destination.upper()]:
  60. if runway.Name == self.PlannedStar.Runway:
  61. self.PlannedRunway = runway
  62. break
  63. def arrivalEstimation(self, runway : Runway, star : ArrivalRoute, weather : WeatherModel):
  64. # calculate remaining trackmiles
  65. trackmiles = self.Report.distanceToIAF
  66. start = star.Route[0]
  67. turnIndices = [ -1, -1 ]
  68. constraints = []
  69. for i in range(0, len(star.Route)):
  70. # identified the base turn
  71. if True == star.Route[i].BaseTurn:
  72. turnIndices[0] = i
  73. # identified the final turn
  74. elif -1 != turnIndices[0] and True == star.Route[i].FinalTurn:
  75. turnIndices[1] = i
  76. # skip waypoints until the final turn point is found
  77. elif -1 != turnIndices[0] and -1 == turnIndices[1]:
  78. continue
  79. trackmiles += start.haversine(star.Route[i]) * 0.539957
  80. # check if a new constraint is defined
  81. altitude = -1
  82. speed = -1
  83. if None != star.Route[i].Altitude:
  84. altitude = star.Route[i].Altitude
  85. if None != star.Route[i].Speed:
  86. speed = star.Route[i].Speed
  87. if -1 != altitude or -1 != speed:
  88. constraints.append([ trackmiles, altitude, speed ])
  89. start = star.Route[i]
  90. # add the remaining distance from the last waypoint to the runway threshold
  91. trackmiles += start.haversine(runway.Start)
  92. if turnIndices[0] > turnIndices[1] or (-1 == turnIndices[1] and -1 != turnIndices[0]):
  93. sys.stderr.write('Invalid constraint definition found for ' + star.Name)
  94. sys.exit(-1)
  95. # calculate descend profile
  96. currentHeading = Waypoint(latitude = self.Report.position.latitude, longitude = self.Report.position.longitude).bearing(star.Route[0])
  97. currentIAS = self.PerformanceData.ias(self.Report.dynamics.altitude, trackmiles)
  98. currentPosition = [ self.Report.dynamics.altitude, self.Report.dynamics.groundSpeed ]
  99. distanceToWaypoint = self.Report.distanceToIAF
  100. flightTimeUntilIafSeconds = 0
  101. flightTimeSeconds = 0
  102. nextWaypointIndex = 0
  103. flownDistance = 0.0
  104. while True:
  105. # check if a constraint cleanup is needed and if a speed-update is needed
  106. if 0 != len(constraints) and flownDistance >= constraints[0][0]:
  107. if -1 != constraints[0][2]:
  108. currentIAS = min(constraints[0][2], self.PerformanceData.ias(self.Report.dynamics.altitude, trackmiles - flownDistance))
  109. currentPosition[1] = min(weather.calculateGS(currentPosition[0], currentIAS, currentHeading), currentPosition[1])
  110. constraints.pop(0)
  111. # search next altitude constraint
  112. altitudeDistance = 0
  113. nextAltitude = 0
  114. for constraint in constraints:
  115. if -1 != constraint[1]:
  116. altitudeDistance = constraint[0]
  117. nextAltitude = constraint[1]
  118. break
  119. # check if update of altitude and speed is needed on 3° glide
  120. if currentPosition[0] > nextAltitude and ((currentPosition[0] - nextAltitude) / 1000 * 3) > (altitudeDistance - flownDistance):
  121. oldGroundspeed = currentPosition[1]
  122. descendRate = (currentPosition[1] / 60) / 3 * 1000 / 6
  123. newAltitude = currentPosition[0] - descendRate
  124. if 0 > newAltitude:
  125. newAltitude = 0
  126. currentPosition = [ newAltitude, min(weather.calculateGS(newAltitude, currentIAS, currentHeading), currentPosition[1]) ]
  127. distance = (currentPosition[1] + oldGroundspeed) / 2 / 60 / 6
  128. else:
  129. distance = currentPosition[1] / 60 / 6
  130. # update the statistics
  131. distanceToWaypoint -= distance
  132. flownDistance += distance
  133. newIAS = min(currentIAS, self.PerformanceData.ias(currentPosition[0], trackmiles - flownDistance))
  134. if newIAS < currentIAS:
  135. currentPosition[1] = min(weather.calculateGS(currentPosition[0], newIAS, currentHeading), currentPosition[1])
  136. currentIAS = newIAS
  137. flightTimeSeconds += 10
  138. if flownDistance <= self.Report.distanceToIAF:
  139. flightTimeUntilIafSeconds += 10
  140. if flownDistance >= trackmiles:
  141. break
  142. # check if we follow a new waypoint pair
  143. if 0 >= distanceToWaypoint:
  144. lastWaypointIndex = nextWaypointIndex
  145. nextWaypointIndex += 1
  146. # check if a skip from base to final turn waypoints is needed
  147. if -1 != turnIndices[0] and nextWaypointIndex > turnIndices[0] and nextWaypointIndex < turnIndices[1]:
  148. nextWaypointIndex = turnIndices[1]
  149. # update the statistics
  150. if nextWaypointIndex < len(star.Route):
  151. distanceToWaypoint = star.Route[lastWaypointIndex].haversine(star.Route[nextWaypointIndex]) * 0.539957
  152. currentHeading = star.Route[lastWaypointIndex].bearing(star.Route[nextWaypointIndex])
  153. currentPosition[1] = min(weather.calculateGS(currentPosition[0], currentIAS, currentHeading), currentPosition[1])
  154. return timedelta(seconds = flightTimeSeconds), timedelta(seconds = flightTimeUntilIafSeconds), trackmiles