123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256 |
- #!/usr/bin/env python
- import copy
- import time
- from datetime import datetime as dt
- from datetime import timedelta
- import pytz
- from aman.config.Airport import Airport
- from aman.config.AirportSequencing import AirportSequencing
- from aman.config.RHC import RHC
- from aman.sys.aco.Node import Node
- from aman.sys.RecedingHorizonWindow import RecedingHorizonWindow
- from aman.types.Inbound import Inbound
- class RecedingHorizonControl:
- def __init__(self, config : RHC):
- self.Windows = []
- # contains the current index and the missed update counter
- self.AssignedWindow = {}
- self.Configuration = config
- self.FreezedIndex = int(self.Configuration.FixedBeforeArrival.seconds / self.Configuration.WindowSize)
- def insertInWindow(self, inbound : Inbound, usePTA : bool):
- if False == usePTA:
- referenceTime = inbound.EnrouteArrivalTime
- else:
- referenceTime = inbound.PlannedArrivalTime
- inserted = False
- for i in range(0, len(self.Windows)):
- window = self.Windows[i]
- # find the correct window
- if window.StartTime <= referenceTime and window.EndTime > referenceTime:
- self.AssignedWindow[inbound.Callsign] = [ i, 0 ]
- inbound.FixedSequence = i < self.FreezedIndex
- if True == inbound.FixedSequence and None == inbound.PlannedArrivalTime:
- inbound.PlannedArrivalTime = inbound.EnrouteArrivalTime
- window.insert(inbound)
- inserted = True
- break
- # create the new window
- if False == inserted:
- if 0 != len(self.Windows):
- lastWindowTime = self.Windows[-1].EndTime
- else:
- lastWindowTime = dt.utcfromtimestamp(int(time.time())).replace(tzinfo = pytz.UTC)
- timestep = timedelta(seconds = self.Configuration.WindowSize)
- while True:
- self.Windows.append(RecedingHorizonWindow(lastWindowTime, lastWindowTime + timestep))
- if self.Windows[-1].EndTime > referenceTime:
- window = self.Windows[-1]
- window.insert(inbound)
- self.AssignedWindow[inbound.Callsign] = [ len(self.Windows) - 1, 0 ]
- inbound.FixedSequence = len(self.Windows) < self.FreezedIndex
- if True == inbound.FixedSequence and None == inbound.PlannedArrivalTime:
- inbound.PlannedArrivalTime = inbound.EnrouteArrivalTime
- break
- lastWindowTime = self.Windows[-1].EndTime
- window.Inbounds.sort(key = lambda x: x.PlannedArrivalTime if None != x.PlannedArrivalTime else x.EnrouteArrivalTime)
- def updateReport(self, inbound : Inbound):
- # check if we need to update
- if inbound.Callsign in self.AssignedWindow:
- index = self.AssignedWindow[inbound.Callsign][0]
- self.AssignedWindow[inbound.Callsign][1] = 0
- plannedInbound = self.Windows[index].inbound(inbound.Callsign)
- plannedInbound.Report = inbound.Report
- plannedInbound.ReportTime = inbound.ReportTime
- plannedInbound.CurrentPosition = inbound.CurrentPosition
- plannedInbound.RequestedRunway = inbound.RequestedRunway
- # ingore fixed updates
- if True == plannedInbound.FixedSequence or index <= self.FreezedIndex:
- plannedInbound.FixedSequence = True
- return
- plannedInbound.WTC = inbound.WTC
- # check if we need to update the inbound
- if None == plannedInbound.PlannedStar:
- reference = inbound.EnrouteArrivalTime
- if plannedInbound.EnrouteArrivalTime > reference:
- reference = plannedInbound.EnrouteArrivalTime
- if reference < self.Windows[index].StartTime or reference >= self.Windows[index].EndTime:
- self.Windows[index].remove(inbound.Callsign)
- self.AssignedWindow.pop(inbound.Callsign)
- inbound.EnrouteArrivalTime = reference
- self.updateReport(inbound)
- else:
- plannedInbound.EnrouteArrivalTime = reference
- self.Windows[index].Inbounds.sort(key = lambda x: x.PlannedArrivalTime if None != x.PlannedArrivalTime else x.EnrouteArrivalTime)
- else:
- self.insertInWindow(inbound, False)
- def resequenceInbound(self, inbound : Inbound):
- index = self.AssignedWindow[inbound.Callsign][0]
- sequenced = self.Windows[index].inbound(inbound.Callsign)
- if None == sequenced:
- return
- # resynchronized the planned information
- sequenced.PlannedRunway = inbound.PlannedRunway
- sequenced.PlannedStar = inbound.PlannedStar
- sequenced.PlannedArrivalRoute = inbound.PlannedArrivalRoute
- sequenced.PlannedArrivalTime = inbound.PlannedArrivalTime
- sequenced.InitialArrivalTime = inbound.InitialArrivalTime
- sequenced.PlannedTrackmiles = inbound.PlannedTrackmiles
- sequenced.AssignmentMode = inbound.AssignmentMode
- sequenced.ExpectedRunway = inbound.ExpectedRunway
- sequenced.HasValidSequence = True
- # resort the inbound
- if sequenced.PlannedArrivalTime < self.Windows[index].StartTime or sequenced.PlannedArrivalTime >= self.Windows[index].EndTime:
- self.Windows[index].remove(sequenced.Callsign)
- self.AssignedWindow.pop(sequenced.Callsign)
- self.insertInWindow(sequenced, True)
- else:
- sequenced.FixedSequence = index < self.FreezedIndex
- self.Windows[index].Inbounds.sort(key = lambda x: x.PlannedArrivalTime if None != x.PlannedArrivalTime else x.EnrouteArrivalTime)
- def latestFixedInbounds(self, configuration : Airport, sequenceConfiguration : AirportSequencing):
- if 0 == len(self.Windows):
- return None, None
- # create the runway tree
- runwayInbounds = {}
- for runway in sequenceConfiguration.ActiveArrivalRunways:
- runwayInbounds[runway.Runway.Name] = None
- # create the IAF tree
- iafInbounds = {}
- for star in configuration.ArrivalRouteConstraints:
- altitude = configuration.ArrivalRouteConstraints[star][0].Altitude
- iaf = configuration.ArrivalRouteConstraints[star][0].Name
- if iaf not in iafInbounds:
- iafInbounds[iaf] = { altitude : None }
- elif altitude not in iafInbounds[iaf]:
- iafInbounds[iaf][altitude] = None
- # associate the inbounds to the runways and the IAFs
- for i in range(min(self.FreezedIndex, len(self.Windows)), -1, -1):
- for inbound in self.Windows[i].Inbounds:
- if None == inbound.PlannedRunway or None == inbound.PlannedArrivalRoute:
- continue
- node = Node(inbound, None, None, None, None)
- if inbound.PlannedRunway.Name in runwayInbounds:
- if None == runwayInbounds[inbound.PlannedRunway.Name] or runwayInbounds[inbound.PlannedRunway.Name].Inbound.PlannedArrivalTime < node.Inbound.PlannedArrivalTime:
- runwayInbounds[inbound.PlannedRunway.Name] = node
- if inbound.PlannedArrivalRoute[0].Waypoint.Name in iafInbounds:
- delta = 100000.0
- targetLevel = None
- for level in iafInbounds[inbound.PlannedArrivalRoute[0].Waypoint.Name]:
- difference = abs(level - inbound.PlannedArrivalRoute[0].Altitude)
- if difference < delta:
- delta = difference
- targetLevel = level
- if None == iafInbounds[inbound.PlannedArrivalRoute[0].Waypoint.Name][targetLevel]:
- iafInbounds[inbound.PlannedArrivalRoute[0].Waypoint.Name][targetLevel] = node
- elif iafInbounds[inbound.PlannedArrivalRoute[0].Waypoint.Name][targetLevel].Inbound.PlannedArrivalTime < node.Inbound.PlannedArrivalTime:
- iafInbounds[inbound.PlannedArrivalRoute[0].Waypoint.Name][targetLevel] = node
- return runwayInbounds, iafInbounds
- def optimizationRelevantInbounds(self):
- if 0 == len(self.Windows):
- return None, None
- inbounds = []
- if self.FreezedIndex + 1 >= len(self.Windows):
- earliestArrivalTime = dt.utcfromtimestamp(int(time.time())).replace(tzinfo = pytz.UTC)
- earliestArrivalTime += self.Configuration.FixedBeforeArrival
- else:
- earliestArrivalTime = self.Windows[self.FreezedIndex + 1].StartTime
- # check if we have a reconnect in the freezed blocks (VATSIM specific behavior)
- for i in range(0, min(len(self.Windows), self.FreezedIndex + 1)):
- for inbound in self.Windows[i].Inbounds:
- if False == inbound.HasValidSequence:
- inbounds.sort(key = lambda x: x.PlannedArrivalTime if None != x.PlannedArrivalTime else x.EnrouteArrivalTime)
- inbounds.append(copy.deepcopy(inbound))
- # no new inbounds
- if len(self.Windows) <= self.FreezedIndex + 1:
- if 0 == len(inbounds):
- return None, None
- else:
- return inbounds, earliestArrivalTime
- # check the overlapping windows
- for i in range(self.FreezedIndex + 1, len(self.Windows)):
- for inbound in self.Windows[i].Inbounds:
- inbounds.append(copy.deepcopy(inbound))
- if 20 <= len(inbounds):
- break
- # check if we found relevant inbounds
- if 0 != len(inbounds):
- inbounds.sort(key = lambda x: x.PlannedArrivalTime if None != x.PlannedArrivalTime else x.EnrouteArrivalTime)
- return inbounds, earliestArrivalTime
- else:
- return None, None
- def sequence(self):
- inbounds = []
- for i in range(0, len(self.Windows)):
- for inbound in self.Windows[i].Inbounds:
- if True == inbound.HasValidSequence:
- inbounds.append(inbound)
- return inbounds
- def cleanupWindows(self):
- currentUtc = dt.utcfromtimestamp(int(time.time())).replace(tzinfo = pytz.UTC)
- offsetCorrection = 0
- # delete the non-required windows
- while 0 != len(self.Windows) and currentUtc > self.Windows[0].EndTime:
- # cleanup the association table
- for inbound in self.Windows[0].Inbounds:
- self.AssignedWindow.pop(inbound.Callsign)
- offsetCorrection += 1
- self.Windows.pop(0)
- # correct the association table
- if 0 != offsetCorrection:
- for callsign in self.AssignedWindow:
- self.AssignedWindow[callsign][0] -= offsetCorrection
- if self.AssignedWindow[callsign][0] <= self.FreezedIndex:
- self.Windows[self.AssignedWindow[callsign][0]].inbound(callsign).FixedSequence = True
- # delete the non-updated aircrafts and increase the missed-counter for later runs
- callsigns = []
- for callsign in self.AssignedWindow:
- if 2 < self.AssignedWindow[callsign][1]:
- self.Windows[self.AssignedWindow[callsign][0]].remove(callsign)
- callsigns.append(callsign)
- self.AssignedWindow[callsign][1] += 1
- for callsign in callsigns:
- self.AssignedWindow.pop(callsign)
|