#!/usr/bin/env python import time from datetime import datetime as dt from datetime import timedelta import pytz from aman.config.RHC import RHC 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] if inbound.PlannedArrivalTime < self.Windows[index].StartTime or inbound.PlannedArrivalTime >= self.Windows[index].EndTime: self.Windows[index].remove(inbound.Callsign) self.AssignedWindow.pop(inbound.Callsign) self.insertInWindow(inbound, True) else: inbound.FixedSequence = index < self.FreezedIndex self.Windows[index].Inbounds.sort(key = lambda x: x.PlannedArrivalTime if None != x.PlannedArrivalTime else x.EnrouteArrivalTime) def lastFixedInboundOnRunway(self, runway : str): # no inbounds available if 0 == len(self.Windows): return None # search from the back to the front to find the last inbound for i in range(min(self.FreezedIndex, len(self.Windows)), -1, -1): for inbound in self.Windows[i].Inbounds: if None != inbound.PlannedRunway and runway == inbound.PlannedRunway.Name: return inbound # no inbound found return None def optimizationRelevantInbounds(self): # no new inbounds if len(self.Windows) <= self.FreezedIndex + 1: return None, None inbounds = [] earliestArrivalTime = self.Windows[self.FreezedIndex + 1].StartTime # check the overlapping windows for i in range(self.FreezedIndex + 1, len(self.Windows)): for inbound in self.Windows[i].Inbounds: inbounds.append(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: 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)