Inbound.py 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111
  1. #!/usr/bin/env python
  2. import pytz
  3. from datetime import datetime, timedelta
  4. from aman.com import AircraftReport_pb2
  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.PerformanceData import PerformanceData
  9. from aman.types.Waypoint import Waypoint
  10. class Inbound:
  11. def __init__(self, report : AircraftReport_pb2.AircraftReport, sequencingConfig : AirportSequencing, navData : SctEseFormat,
  12. performanceData : PerformanceData, weatherModel : WeatherModel):
  13. self.Report = report
  14. self.CurrentPosition = report.position
  15. self.ReportTime = datetime.strptime(report.reportTime + '+0000', '%Y%m%d%H%M%S%z').replace(tzinfo = pytz.UTC)
  16. # search performance data -> fallback to A320
  17. if self.Report.aircraft.type in performanceData.Aircrafts:
  18. self.PerformanceData = performanceData.Aircrafts[self.Report.aircraft.type]
  19. if None == self.PerformanceData:
  20. self.PerformanceData = performanceData.Aircrafts['A320']
  21. self.findArrivalRunway(sequencingConfig)
  22. self.findArrivalRoute(navData)
  23. duration = self.secondsUntilTouchdown(weatherModel)
  24. self.InitialArrivalTime = self.ReportTime + duration
  25. self.EstimatedArrivalTime = self.InitialArrivalTime
  26. self.EstimatedStarEntryTime = None
  27. def findArrivalRunway(self, sequencingConfig : AirportSequencing):
  28. self.PlannedRunway = None
  29. # find the nearest runway for an initial guess
  30. distance = 100000.0
  31. currentPosition = Waypoint(latitude = self.Report.position.latitude, longitude = self.Report.position.longitude)
  32. for runway in sequencingConfig.ActiveArrivalRunways:
  33. candidateDistance = runway.Runway.Start.haversine(currentPosition)
  34. if distance > candidateDistance:
  35. self.PlannedRunway = runway
  36. distance = candidateDistance
  37. def findArrivalRoute(self, navData : SctEseFormat):
  38. self.PlannedStar = None
  39. if None == self.PlannedRunway:
  40. return
  41. for arrivalRunway in navData.ArrivalRoutes:
  42. if arrivalRunway == self.PlannedRunway.Runway.Name:
  43. stars = navData.ArrivalRoutes[arrivalRunway]
  44. for star in stars:
  45. if 0 != len(star.Route) and self.Report.initialApproachFix == star.Iaf.Name:
  46. self.PlannedStar = star
  47. return
  48. def secondsUntilTouchdown(self, weather : WeatherModel):
  49. if None == self.PlannedRunway or None == self.PlannedStar:
  50. return timedelta(seconds = 0)
  51. # calculate remaining trackmiles
  52. remainingDistanceNM = self.Report.distanceToIAF
  53. start = self.PlannedStar.Route[0]
  54. for i in range(1, len(self.PlannedStar.Route)):
  55. remainingDistanceNM += start.haversine(self.PlannedStar.Route[i]) * 0.539957
  56. start = self.PlannedStar.Route[i]
  57. # calculate descend profile
  58. flightTimeSeconds = 0
  59. currentHeading = Waypoint(latitude = self.Report.position.latitude, longitude = self.Report.position.longitude).bearing(self.PlannedStar.Route[0])
  60. distanceToWaypoint = self.Report.distanceToIAF
  61. nextWaypointIndex = 0
  62. currentPosition = [ self.Report.dynamics.altitude, self.Report.dynamics.groundSpeed ]
  63. while 0 < currentPosition[0]:
  64. lastDistance = remainingDistanceNM
  65. # TODO integrate speed and altitude constraints
  66. # calculate the next position after 10 seconds
  67. if (currentPosition[0] / 1000 * 3) > remainingDistanceNM:
  68. oldGroundspeed = currentPosition[1]
  69. descendRate = (currentPosition[1] / 60) / 3 * 1000 / 6
  70. newAltitude = currentPosition[0] - descendRate
  71. if 0 > newAltitude:
  72. newAltitude = 0
  73. # get the planned IAS and calculate the new altitude and GS out of the predicted information
  74. # we assume that the aircraft only decelerates
  75. ias = self.PerformanceData.ias(newAltitude, remainingDistanceNM)
  76. currentPosition = [ newAltitude, min(weather.calculateGS(newAltitude, int(ias), currentHeading), currentPosition[1]) ]
  77. # use the average between last and current speed to estimate the remaining distance
  78. remainingDistanceNM -= (currentPosition[1] + oldGroundspeed) / 2 / 60 / 6
  79. else:
  80. remainingDistanceNM -= currentPosition[1] / 60 / 6
  81. flightTimeSeconds += 10
  82. if 0 > remainingDistanceNM:
  83. break
  84. # check if we follow a new waypoint pair
  85. distanceToWaypoint -= abs(lastDistance - remainingDistanceNM)
  86. if 0 >= distanceToWaypoint:
  87. nextWaypointIndex += 1
  88. if nextWaypointIndex < len(self.PlannedStar.Route):
  89. currentHeading = self.PlannedStar.Route[nextWaypointIndex - 1].bearing(self.PlannedStar.Route[nextWaypointIndex])
  90. return timedelta(seconds = flightTimeSeconds)