Compare commits
	
		
			226 Commits
		
	
	
		
			a1c48d7851
			...
			feature/op
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 3237f20994 | |||
| 
						 | 
					6426aa2bd4 | ||
| 
						 | 
					716c1090f3 | ||
| 
						 | 
					fdf38b46dd | ||
| 
						 | 
					c9937d39c2 | ||
| 
						 | 
					357f6e7b11 | ||
| 
						 | 
					2fdb73e32e | ||
| 
						 | 
					bf6417c66c | ||
| 
						 | 
					5ef3d4e40a | ||
| 
						 | 
					8ac4977c08 | ||
| 
						 | 
					64fcb8ed01 | ||
| 
						 | 
					57867a2e21 | ||
| 
						 | 
					f6736133a7 | ||
| 
						 | 
					6dc4f6de72 | ||
| 
						 | 
					50b60915f2 | ||
| 
						 | 
					857f278afe | ||
| 
						 | 
					8efed19e34 | ||
| 
						 | 
					b78e1952ee | ||
| 
						 | 
					8d196129f0 | ||
| 
						 | 
					97c7173313 | ||
| 
						 | 
					b441b945b9 | ||
| 
						 | 
					d9bc25a507 | ||
| 
						 | 
					f83febab3a | ||
| 
						 | 
					4f69df6cc7 | ||
| 
						 | 
					1b53084e8c | ||
| 
						 | 
					4439f91633 | ||
| 
						 | 
					13837fdb62 | ||
| 
						 | 
					a2ea26ab5d | ||
| 
						 | 
					83acbdc3b5 | ||
| 
						 | 
					f4fa9eeb18 | ||
| 
						 | 
					fc84aab0f2 | ||
| 
						 | 
					91683ec899 | ||
| 
						 | 
					8301fba2d2 | ||
| 
						 | 
					e265629439 | ||
| 
						 | 
					8ebeef6938 | ||
| 
						 | 
					e1eb50f6d3 | ||
| 
						 | 
					7006e8b835 | ||
| 
						 | 
					dda7a90ef7 | ||
| 
						 | 
					d1e90cfd22 | ||
| 
						 | 
					78d61eb6cc | ||
| 
						 | 
					1acec75d08 | ||
| 
						 | 
					ae96e5be6b | ||
| 
						 | 
					bb6cacd898 | ||
| 
						 | 
					cd5c21d099 | ||
| 
						 | 
					6fd4f4da1b | ||
| 
						 | 
					1bf7d850eb | ||
| 
						 | 
					1e031dcb7c | ||
| 
						 | 
					cbf033f4b6 | ||
| 
						 | 
					760145731d | ||
| 
						 | 
					5242fe0d87 | ||
| 
						 | 
					72959a8e26 | ||
| 
						 | 
					5c2765d67a | ||
| 
						 | 
					bb2aaeba7a | ||
| 
						 | 
					4f21f8968b | ||
| 
						 | 
					ad129dc2b6 | ||
| 
						 | 
					01b12b4398 | ||
| 
						 | 
					ba34ff97a9 | ||
| 
						 | 
					a85bcac1e8 | ||
| 
						 | 
					4fbe9d1060 | ||
| 
						 | 
					74b8ec33d5 | ||
| 
						 | 
					52aefd0966 | ||
| 
						 | 
					4797cef3f7 | ||
| 
						 | 
					92b7e9e429 | ||
| 
						 | 
					70be822e2d | ||
| 
						 | 
					a5cb8914b5 | ||
| 
						 | 
					0a891b99a5 | ||
| 
						 | 
					fb40b0aad9 | ||
| 
						 | 
					9627ae34b7 | ||
| 
						 | 
					efae307e84 | ||
| 
						 | 
					7257ab2956 | ||
| 
						 | 
					9b9746e9bb | ||
| 
						 | 
					848d89a918 | ||
| 
						 | 
					6aa2009cce | ||
| 
						 | 
					22216b6627 | ||
| 
						 | 
					6d5e635d42 | ||
| 
						 | 
					50f3e28baf | ||
| 
						 | 
					f14c6735cc | ||
| 
						 | 
					2b4200ba58 | ||
| 
						 | 
					7636c549db | ||
| 
						 | 
					836ff0a8b2 | ||
| 
						 | 
					8277721edb | ||
| 
						 | 
					3746301b0d | ||
| 
						 | 
					a8419c286f | ||
| 
						 | 
					fa0a94d733 | ||
| 
						 | 
					0e96f0402e | ||
| 
						 | 
					06974b807c | ||
| 
						 | 
					f359ec8189 | ||
| 
						 | 
					be6fe84d77 | ||
| 
						 | 
					3b767489c3 | ||
| 
						 | 
					5f00ea08cf | ||
| 
						 | 
					c767572dee | ||
| 
						 | 
					bd0fdb0899 | ||
| 
						 | 
					21e79a26f0 | ||
| 
						 | 
					693f48c535 | ||
| 
						 | 
					f021baf4cc | ||
| 
						 | 
					18577ebe9a | ||
| 
						 | 
					d2b326fa9c | ||
| 
						 | 
					23d00899fc | ||
| 
						 | 
					74bf3e7439 | ||
| 
						 | 
					60d671ea9a | ||
| 
						 | 
					3affbd9d57 | ||
| 
						 | 
					3560e98ad2 | ||
| 
						 | 
					a9fc6bc701 | ||
| 
						 | 
					7fe1af1def | ||
| 
						 | 
					09fdf42255 | ||
| 
						 | 
					03483953c5 | ||
| 
						 | 
					219ff481c3 | ||
| 
						 | 
					68dbb0b7da | ||
| 
						 | 
					8426d7cd30 | ||
| 
						 | 
					4be95869b0 | ||
| 
						 | 
					f73a0864de | ||
| 
						 | 
					0a51874006 | ||
| 
						 | 
					cae904ee39 | ||
| 
						 | 
					9bf0600488 | ||
| 
						 | 
					50cd3e887e | ||
| 
						 | 
					35d1012bf5 | ||
| 
						 | 
					9028ef0442 | ||
| 
						 | 
					e4ce4ff654 | ||
| 
						 | 
					2d3384f0aa | ||
| 
						 | 
					7452eb595d | ||
| 
						 | 
					f7b8f26e48 | ||
| 
						 | 
					5df1ceb204 | ||
| 
						 | 
					0a3502e98a | ||
| 
						 | 
					52d0373ebb | ||
| 
						 | 
					f74ae4900c | ||
| 
						 | 
					e1663d7742 | ||
| 
						 | 
					38b4865ea5 | ||
| 
						 | 
					8b34f622a3 | ||
| 
						 | 
					eba9e2deab | ||
| 
						 | 
					22e9018807 | ||
| 
						 | 
					bf10649df6 | ||
| 
						 | 
					b3c98cdcea | ||
| 
						 | 
					d3a2784ec6 | ||
| 
						 | 
					9631157b10 | ||
| 
						 | 
					530c9ea731 | ||
| 
						 | 
					39dcd03458 | ||
| 
						 | 
					46e04fca23 | ||
| 
						 | 
					7cdb04c8fd | ||
| 
						 | 
					75224a1952 | ||
| 
						 | 
					8c703c13a1 | ||
| 
						 | 
					e6fc82fd5a | ||
| 
						 | 
					cf8ec3242e | ||
| 
						 | 
					cbbaf0c021 | ||
| 
						 | 
					d6b85b1660 | ||
| 
						 | 
					f4da74febd | ||
| 
						 | 
					d0be115fce | ||
| 
						 | 
					feb4f85dac | ||
| 
						 | 
					d1536804a4 | ||
| 
						 | 
					92d992f92c | ||
| 
						 | 
					07a447136d | ||
| 
						 | 
					921919488f | ||
| 
						 | 
					6f36d8f569 | ||
| 
						 | 
					40ddd7c188 | ||
| 
						 | 
					2ef4a485d8 | ||
| 
						 | 
					63378a347b | ||
| 
						 | 
					7088bd7bcd | ||
| 
						 | 
					9cd46dc4cc | ||
| 
						 | 
					559ab1fa03 | ||
| 
						 | 
					1a23499f61 | ||
| 
						 | 
					7985dda3ce | ||
| 
						 | 
					98285869cd | ||
| 
						 | 
					f795b301a2 | ||
| 
						 | 
					7c90ecc3b5 | ||
| 
						 | 
					f162047767 | ||
| 
						 | 
					094e0c627a | ||
| 
						 | 
					b498fe94d1 | ||
| 
						 | 
					ec019d5006 | ||
| 
						 | 
					7762cbf213 | ||
| 
						 | 
					e450b58428 | ||
| 
						 | 
					014ea5fa0a | ||
| 
						 | 
					9c9e7dd445 | ||
| 
						 | 
					5c235f7d2a | ||
| 
						 | 
					19a9947d3d | ||
| 
						 | 
					3d87c3918b | ||
| 
						 | 
					97a2f24f28 | ||
| 
						 | 
					dd9e725fc2 | ||
| 
						 | 
					ccb3774872 | ||
| 
						 | 
					51c963de52 | ||
| 
						 | 
					7c6d098812 | ||
| 
						 | 
					d191da2303 | ||
| 
						 | 
					12d77d0e71 | ||
| 
						 | 
					33b32befbc | ||
| 
						 | 
					1b2003e879 | ||
| 
						 | 
					7a1d4a5959 | ||
| 
						 | 
					62f2a6c3ed | ||
| 
						 | 
					01ce0f1bfe | ||
| 
						 | 
					6151fc255a | ||
| 
						 | 
					fec26a6d6d | ||
| 
						 | 
					061eb7eac6 | ||
| 
						 | 
					a468f1cc53 | ||
| 
						 | 
					125eef8729 | ||
| 
						 | 
					c09a5ffe77 | ||
| 
						 | 
					c835944e8d | ||
| 
						 | 
					0c97e5aa67 | ||
| 
						 | 
					43589eaa35 | ||
| 
						 | 
					a7541925c7 | ||
| 
						 | 
					3b8989508e | ||
| 
						 | 
					217c9ad742 | ||
| 
						 | 
					91b735df2f | ||
| 
						 | 
					9f6c9f1ff8 | ||
| 
						 | 
					44837e59f1 | ||
| 
						 | 
					5295d1c155 | ||
| 
						 | 
					9887aa48a4 | ||
| 
						 | 
					73e5a42f52 | ||
| 
						 | 
					3313a8d463 | ||
| 
						 | 
					cea52736bf | ||
| 
						 | 
					d388fb5591 | ||
| 
						 | 
					2a83754fa4 | ||
| 
						 | 
					a611a91fe3 | ||
| 
						 | 
					10d06c2f67 | ||
| 
						 | 
					17f11a640a | ||
| 
						 | 
					909cfc9e27 | ||
| 
						 | 
					014379740b | ||
| 
						 | 
					1e043e2765 | ||
| 
						 | 
					9d69a60396 | ||
| 
						 | 
					7f7506104d | ||
| 
						 | 
					e5a773cdcb | ||
| 
						 | 
					a0b00f7c42 | ||
| 
						 | 
					276e50daa3 | ||
| 
						 | 
					2ef1e13bd6 | ||
| 
						 | 
					bba4a75527 | ||
| 
						 | 
					58d2f5f7f4 | ||
| 
						 | 
					5e6301f749 | ||
| 
						 | 
					c07e767ef8 | ||
| 
						 | 
					e02f429364 | ||
| 
						 | 
					36ab891f44 | 
							
								
								
									
										2
									
								
								.gitmodules
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.gitmodules
									
									
									
									
										vendored
									
									
								
							@@ -1,4 +1,4 @@
 | 
				
			|||||||
[submodule "src/protobuf"]
 | 
					[submodule "src/protobuf"]
 | 
				
			||||||
	path = src/protobuf
 | 
						path = src/protobuf
 | 
				
			||||||
	url = git@git.vatsim-germany.org:nav/aman-com.git
 | 
						url = git@git.ascarion.org:vatger/aman-com.git
 | 
				
			||||||
	branch = feature/protobuf
 | 
						branch = feature/protobuf
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										87
									
								
								aman/AMAN.py
									
									
									
									
									
								
							
							
						
						
									
										87
									
								
								aman/AMAN.py
									
									
									
									
									
								
							@@ -2,7 +2,10 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
import glob
 | 
					import glob
 | 
				
			||||||
import os
 | 
					import os
 | 
				
			||||||
 | 
					import random
 | 
				
			||||||
import sys
 | 
					import sys
 | 
				
			||||||
 | 
					from threading import Lock
 | 
				
			||||||
 | 
					import time
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from aman.com import AircraftReport_pb2
 | 
					from aman.com import AircraftReport_pb2
 | 
				
			||||||
from aman.com.Euroscope import Euroscope
 | 
					from aman.com.Euroscope import Euroscope
 | 
				
			||||||
@@ -27,33 +30,32 @@ class AMAN:
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    def __init__(self):
 | 
					    def __init__(self):
 | 
				
			||||||
        # default initialization of members
 | 
					        # default initialization of members
 | 
				
			||||||
        self.systemConfig = None
 | 
					 | 
				
			||||||
        self.aircraftPerformance = None
 | 
					 | 
				
			||||||
        self.receiver = None
 | 
					 | 
				
			||||||
        self.weather = None
 | 
					 | 
				
			||||||
        self.workers = []
 | 
					 | 
				
			||||||
        self.inbounds = {}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def __del__(self):
 | 
					 | 
				
			||||||
        self.release()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def aquire(self):
 | 
					 | 
				
			||||||
        configPath = AMAN.findConfigPath()
 | 
					        configPath = AMAN.findConfigPath()
 | 
				
			||||||
 | 
					        self.SystemConfig = None
 | 
				
			||||||
 | 
					        self.AircraftPerformance = None
 | 
				
			||||||
 | 
					        self.Receiver = None
 | 
				
			||||||
 | 
					        self.Weather = None
 | 
				
			||||||
 | 
					        self.WebUi = None
 | 
				
			||||||
 | 
					        self.Workers = []
 | 
				
			||||||
 | 
					        self.WorkersLock = Lock()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # read all system relevant configuration files
 | 
					        # read all system relevant configuration files
 | 
				
			||||||
        self.systemConfig = System(os.path.join(configPath, 'System.ini'))
 | 
					        self.SystemConfig = System(os.path.join(configPath, 'System.ini'))
 | 
				
			||||||
        print('Parsed System.ini')
 | 
					        print('Parsed System.ini')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # read the aircraft performance data
 | 
					        # read the aircraft performance data
 | 
				
			||||||
        self.aircraftPerformance = AircraftPerformance(os.path.join(configPath, 'PerformanceData.ini'))
 | 
					        self.AircraftPerformance = AircraftPerformance(os.path.join(configPath, 'PerformanceData.ini'))
 | 
				
			||||||
        if None == self.aircraftPerformance:
 | 
					        if None == self.AircraftPerformance:
 | 
				
			||||||
            sys.stderr.write('No aircraft performance data found!')
 | 
					            sys.stderr.write('No aircraft performance data found!')
 | 
				
			||||||
            sys.exit(-1)
 | 
					            sys.exit(-1)
 | 
				
			||||||
        else:
 | 
					        else:
 | 
				
			||||||
            print('Parsed PerformanceData.ini. Extracted ' + str(len(self.aircraftPerformance.aircrafts)) + ' aircrafts')
 | 
					            print('Parsed PerformanceData.ini. Extracted ' + str(len(self.AircraftPerformance.Aircrafts)) + ' aircrafts')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        self.weather = Weather()
 | 
					        # create the communication syb
 | 
				
			||||||
        self.weather.acquire(self.systemConfig.Weather)
 | 
					        self.Weather = Weather(self.SystemConfig.Weather)
 | 
				
			||||||
 | 
					        self.Receiver = Euroscope(configPath, self.SystemConfig.Server, self)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.acquireLock()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # find the airport configurations and create the workers
 | 
					        # find the airport configurations and create the workers
 | 
				
			||||||
        airportsPath = os.path.join(os.path.join(configPath, 'airports'), '*.ini')
 | 
					        airportsPath = os.path.join(os.path.join(configPath, 'airports'), '*.ini')
 | 
				
			||||||
@@ -64,34 +66,45 @@ class AMAN:
 | 
				
			|||||||
            airportConfig = Airport(file, icao)
 | 
					            airportConfig = Airport(file, icao)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            # initialize the worker thread
 | 
					            # initialize the worker thread
 | 
				
			||||||
            worker = Worker()
 | 
					            worker = Worker(icao, airportConfig, self.Weather, self.AircraftPerformance, self.Receiver)
 | 
				
			||||||
            worker.acquire(icao, airportConfig)
 | 
					            self.Workers.append(worker)
 | 
				
			||||||
            self.workers.append(worker)
 | 
					 | 
				
			||||||
            print('Started worker for ' + icao)
 | 
					            print('Started worker for ' + icao)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # create the EuroScope receiver
 | 
					        self.releaseLock()
 | 
				
			||||||
        self.receiver = Euroscope()
 | 
					 | 
				
			||||||
        self.receiver.acquire(configPath, self.systemConfig.Server, self)
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def release(self):
 | 
					        # initialize the random number generator
 | 
				
			||||||
        if None != self.receiver:
 | 
					        random.seed(time.time())
 | 
				
			||||||
            self.receiver.release()
 | 
					 | 
				
			||||||
        self.receiver = None
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if None != self.weather:
 | 
					    def acquireLock(self):
 | 
				
			||||||
            self.weather.release()
 | 
					        if None != self.WorkersLock:
 | 
				
			||||||
        self.weather = None
 | 
					            self.WorkersLock.acquire()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if None != self.workers:
 | 
					    def releaseLock(self):
 | 
				
			||||||
            for worker in self.workers:
 | 
					        if None != self.WorkersLock:
 | 
				
			||||||
                worker.release()
 | 
					            self.WorkersLock.release()
 | 
				
			||||||
        self.workers = None
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def updateAircraftReport(self, report : AircraftReport_pb2.AircraftReport):
 | 
					    def updateAircraftReport(self, report : AircraftReport_pb2.AircraftReport):
 | 
				
			||||||
 | 
					        self.acquireLock()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # find the correct worker for the inbound
 | 
					        # find the correct worker for the inbound
 | 
				
			||||||
        for worker in self.workers:
 | 
					        for worker in self.Workers:
 | 
				
			||||||
            if worker.icao == report.destination:
 | 
					            if worker.Icao == report.destination:
 | 
				
			||||||
                worker.acquireLock()
 | 
					                worker.acquireLock()
 | 
				
			||||||
                worker.reportQueue[report.aircraft.callsign] = report
 | 
					                worker.ReportQueue[report.aircraft.callsign] = report
 | 
				
			||||||
                worker.releaseLock()
 | 
					                worker.releaseLock()
 | 
				
			||||||
                break
 | 
					                break
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.releaseLock()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def findAirport(self, icao : str):
 | 
				
			||||||
 | 
					        self.acquireLock()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        airport = None
 | 
				
			||||||
 | 
					        for worker in self.Workers:
 | 
				
			||||||
 | 
					            if icao == worker.Icao:
 | 
				
			||||||
 | 
					                airport = worker
 | 
				
			||||||
 | 
					                break
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.releaseLock()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return airport
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										1
									
								
								aman/VERSION
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								aman/VERSION
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1 @@
 | 
				
			|||||||
 | 
					0.1.0
 | 
				
			||||||
							
								
								
									
										230
									
								
								aman/app.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										230
									
								
								aman/app.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,230 @@
 | 
				
			|||||||
 | 
					#!/usr/bin/env python
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import json
 | 
				
			||||||
 | 
					import os
 | 
				
			||||||
 | 
					from  subprocess import Popen, PIPE
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from flask import Flask, Response, request
 | 
				
			||||||
 | 
					from flask_cors import CORS, cross_origin
 | 
				
			||||||
 | 
					from json import JSONEncoder
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from aman.AMAN import AMAN
 | 
				
			||||||
 | 
					from aman.config.AirportSequencing import AirportSequencing
 | 
				
			||||||
 | 
					from aman.config.RunwaySequencing import RunwaySequencing
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class InboundEncoder(JSONEncoder):
 | 
				
			||||||
 | 
					    def default(self, o):
 | 
				
			||||||
 | 
					        if None == o.PlannedArrivalTime or None == o.EnrouteArrivalTime or None == o.PlannedRunway:
 | 
				
			||||||
 | 
					            return {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # configure the PTA
 | 
				
			||||||
 | 
					        pta = str(o.PlannedArrivalTime)
 | 
				
			||||||
 | 
					        delimiter = pta.find('.')
 | 
				
			||||||
 | 
					        if -1 == delimiter:
 | 
				
			||||||
 | 
					            delimiter = pta.find('+')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # calculate the delta time
 | 
				
			||||||
 | 
					        delta = int((o.PlannedArrivalTime - o.EnrouteArrivalTime).total_seconds() / 60.0);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return {
 | 
				
			||||||
 | 
					            'callsign' : o.Callsign,
 | 
				
			||||||
 | 
					            'fixed' : o.FixedSequence,
 | 
				
			||||||
 | 
					            'runway' : o.PlannedRunway.Name,
 | 
				
			||||||
 | 
					            'pta' : pta[0:delimiter],
 | 
				
			||||||
 | 
					            'delay' : delta,
 | 
				
			||||||
 | 
					            'wtc' : o.WTC,
 | 
				
			||||||
 | 
					            'iaf' : o.Report.initialApproachFix,
 | 
				
			||||||
 | 
					            'expectedrunway' : o.ExpectedRunway,
 | 
				
			||||||
 | 
					            'assignmentmode' : o.AssignmentMode
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class RunwaySequencingEncoder(JSONEncoder):
 | 
				
			||||||
 | 
					    def default(self, o):
 | 
				
			||||||
 | 
					        return { 'runway' : o.Runway.Name, 'spacing' : o.Spacing }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# initialize the environment variables
 | 
				
			||||||
 | 
					if 'AMAN_PATH' not in os.environ:
 | 
				
			||||||
 | 
					    os.environ['AMAN_PATH'] = 'C:\\Repositories\VATSIM\\AMAN\\aman-sys\\aman'
 | 
				
			||||||
 | 
					if 'AMAN_CONFIG_PATH' not in os.environ:
 | 
				
			||||||
 | 
					    os.environ['AMAN_CONFIG_PATH'] = 'C:\\Repositories\\VATSIM\\AMAN\\config'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# initialize the AMAN and the interface version
 | 
				
			||||||
 | 
					aman = AMAN()
 | 
				
			||||||
 | 
					version = '0.0.0'
 | 
				
			||||||
 | 
					with open(os.path.join(os.environ['AMAN_PATH'], 'VERSION')) as file:
 | 
				
			||||||
 | 
					    version = file.readline()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# initialize the web services
 | 
				
			||||||
 | 
					app = Flask('AMAN')
 | 
				
			||||||
 | 
					cors = CORS(app)
 | 
				
			||||||
 | 
					app.config['CORS_HEADERS'] = 'Content-Type'
 | 
				
			||||||
 | 
					if __name__ == '__main__':
 | 
				
			||||||
 | 
					    app.run()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@app.route('/aman/airports')
 | 
				
			||||||
 | 
					@cross_origin()
 | 
				
			||||||
 | 
					def airports():
 | 
				
			||||||
 | 
					    # get the airports
 | 
				
			||||||
 | 
					    retval = []
 | 
				
			||||||
 | 
					    for airport in aman.Workers:
 | 
				
			||||||
 | 
					        retval.append(airport.Icao)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    data = json.dumps({ 'version' : version, 'airports' : retval }, ensure_ascii=True)
 | 
				
			||||||
 | 
					    return Response(data, status=200, mimetype='application/json')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@app.route('/aman/admin/newuser')
 | 
				
			||||||
 | 
					def newUser():
 | 
				
			||||||
 | 
					    toolpath = os.path.join(os.path.join(os.environ['AMAN_PATH'], 'tools'), 'KeyPairCreator.py')
 | 
				
			||||||
 | 
					    serverKeypath = os.path.join(os.path.join(os.path.join(AMAN.findConfigPath(), 'keys'), 'server'), 'server.key')
 | 
				
			||||||
 | 
					    clientKeypath = os.path.join(os.path.join(AMAN.findConfigPath(), 'keys'), 'clients')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    cmd = ['python', toolpath, '--directory=' + clientKeypath, '--publickey=' + serverKeypath]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    child = Popen(cmd, stdout=PIPE, stderr=PIPE)
 | 
				
			||||||
 | 
					    stdout, _ = child.communicate()
 | 
				
			||||||
 | 
					    if 0 != child.returncode:
 | 
				
			||||||
 | 
					        return Response('{}', status=404, mimetype='application/json')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    keys = stdout.splitlines()
 | 
				
			||||||
 | 
					    server = keys[0].decode('ascii')
 | 
				
			||||||
 | 
					    public = keys[1].decode('ascii')
 | 
				
			||||||
 | 
					    private = keys[2].decode('ascii')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    dictionary = {
 | 
				
			||||||
 | 
					        'server' : server,
 | 
				
			||||||
 | 
					        'public' : public,
 | 
				
			||||||
 | 
					        'private' : private,
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    data = json.dumps(dictionary, ensure_ascii=True)
 | 
				
			||||||
 | 
					    return Response(data, status=200, mimetype='application/json')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@app.route('/aman/configuration/<icao>')
 | 
				
			||||||
 | 
					@cross_origin()
 | 
				
			||||||
 | 
					def configuration(icao):
 | 
				
			||||||
 | 
					    airport = aman.findAirport(icao.upper())
 | 
				
			||||||
 | 
					    if None == airport:
 | 
				
			||||||
 | 
					        return Response('{}', status=404, mimetype='application/json')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # get the current runway configuration
 | 
				
			||||||
 | 
					    config = airport.SequencingConfiguration
 | 
				
			||||||
 | 
					    dependencies = []
 | 
				
			||||||
 | 
					    for dependency in config.RunwayDependencies:
 | 
				
			||||||
 | 
					        rwy0 = config.runway(dependency[0])
 | 
				
			||||||
 | 
					        rwy1 = config.runway(dependency[1])
 | 
				
			||||||
 | 
					        cand1 = [ rwy0.Name, rwy1.Name ]
 | 
				
			||||||
 | 
					        cand2 = [ rwy1.Name, rwy0.Name ]
 | 
				
			||||||
 | 
					        found = False
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        for dep in dependencies:
 | 
				
			||||||
 | 
					            if cand1 == dep or cand2 == dep:
 | 
				
			||||||
 | 
					                found = True
 | 
				
			||||||
 | 
					                break
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if False == found:
 | 
				
			||||||
 | 
					            dependencies.append(cand1)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    runways = airport.Configuration.GngData.Runways[airport.Icao];
 | 
				
			||||||
 | 
					    availableRunways = [];
 | 
				
			||||||
 | 
					    for runway in runways:
 | 
				
			||||||
 | 
					        availableRunways.append(runway.Name);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # get all IAFs of the airport
 | 
				
			||||||
 | 
					    iafs = []
 | 
				
			||||||
 | 
					    for runway in airport.Configuration.GngData.ArrivalRoutes:
 | 
				
			||||||
 | 
					        for route in airport.Configuration.GngData.ArrivalRoutes[runway]:
 | 
				
			||||||
 | 
					            found = False
 | 
				
			||||||
 | 
					            for iaf in iafs:
 | 
				
			||||||
 | 
					                if iaf['name'] == route.Iaf.Name:
 | 
				
			||||||
 | 
					                    found = True
 | 
				
			||||||
 | 
					                    break
 | 
				
			||||||
 | 
					            if False == found:
 | 
				
			||||||
 | 
					                iafs.append({ 'name' : route.Iaf.Name, 'lat' : route.Iaf.Coordinate[0], 'lon' : route.Iaf.Coordinate[1] })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    dictionary = {
 | 
				
			||||||
 | 
					        'airport' : airport.Icao,
 | 
				
			||||||
 | 
					        'useShallShouldMay' : config.UseShallShouldMay,
 | 
				
			||||||
 | 
					        'availableRunways' : availableRunways,
 | 
				
			||||||
 | 
					        'activeRunways' : config.ActiveArrivalRunways,
 | 
				
			||||||
 | 
					        'dependentRunways' : dependencies,
 | 
				
			||||||
 | 
					        'iafColorization' : airport.Configuration.IafColorization,
 | 
				
			||||||
 | 
					        'iafs' : iafs
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    data = json.dumps(dictionary, ensure_ascii=True, cls=RunwaySequencingEncoder)
 | 
				
			||||||
 | 
					    return Response(data, status=200, mimetype='application/json')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@app.route('/aman/sequence/<icao>')
 | 
				
			||||||
 | 
					@cross_origin()
 | 
				
			||||||
 | 
					def sequence(icao):
 | 
				
			||||||
 | 
					    airport = aman.findAirport(icao.upper())
 | 
				
			||||||
 | 
					    if None == airport:
 | 
				
			||||||
 | 
					        return Response('{}', status=404, mimetype='application/json')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # convert the timestamp
 | 
				
			||||||
 | 
					    stamp = str(airport.SequencingConfiguration.LastUpdateTimestamp)
 | 
				
			||||||
 | 
					    delimiter = stamp.find('.')
 | 
				
			||||||
 | 
					    if -1 == delimiter:
 | 
				
			||||||
 | 
					        delimiter = stamp.find('+')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    dictionary = {
 | 
				
			||||||
 | 
					        'airport': airport.Icao,
 | 
				
			||||||
 | 
					        'lastConfigurationUpdate': stamp[0:delimiter],
 | 
				
			||||||
 | 
					        'sequence': airport.inboundSequence()
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    data = json.dumps(dictionary, ensure_ascii=True, cls=InboundEncoder)
 | 
				
			||||||
 | 
					    return Response(data, status=200, mimetype='application/json')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@app.route('/aman/configure', methods=['POST'])
 | 
				
			||||||
 | 
					@cross_origin()
 | 
				
			||||||
 | 
					def configure():
 | 
				
			||||||
 | 
					    data = request.get_json()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # validate that the airport exists
 | 
				
			||||||
 | 
					    if 'airport' not in data:
 | 
				
			||||||
 | 
					        return Response('{}', status=404, mimetype='application/json')
 | 
				
			||||||
 | 
					    airport = aman.findAirport(data['airport'].upper())
 | 
				
			||||||
 | 
					    if None == airport:
 | 
				
			||||||
 | 
					        return Response('{}', status=404, mimetype='application/json')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # check that all top-level information are available
 | 
				
			||||||
 | 
					    if 'useShallShouldMay' not in data or 'activeRunways' not in data or 'dependentRunways' not in data:
 | 
				
			||||||
 | 
					        return Response('{}', status=404, mimetype='application/json')
 | 
				
			||||||
 | 
					    if False == isinstance(data['useShallShouldMay'], bool) or 0 == len(data['activeRunways']):
 | 
				
			||||||
 | 
					        return Response('{}', status=404, mimetype='application/json')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # create the toplevel information
 | 
				
			||||||
 | 
					    config = AirportSequencing(airport.Icao)
 | 
				
			||||||
 | 
					    config.Airport = data['airport'].upper()
 | 
				
			||||||
 | 
					    config.UseShallShouldMay = data['useShallShouldMay']
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # parse the active runways
 | 
				
			||||||
 | 
					    for activeRunway in data['activeRunways']:
 | 
				
			||||||
 | 
					        if 'runway' not in activeRunway or 'spacing' not in activeRunway:
 | 
				
			||||||
 | 
					            return Response('{}', status=404, mimetype='application/json')
 | 
				
			||||||
 | 
					        if False == isinstance(activeRunway['runway'], str) or False == isinstance(activeRunway['spacing'], int):
 | 
				
			||||||
 | 
					            return Response('{}', status=404, mimetype='application/json')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        gngRunway = None
 | 
				
			||||||
 | 
					        for runway in airport.Configuration.GngData.Runways[airport.Icao]:
 | 
				
			||||||
 | 
					            if runway.Name == activeRunway['runway']:
 | 
				
			||||||
 | 
					                gngRunway = runway
 | 
				
			||||||
 | 
					                break
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # could not find the runway
 | 
				
			||||||
 | 
					        if None == gngRunway:
 | 
				
			||||||
 | 
					            return None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        runway = RunwaySequencing(gngRunway)
 | 
				
			||||||
 | 
					        runway.Spacing = activeRunway['spacing']
 | 
				
			||||||
 | 
					        config.activateRunway(runway)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # parse the dependent runways
 | 
				
			||||||
 | 
					    for dependency in data['dependentRunways']:
 | 
				
			||||||
 | 
					        if 2 != len(dependency) or False == isinstance(dependency[0], str) or False == isinstance(dependency[1], str):
 | 
				
			||||||
 | 
					            return Response('{}', status=404, mimetype='application/json')
 | 
				
			||||||
 | 
					        if False == config.addDependency(dependency[0], dependency[1]):
 | 
				
			||||||
 | 
					            return Response('{}', status=404, mimetype='application/json')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    airport.Configuration.assignmentUpdate(config)
 | 
				
			||||||
 | 
					    airport.configure(config)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return Response('{}', status=200, mimetype='application/json')
 | 
				
			||||||
@@ -19,8 +19,8 @@ from datetime import datetime as dt
 | 
				
			|||||||
#               - third  element of wind data tuple: wind speed (KT)
 | 
					#               - third  element of wind data tuple: wind speed (KT)
 | 
				
			||||||
class DwdCrawler():
 | 
					class DwdCrawler():
 | 
				
			||||||
    def __init__(self):
 | 
					    def __init__(self):
 | 
				
			||||||
        self.updateTime = None
 | 
					        self.UpdateTime = None
 | 
				
			||||||
        self.windData = None
 | 
					        self.WindData = None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def parseGaforAreas(areas : str):
 | 
					    def parseGaforAreas(areas : str):
 | 
				
			||||||
        areas = areas.replace(':', '')
 | 
					        areas = areas.replace(':', '')
 | 
				
			||||||
@@ -57,6 +57,9 @@ class DwdCrawler():
 | 
				
			|||||||
            altitude = int(altitude.replace('FL', '')) * 100
 | 
					            altitude = int(altitude.replace('FL', '')) * 100
 | 
				
			||||||
        else:
 | 
					        else:
 | 
				
			||||||
            altitude = int(altitude.replace('FT', ''))
 | 
					            altitude = int(altitude.replace('FT', ''))
 | 
				
			||||||
 | 
					        if 'VRB' == windData[0]:
 | 
				
			||||||
 | 
					            row = ( altitude, 0, int(windData[1].replace('KT', '')) )
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
            row = ( altitude, int(windData[0]), int(windData[1].replace('KT', '')) )
 | 
					            row = ( altitude, int(windData[0]), int(windData[1].replace('KT', '')) )
 | 
				
			||||||
        table.append(row)
 | 
					        table.append(row)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -107,7 +110,8 @@ class DwdCrawler():
 | 
				
			|||||||
                for line in content.splitlines():
 | 
					                for line in content.splitlines():
 | 
				
			||||||
                    if '' == line:
 | 
					                    if '' == line:
 | 
				
			||||||
                        if 0 != len(windTable):
 | 
					                        if 0 != len(windTable):
 | 
				
			||||||
                            windInformation.append(( areaIds, windTable ))
 | 
					                            for id in areaIds:
 | 
				
			||||||
 | 
					                                windInformation.append([ id, windTable ])
 | 
				
			||||||
                        areaIds = None
 | 
					                        areaIds = None
 | 
				
			||||||
                        windTable = []
 | 
					                        windTable = []
 | 
				
			||||||
                    elif line.startswith('GAFOR-Gebiete'):
 | 
					                    elif line.startswith('GAFOR-Gebiete'):
 | 
				
			||||||
@@ -125,8 +129,8 @@ class DwdCrawler():
 | 
				
			|||||||
                    return nextUpdate, windInformation
 | 
					                    return nextUpdate, windInformation
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def receiveWindData(self):
 | 
					    def receiveWindData(self):
 | 
				
			||||||
        self.updateTime = None
 | 
					        self.UpdateTime = None
 | 
				
			||||||
        self.windData = None
 | 
					        self.WindData = None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        with urllib.request.urlopen('https://www.dwd.de/DE/fachnutzer/luftfahrt/teaser/luftsportberichte/luftsportberichte_node.html') as site:
 | 
					        with urllib.request.urlopen('https://www.dwd.de/DE/fachnutzer/luftfahrt/teaser/luftsportberichte/luftsportberichte_node.html') as site:
 | 
				
			||||||
            data = site.read().decode('utf-8')
 | 
					            data = site.read().decode('utf-8')
 | 
				
			||||||
@@ -141,17 +145,18 @@ class DwdCrawler():
 | 
				
			|||||||
                    pages.append('https://www.dwd.de/' + link['href'].split(';')[0])
 | 
					                    pages.append('https://www.dwd.de/' + link['href'].split(';')[0])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            # receive the wind data
 | 
					            # receive the wind data
 | 
				
			||||||
            self.updateTime = None
 | 
					            self.UpdateTime = None
 | 
				
			||||||
            self.windData = []
 | 
					            self.WindData = {}
 | 
				
			||||||
            for page in pages:
 | 
					            for page in pages:
 | 
				
			||||||
                next, wind = self.parseGaforPage(page)
 | 
					                next, wind = self.parseGaforPage(page)
 | 
				
			||||||
                if None != next:
 | 
					                if None != next:
 | 
				
			||||||
                    if None == self.updateTime or self.updateTime > next:
 | 
					                    if None == self.UpdateTime or self.UpdateTime > next:
 | 
				
			||||||
                        self.updateTime = next
 | 
					                        self.UpdateTime = next
 | 
				
			||||||
                    self.windData.extend(wind)
 | 
					                    for gafor in wind:
 | 
				
			||||||
 | 
					                        self.WindData[gafor[0]] = gafor[1]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            # indicate that new wind data is available
 | 
					            # indicate that new wind data is available
 | 
				
			||||||
            if None != self.updateTime:
 | 
					            if None != self.UpdateTime:
 | 
				
			||||||
                return True
 | 
					                return True
 | 
				
			||||||
            else:
 | 
					            else:
 | 
				
			||||||
                return False
 | 
					                return False
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,6 +1,5 @@
 | 
				
			|||||||
#!/usr/bin/env python
 | 
					#!/usr/bin/env python
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import ctypes
 | 
					 | 
				
			||||||
import glob
 | 
					import glob
 | 
				
			||||||
import os
 | 
					import os
 | 
				
			||||||
import sys
 | 
					import sys
 | 
				
			||||||
@@ -9,63 +8,49 @@ import time
 | 
				
			|||||||
import zmq
 | 
					import zmq
 | 
				
			||||||
import zmq.auth
 | 
					import zmq.auth
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from aman.com import AircraftReport_pb2
 | 
					from aman.com import Communication_pb2
 | 
				
			||||||
from aman.config.Server import Server
 | 
					from aman.config.Server import Server
 | 
				
			||||||
from threading import Thread, _active
 | 
					from threading import Thread
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class ReceiverThread(Thread):
 | 
					class ComThread(Thread):
 | 
				
			||||||
    def __init__(self, socket, aman):
 | 
					    def __init__(self, com, aman):
 | 
				
			||||||
        Thread.__init__(self)
 | 
					        Thread.__init__(self)
 | 
				
			||||||
        self.socket = socket
 | 
					        self.Com = com
 | 
				
			||||||
        self.aman = aman
 | 
					        self.AMAN = aman
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def run(self):
 | 
					    def run(self):
 | 
				
			||||||
        while True:
 | 
					        while True:
 | 
				
			||||||
            try:
 | 
					            try:
 | 
				
			||||||
                msg = self.socket.recv(zmq.NOBLOCK)
 | 
					                msg = self.Com.Socket.recv(zmq.NOBLOCK)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                # parse the received message
 | 
					                # parse the received message
 | 
				
			||||||
                report = AircraftReport_pb2.AircraftReport()
 | 
					                report = Communication_pb2.AircraftUpdate()
 | 
				
			||||||
                report.ParseFromString(msg)
 | 
					                report.ParseFromString(msg)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                # try to associate the received aircraft to an airport
 | 
					                # try to associate the received aircrafts to airports
 | 
				
			||||||
                self.aman.updateAircraftReport(report)
 | 
					                for inbound in report.reports:
 | 
				
			||||||
 | 
					                    self.AMAN.updateAircraftReport(inbound)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                # get the sequence of the airport
 | 
				
			||||||
 | 
					                if None != report.airport:
 | 
				
			||||||
 | 
					                    airport = self.AMAN.findAirport(report.airport)
 | 
				
			||||||
 | 
					                    if None != airport:
 | 
				
			||||||
 | 
					                        self.Com.sendSequence(report.airport, airport.inboundSequence(), airport.WeatherModel)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            except zmq.ZMQError as error:
 | 
					            except zmq.ZMQError as error:
 | 
				
			||||||
                if zmq.EAGAIN == error.errno:
 | 
					                if zmq.EAGAIN == error.errno:
 | 
				
			||||||
                    time.sleep(0.5)
 | 
					                    time.sleep(0.1)
 | 
				
			||||||
                    continue
 | 
					                    continue
 | 
				
			||||||
                else:
 | 
					                else:
 | 
				
			||||||
                    return
 | 
					                    return
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def threadId(self):
 | 
					 | 
				
			||||||
        if hasattr(self, '_thread_id'):
 | 
					 | 
				
			||||||
            return self._thread_id
 | 
					 | 
				
			||||||
        for id, thread in _active.items():
 | 
					 | 
				
			||||||
            if thread is self:
 | 
					 | 
				
			||||||
                return id
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def stopThread(self):
 | 
					 | 
				
			||||||
        id = self.threadId()
 | 
					 | 
				
			||||||
        res = ctypes.pythonapi.PyThreadState_SetAsyncExc(id, ctypes.py_object(SystemExit))
 | 
					 | 
				
			||||||
        if 1 < res:
 | 
					 | 
				
			||||||
            ctypes.pythonapi.PyThreadState_SetAsyncExc(id, 0)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# @brief Receives and sends messages to EuroScope plugins
 | 
					# @brief Receives and sends messages to EuroScope plugins
 | 
				
			||||||
class Euroscope:
 | 
					class Euroscope:
 | 
				
			||||||
    def __init__(self):
 | 
					    def __init__(self, configPath : str, config : Server, aman):
 | 
				
			||||||
        self.context = None
 | 
					        self.Context = None
 | 
				
			||||||
        self.receiverSocket = None
 | 
					        self.Socket = None
 | 
				
			||||||
        self.receiverThread = None
 | 
					        self.Thread = None
 | 
				
			||||||
        self.notificationSocket = None
 | 
					        self.Context = zmq.Context()
 | 
				
			||||||
 | 
					 | 
				
			||||||
    def __del__(self):
 | 
					 | 
				
			||||||
        self.release()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    # @brief Initializes the ZMQ socket
 | 
					 | 
				
			||||||
    # @param[in] config The server configuration
 | 
					 | 
				
			||||||
    def acquire(self, configPath : str, config : Server, aman):
 | 
					 | 
				
			||||||
        self.context = zmq.Context()
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # find the key directories
 | 
					        # find the key directories
 | 
				
			||||||
        serverKeyPath = os.path.join(os.path.join(configPath, 'keys'), 'server')
 | 
					        serverKeyPath = os.path.join(os.path.join(configPath, 'keys'), 'server')
 | 
				
			||||||
@@ -88,34 +73,62 @@ class Euroscope:
 | 
				
			|||||||
        keyPair = zmq.auth.load_certificate(keyPairPath[0])
 | 
					        keyPair = zmq.auth.load_certificate(keyPairPath[0])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # initialize the receiver
 | 
					        # initialize the receiver
 | 
				
			||||||
        self.receiverSocket = zmq.Socket(self.context, zmq.SUB)
 | 
					        self.Socket = zmq.Socket(self.Context, zmq.REP)
 | 
				
			||||||
        self.receiverSocket.setsockopt(zmq.CURVE_PUBLICKEY, keyPair[0])
 | 
					        self.Socket.setsockopt(zmq.CURVE_PUBLICKEY, keyPair[0])
 | 
				
			||||||
        self.receiverSocket.setsockopt(zmq.CURVE_SECRETKEY, keyPair[1])
 | 
					        self.Socket.setsockopt(zmq.CURVE_SECRETKEY, keyPair[1])
 | 
				
			||||||
        self.receiverSocket.setsockopt(zmq.CURVE_SERVER, True)
 | 
					        self.Socket.setsockopt(zmq.CURVE_SERVER, True)
 | 
				
			||||||
        self.receiverSocket.bind('tcp://' + config.Address + ':' + str(config.PortReceiver))
 | 
					        self.Socket.bind('tcp://' + config.Address + ':' + str(config.PortReceiver))
 | 
				
			||||||
        self.receiverSocket.setsockopt(zmq.SUBSCRIBE, b'')
 | 
					        #self.Socket.setsockopt(zmq.SUBSCRIBE, b'')
 | 
				
			||||||
        self.receiverThread = ReceiverThread(self.receiverSocket, aman)
 | 
					        self.Thread = ComThread(self, aman)
 | 
				
			||||||
        self.receiverThread.start()
 | 
					        self.Thread.setDaemon(True)
 | 
				
			||||||
 | 
					        self.Thread.start()
 | 
				
			||||||
        print('Listening to tcp://' + config.Address + ':' + str(config.PortReceiver))
 | 
					        print('Listening to tcp://' + config.Address + ':' + str(config.PortReceiver))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # initialize the notification
 | 
					    def sendSequence(self, airport : str, inbounds, weather):
 | 
				
			||||||
        self.notificationSocket = zmq.Socket(self.context, zmq.PUB)
 | 
					        if None == self.Socket:
 | 
				
			||||||
        self.notificationSocket.setsockopt(zmq.CURVE_PUBLICKEY, keyPair[0])
 | 
					            return
 | 
				
			||||||
        self.notificationSocket.setsockopt(zmq.CURVE_SECRETKEY, keyPair[1])
 | 
					 | 
				
			||||||
        self.notificationSocket.setsockopt(zmq.CURVE_SERVER, True)
 | 
					 | 
				
			||||||
        self.notificationSocket.bind('tcp://' + config.Address + ':' + str(config.PortNotification))
 | 
					 | 
				
			||||||
        print('Publishing to tcp://' + config.Address + ':' + str(config.PortNotification))
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def release(self):
 | 
					        sequence = Communication_pb2.AircraftSequence()
 | 
				
			||||||
        if None != self.receiverThread:
 | 
					        sequence.airport = airport
 | 
				
			||||||
            self.receiverThread.stopThread()
 | 
					 | 
				
			||||||
            self.receiverThread.join()
 | 
					 | 
				
			||||||
        self.receiverThread = None
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if None != self.receiverSocket:
 | 
					        # convert the wind data
 | 
				
			||||||
            self.receiverSocket.close()
 | 
					        if None != weather.Altitudes:
 | 
				
			||||||
        self.receiverSocket = None
 | 
					            for i in range(0, len(weather.Altitudes)):
 | 
				
			||||||
 | 
					                entry = sequence.windData.add()
 | 
				
			||||||
 | 
					                entry.altitude = int(weather.Altitudes[i])
 | 
				
			||||||
 | 
					                entry.direction = int(weather.Directions[i])
 | 
				
			||||||
 | 
					                entry.speed = int(weather.Windspeeds[i])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if None != self.notificationSocket:
 | 
					        # convert the inbound sequence
 | 
				
			||||||
            self.notificationSocket.close()
 | 
					        for inbound in inbounds:
 | 
				
			||||||
        self.notificationSocket = None
 | 
					            entry = sequence.sequence.add()
 | 
				
			||||||
 | 
					            entry.callsign = inbound.Callsign
 | 
				
			||||||
 | 
					            entry.fixed = inbound.FixedSequence
 | 
				
			||||||
 | 
					            if None != inbound.PlannedStar:
 | 
				
			||||||
 | 
					                entry.arrivalRoute = inbound.PlannedStar.Name
 | 
				
			||||||
 | 
					            if None != inbound.PlannedRunway:
 | 
				
			||||||
 | 
					                entry.arrivalRunway = inbound.PlannedRunway.Name
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if None != inbound.PerformanceData:
 | 
				
			||||||
 | 
					                entry.performance.iasAboveFL240 = int(round(inbound.PerformanceData.SpeedAboveFL240))
 | 
				
			||||||
 | 
					                entry.performance.iasAboveFL100 = int(round(inbound.PerformanceData.SpeedAboveFL100))
 | 
				
			||||||
 | 
					                entry.performance.iasBelowFL100 = int(round(inbound.PerformanceData.SpeedBelowFL100))
 | 
				
			||||||
 | 
					                entry.performance.iasApproach = int(round(inbound.PerformanceData.SpeedApproach))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if None != inbound.PlannedArrivalRoute:
 | 
				
			||||||
 | 
					                for waypoint in inbound.PlannedArrivalRoute:
 | 
				
			||||||
 | 
					                    wp = entry.waypoints.add()
 | 
				
			||||||
 | 
					                    wp.name = waypoint.Waypoint.Name
 | 
				
			||||||
 | 
					                    wp.altitude = int(round(waypoint.Altitude))
 | 
				
			||||||
 | 
					                    wp.indicatedAirspeed = int(round(waypoint.IndicatedAirspeed))
 | 
				
			||||||
 | 
					                    wp.groundSpeed = int(round(waypoint.GroundSpeed))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    pta = str(waypoint.PTA)
 | 
				
			||||||
 | 
					                    delimiter = pta.find('.')
 | 
				
			||||||
 | 
					                    if -1 == delimiter:
 | 
				
			||||||
 | 
					                        delimiter = pta.find('+')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    wp.pta = pta[0:delimiter]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        message = sequence.SerializeToString()
 | 
				
			||||||
 | 
					        self.Socket.send(message)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -11,46 +11,37 @@ from aman.com.DwdCrawler import DwdCrawler
 | 
				
			|||||||
import aman.config.Weather
 | 
					import aman.config.Weather
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class Weather(Thread):
 | 
					class Weather(Thread):
 | 
				
			||||||
    def __init__(self):
 | 
					    def __init__(self, config : aman.config.Weather.Weather):
 | 
				
			||||||
        Thread.__init__(self)
 | 
					        Thread.__init__(self)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        self.nextUpdate = None
 | 
					        self.NextUpdate = dt.utcfromtimestamp(int(time.time()))
 | 
				
			||||||
        self.lastUpdateTried = None
 | 
					        self.LastUpdateTried = None
 | 
				
			||||||
        self.stopThread = False
 | 
					        self.StopThread = False
 | 
				
			||||||
        self.provider = None
 | 
					        self.Provider = None
 | 
				
			||||||
 | 
					 | 
				
			||||||
    def acquire(self, config : aman.config.Weather.Weather):
 | 
					 | 
				
			||||||
        self.nextUpdate = dt.utcfromtimestamp(int(time.time()))
 | 
					 | 
				
			||||||
        self.lastUpdateTried = None
 | 
					 | 
				
			||||||
        self.stopThread = False
 | 
					 | 
				
			||||||
        self.provider = None
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if 'DWD' == config.Provider.upper():
 | 
					        if 'DWD' == config.Provider.upper():
 | 
				
			||||||
            self.provider = DwdCrawler()
 | 
					            self.Provider = DwdCrawler()
 | 
				
			||||||
        else:
 | 
					        elif 'NONE' != config.Provider.upper():
 | 
				
			||||||
            sys.stderr.write('Invalid or unknown weather-provider defined')
 | 
					            sys.stderr.write('Invalid or unknown weather-provider defined')
 | 
				
			||||||
            sys.exit(-1)
 | 
					            sys.exit(-1)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.setDaemon(True)
 | 
				
			||||||
        self.start()
 | 
					        self.start()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def release(self):
 | 
					 | 
				
			||||||
        self.stopThread = True
 | 
					 | 
				
			||||||
        self.join()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def currentClock():
 | 
					    def currentClock():
 | 
				
			||||||
        clock = dt.utcfromtimestamp(int(time.time())).replace(tzinfo = pytz.UTC)
 | 
					        clock = dt.utcfromtimestamp(int(time.time())).replace(tzinfo = pytz.UTC)
 | 
				
			||||||
        return clock
 | 
					        return clock
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def run(self):
 | 
					    def run(self):
 | 
				
			||||||
        while False == self.stopThread and None != self.provider:
 | 
					        while False == self.StopThread and None != self.Provider:
 | 
				
			||||||
            now = Weather.currentClock()
 | 
					            now = Weather.currentClock()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            # check if an update is required
 | 
					            # check if an update is required
 | 
				
			||||||
            if None != self.provider.updateTime and self.provider.updateTime > now:
 | 
					            if None != self.Provider.UpdateTime and self.Provider.UpdateTime > now:
 | 
				
			||||||
                time.sleep(1)
 | 
					                time.sleep(1)
 | 
				
			||||||
                continue
 | 
					                continue
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            if None == self.lastUpdateTried or self.lastUpdateTried <= now:
 | 
					            if None == self.LastUpdateTried or self.LastUpdateTried <= now:
 | 
				
			||||||
                if True == self.provider.receiveWindData():
 | 
					                if True == self.Provider.receiveWindData():
 | 
				
			||||||
                    self.nextUpdate = self.provider.updateTime
 | 
					                    self.NextUpdate = self.Provider.UpdateTime
 | 
				
			||||||
                    print('Received new wind data')
 | 
					                    print('Received new wind data')
 | 
				
			||||||
@@ -8,7 +8,7 @@ class AircraftPerformance:
 | 
				
			|||||||
    def __init__(self, filepath : str):
 | 
					    def __init__(self, filepath : str):
 | 
				
			||||||
        config = configparser.ConfigParser()
 | 
					        config = configparser.ConfigParser()
 | 
				
			||||||
        config.read(filepath)
 | 
					        config.read(filepath)
 | 
				
			||||||
        self.aircrafts = { }
 | 
					        self.Aircrafts = { }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # iterate over all entries
 | 
					        # iterate over all entries
 | 
				
			||||||
        for key in config:
 | 
					        for key in config:
 | 
				
			||||||
@@ -17,12 +17,12 @@ class AircraftPerformance:
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
            aircraft = PerformanceData(key)
 | 
					            aircraft = PerformanceData(key)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            aircraft.speedAboveFL240 = config[key]['speedabovefl240']
 | 
					            aircraft.SpeedAboveFL240 = float(config[key]['speedabovefl240'])
 | 
				
			||||||
            aircraft.rodAboveFL240 = config[key]['rodabovefl240']
 | 
					            aircraft.RodAboveFL240 = float(config[key]['rodabovefl240'])
 | 
				
			||||||
            aircraft.speedAboveFL100 = config[key]['speedabovefl100']
 | 
					            aircraft.SpeedAboveFL100 = float(config[key]['speedabovefl100'])
 | 
				
			||||||
            aircraft.rodAboveFL100 = config[key]['rodabovefl100']
 | 
					            aircraft.RodAboveFL100 = float(config[key]['rodabovefl100'])
 | 
				
			||||||
            aircraft.speedBelowFL100 = config[key]['speedbelowfl100']
 | 
					            aircraft.SpeedBelowFL100 = float(config[key]['speedbelowfl100'])
 | 
				
			||||||
            aircraft.rodBelowFL100 = config[key]['rodbelowfl100']
 | 
					            aircraft.RodBelowFL100 = float(config[key]['rodbelowfl100'])
 | 
				
			||||||
            aircraft.speedApproach = config[key]['speedapproach']
 | 
					            aircraft.SpeedApproach = float(config[key]['speedapproach'])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            self.aircrafts[aircraft.icao] = aircraft
 | 
					            self.Aircrafts[aircraft.Icao] = aircraft
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,11 +1,16 @@
 | 
				
			|||||||
#!/usr/bin/env python
 | 
					#!/usr/bin/env python
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import configparser
 | 
					import configparser
 | 
				
			||||||
 | 
					from datetime import timedelta
 | 
				
			||||||
import glob
 | 
					import glob
 | 
				
			||||||
import os
 | 
					import os
 | 
				
			||||||
import sys
 | 
					import sys
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from formats.SctEseFormat import SctEseFormat
 | 
					from aman.config.RHC import RHC
 | 
				
			||||||
 | 
					from aman.config.AirportSequencing import AirportSequencing
 | 
				
			||||||
 | 
					from aman.config.RunwaySequencing import RunwaySequencing, RunwayAssignmentType
 | 
				
			||||||
 | 
					from aman.formats.SctEseFormat import SctEseFormat
 | 
				
			||||||
 | 
					from aman.types.Waypoint import Waypoint
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class Airport:
 | 
					class Airport:
 | 
				
			||||||
    def findGngData(data, path):
 | 
					    def findGngData(data, path):
 | 
				
			||||||
@@ -30,14 +35,282 @@ class Airport:
 | 
				
			|||||||
            return []
 | 
					            return []
 | 
				
			||||||
        return planning['routes'].split(':')
 | 
					        return planning['routes'].split(':')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def __init__(self, filepath : str, icao : str):
 | 
					    def parseDefaultSequencingConfiguration(self, icao : str, planning):
 | 
				
			||||||
        self.arrivalRoutes = {}
 | 
					        if None == planning.get('activearrivalrunwaydefault'):
 | 
				
			||||||
 | 
					            sys.stderr.write('No "activearrivalrunwaydefault" entry found!')
 | 
				
			||||||
 | 
					            sys.exit(-1)
 | 
				
			||||||
 | 
					        if None == planning.get('activearrivalmodedefault'):
 | 
				
			||||||
 | 
					            sys.stderr.write('No "activearrivalmodedefault" entry found!')
 | 
				
			||||||
 | 
					            sys.exit(-1)
 | 
				
			||||||
 | 
					        if None == planning.get('arrivalspacingdefault'):
 | 
				
			||||||
 | 
					            sys.stderr.write('No "arrivalspacingdefault" entry found!')
 | 
				
			||||||
 | 
					            sys.exit(-1)
 | 
				
			||||||
 | 
					        if not icao in self.GngData.Runways:
 | 
				
			||||||
 | 
					            sys.stderr.write('Unable to find' + icao + 'in the SCT data!')
 | 
				
			||||||
 | 
					            sys.exit(-1)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # parse the default arrival mode
 | 
				
			||||||
 | 
					        if 'STAGGERED' == planning['activearrivalmodedefault']:
 | 
				
			||||||
 | 
					            staggered = True
 | 
				
			||||||
 | 
					        elif 'IPA' == planning['activearrivalmodedefault']:
 | 
				
			||||||
 | 
					            staggered = False
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            sys.stderr.write('Unknown arrival mode in "" found! (STAGGERED or IPA needs to be set)')
 | 
				
			||||||
 | 
					            sys.exit(-1)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # translate the spacing into a map
 | 
				
			||||||
 | 
					        ident = ''
 | 
				
			||||||
 | 
					        spacings = {}
 | 
				
			||||||
 | 
					        spacingConfig = list(filter(None, planning['arrivalspacingdefault'].split(':')))
 | 
				
			||||||
 | 
					        for i in range(0, len(spacingConfig)):
 | 
				
			||||||
 | 
					            if 0 == i % 2:
 | 
				
			||||||
 | 
					                ident = spacingConfig[i]
 | 
				
			||||||
 | 
					            elif '' != ident:
 | 
				
			||||||
 | 
					                spacings[ident] = int(spacingConfig[i])
 | 
				
			||||||
 | 
					            else:
 | 
				
			||||||
 | 
					                sys.stderr.write('No runway defined in "arrivalspacingdefault"!')
 | 
				
			||||||
 | 
					                sys.exit(-1)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # create the sequencing data per runway
 | 
				
			||||||
 | 
					        self.DefaultSequencing = AirportSequencing(icao)
 | 
				
			||||||
 | 
					        for ident in list(filter(None, planning['activearrivalrunwaydefault'].split(':'))):
 | 
				
			||||||
 | 
					            if not ident in spacings:
 | 
				
			||||||
 | 
					                sys.stderr.write('Unable to find sequencing data for ' + ident + ' of ' + icao)
 | 
				
			||||||
 | 
					                sys.exit(-1)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            found = False
 | 
				
			||||||
 | 
					            for runway in self.GngData.Runways[icao]:
 | 
				
			||||||
 | 
					                if ident == runway.Name:
 | 
				
			||||||
 | 
					                    sequence = RunwaySequencing(runway)
 | 
				
			||||||
 | 
					                    sequence.Spacing = spacings[ident]
 | 
				
			||||||
 | 
					                    self.DefaultSequencing.activateRunway(sequence)
 | 
				
			||||||
 | 
					                    found = True
 | 
				
			||||||
 | 
					                    break
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if False == found:
 | 
				
			||||||
 | 
					                sys.stderr.write('Unable to find the runway for ' + ident + ' of ' + icao + ' in SCT data!')
 | 
				
			||||||
 | 
					                sys.exit(-1)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # create the dependencies, if needed
 | 
				
			||||||
 | 
					        if True == staggered:
 | 
				
			||||||
 | 
					            if None == planning.get('runwaydependenciesdefault'):
 | 
				
			||||||
 | 
					                sys.stderr.write('Unable to find the runway dependencies for staggered approaches of ' + icao + '!')
 | 
				
			||||||
 | 
					                sys.exit(-1)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            dependencies = list(filter(None, planning['runwaydependenciesdefault'].split(':')))
 | 
				
			||||||
 | 
					            if 0 != len(dependencies) % 2:
 | 
				
			||||||
 | 
					                sys.stderr.write('No valid set of runway dependencies found!')
 | 
				
			||||||
 | 
					                sys.exit(-1)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            for i in range(0, len(dependencies), 2):
 | 
				
			||||||
 | 
					                self.DefaultSequencing.addDependency(dependencies[i], dependencies[i + 1])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def parseConstraints(self, planning):
 | 
				
			||||||
 | 
					        self.ArrivalRouteConstraints = {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # check if the IAF sequence constraint is defined
 | 
				
			||||||
 | 
					        if 'iafsequence' in planning:
 | 
				
			||||||
 | 
					            self.IafSpacing = float(planning['iafsequence'])
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            self.IafSpacing = 10.0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # parse the arrival constraints
 | 
				
			||||||
 | 
					        for key in planning:
 | 
				
			||||||
 | 
					            if True == key.startswith('constraints'):
 | 
				
			||||||
 | 
					                star = key.replace('constraints', '').upper()
 | 
				
			||||||
 | 
					                if '' != star:
 | 
				
			||||||
 | 
					                    elements = list(filter(None, planning[key].split(':')))
 | 
				
			||||||
 | 
					                    if 3 > len(elements):
 | 
				
			||||||
 | 
					                        sys.stderr.write('Invalid constraint line: ' + key + '=' + planning[key])
 | 
				
			||||||
 | 
					                        sys.exit(-1)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    waypoints = []
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    # values for the waypoint constraints
 | 
				
			||||||
 | 
					                    waypointName = elements[0]
 | 
				
			||||||
 | 
					                    constraints = [-1, -1]
 | 
				
			||||||
 | 
					                    isBaseTurn = False
 | 
				
			||||||
 | 
					                    isFinalTurn = False
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    index = 1
 | 
				
			||||||
 | 
					                    while index < len(elements):
 | 
				
			||||||
 | 
					                        if 'A' == elements[index] or 'S' == elements[index]:
 | 
				
			||||||
 | 
					                            if index + 1 == len(elements) or False == elements[index + 1].isnumeric():
 | 
				
			||||||
 | 
					                                sys.stderr.write('Invalid constraint line: ' + key + '=' + planning[key])
 | 
				
			||||||
 | 
					                                sys.exit(-1)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                            if 'A' == elements[index]:
 | 
				
			||||||
 | 
					                                constraints[0] = int(elements[index + 1])
 | 
				
			||||||
 | 
					                            else:
 | 
				
			||||||
 | 
					                                constraints[1] = int(elements[index + 1])
 | 
				
			||||||
 | 
					                            index += 1
 | 
				
			||||||
 | 
					                        elif 'B' == elements[index]:
 | 
				
			||||||
 | 
					                            isBaseTurn = True
 | 
				
			||||||
 | 
					                        elif 'F' == elements[index]:
 | 
				
			||||||
 | 
					                            isFinalTurn = True
 | 
				
			||||||
 | 
					                        else:
 | 
				
			||||||
 | 
					                            if False == isBaseTurn and False == isFinalTurn and -1 == constraints[0] and -1 == constraints[1] and '' == waypointName:
 | 
				
			||||||
 | 
					                                sys.stderr.write('Invalid constraint line: ' + key + '=' + planning[key])
 | 
				
			||||||
 | 
					                                sys.exit(-1)
 | 
				
			||||||
 | 
					                            if True == isBaseTurn and True == isFinalTurn:
 | 
				
			||||||
 | 
					                                sys.stderr.write('Invalid constraint line: ' + key + '=' + planning[key])
 | 
				
			||||||
 | 
					                                sys.exit(-1)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                            waypoints.append(Waypoint(name = waypointName, base = isBaseTurn, final = isFinalTurn))
 | 
				
			||||||
 | 
					                            if -1 != constraints[0]:
 | 
				
			||||||
 | 
					                                waypoints[-1].Altitude = constraints[0]
 | 
				
			||||||
 | 
					                            if -1 != constraints[1]:
 | 
				
			||||||
 | 
					                                waypoints[-1].Speed = constraints[1]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                            # reset temporary data
 | 
				
			||||||
 | 
					                            waypointName = elements[index]
 | 
				
			||||||
 | 
					                            constraints = [-1, -1]
 | 
				
			||||||
 | 
					                            isBaseTurn = False
 | 
				
			||||||
 | 
					                            isFinalTurn = False
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                        index += 1
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    # check if we have to add the last waypoint
 | 
				
			||||||
 | 
					                    if 0 != len(waypoints) and waypointName != waypoints[-1].Name:
 | 
				
			||||||
 | 
					                        waypoints.append(Waypoint(name = waypointName, base = isBaseTurn, final = isFinalTurn))
 | 
				
			||||||
 | 
					                        if -1 != constraints[0]:
 | 
				
			||||||
 | 
					                            waypoints[-1].Altitude = constraints[0]
 | 
				
			||||||
 | 
					                        if -1 != constraints[1]:
 | 
				
			||||||
 | 
					                            waypoints[-1].Speed = constraints[1]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    # register the arrival route
 | 
				
			||||||
 | 
					                    self.ArrivalRouteConstraints[star] = waypoints
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def parseAssignment(assignment : str):
 | 
				
			||||||
 | 
					        elements = list(filter(None, assignment.split(':')))
 | 
				
			||||||
 | 
					        retval = {}
 | 
				
			||||||
 | 
					        type = None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        index = 0
 | 
				
			||||||
 | 
					        while index < len(elements):
 | 
				
			||||||
 | 
					            if 0 == index % 2:
 | 
				
			||||||
 | 
					                if 'A' == elements[index]:
 | 
				
			||||||
 | 
					                    type = RunwayAssignmentType.AircraftType
 | 
				
			||||||
 | 
					                elif 'G' == elements[index]:
 | 
				
			||||||
 | 
					                    type = RunwayAssignmentType.GateAssignment
 | 
				
			||||||
 | 
					                else:
 | 
				
			||||||
 | 
					                    sys.stderr.write('Invalid assignment type: ' + elements[index])
 | 
				
			||||||
 | 
					                    sys.exit(-1)
 | 
				
			||||||
 | 
					            else:
 | 
				
			||||||
 | 
					                if None == type:
 | 
				
			||||||
 | 
					                    sys.stderr.write('No assignment type defined')
 | 
				
			||||||
 | 
					                    sys.exit(-1)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                if type not in retval:
 | 
				
			||||||
 | 
					                    retval.setdefault(type, [])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                retval[type].append(elements[index])
 | 
				
			||||||
 | 
					                type = None
 | 
				
			||||||
 | 
					            index += 1
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return retval
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def findRunway(self, icao : str, name : str):
 | 
				
			||||||
 | 
					        for runway in self.GngData.Runways[icao]:
 | 
				
			||||||
 | 
					            if name == runway.Name:
 | 
				
			||||||
 | 
					                return runway
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        sys.stderr.write('Unable to find runway ' + name + ' in the sequencing data for ' + icao)
 | 
				
			||||||
 | 
					        raise Exception()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def updateRunwayAssignment(dictionary, runway, assignments):
 | 
				
			||||||
 | 
					        if runway not in dictionary:
 | 
				
			||||||
 | 
					            dictionary.setdefault(runway, {})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        for key in assignments:
 | 
				
			||||||
 | 
					            if key not in dictionary[runway]:
 | 
				
			||||||
 | 
					                dictionary[runway].setdefault(key, assignments[key])
 | 
				
			||||||
 | 
					            else:
 | 
				
			||||||
 | 
					                dictionary[runway][key].extend(assignments[key])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def parseOptimization(self, key : str, line : str):
 | 
				
			||||||
 | 
					        star = key.replace('optimization', '').upper()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # check if the STAR exists
 | 
				
			||||||
 | 
					        found = False
 | 
				
			||||||
 | 
					        for rwy in self.GngData.ArrivalRoutes:
 | 
				
			||||||
 | 
					            for route in self.GngData.ArrivalRoutes[rwy]:
 | 
				
			||||||
 | 
					                if star == route.Name:
 | 
				
			||||||
 | 
					                    found = True
 | 
				
			||||||
 | 
					                    break
 | 
				
			||||||
 | 
					            if True == found:
 | 
				
			||||||
 | 
					                break
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if False == found:
 | 
				
			||||||
 | 
					            sys.stderr.write('Unknown star:' + key)
 | 
				
			||||||
 | 
					            raise Exception()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        elements = line.split(':')
 | 
				
			||||||
 | 
					        if 2 != len(elements):
 | 
				
			||||||
 | 
					            sys.stderr.write('Invalid optimization parameter for ' + key)
 | 
				
			||||||
 | 
					            raise Exception()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        maxTTG = int(elements[0])
 | 
				
			||||||
 | 
					        ttgRatio = float(elements[1])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return star, maxTTG, ttgRatio
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def updateOptimizationParameters(dictionary, star, maxTTG, ttgRatio):
 | 
				
			||||||
 | 
					        if star not in dictionary:
 | 
				
			||||||
 | 
					            dictionary.setdefault(star, [])
 | 
				
			||||||
 | 
					        dictionary[star] = [ maxTTG, ttgRatio ]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def parseRunwayAssignment(self, icao : str, planning):
 | 
				
			||||||
 | 
					        self.OptimizationParameters = {}
 | 
				
			||||||
 | 
					        self.RunwayAssignmentsShall = {}
 | 
				
			||||||
 | 
					        self.RunwayAssignmentsShould = {}
 | 
				
			||||||
 | 
					        self.RunwayAssignmentsMay = {}
 | 
				
			||||||
 | 
					        self.MaxDelayMay = timedelta(minutes=10)
 | 
				
			||||||
 | 
					        mayFound = False
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        for key in planning:
 | 
				
			||||||
 | 
					            if True == key.startswith('shallassign'):
 | 
				
			||||||
 | 
					                runway = self.findRunway(icao, key.replace('shallassign', '').upper())
 | 
				
			||||||
 | 
					                assignments = Airport.parseAssignment(planning[key])
 | 
				
			||||||
 | 
					                Airport.updateRunwayAssignment(self.RunwayAssignmentsShall, runway, assignments)
 | 
				
			||||||
 | 
					            elif True == key.startswith('shouldassign'):
 | 
				
			||||||
 | 
					                runway = self.findRunway(icao, key.replace('shouldassign', '').upper())
 | 
				
			||||||
 | 
					                assignments = Airport.parseAssignment(planning[key])
 | 
				
			||||||
 | 
					                Airport.updateRunwayAssignment(self.RunwayAssignmentsShould, runway, assignments)
 | 
				
			||||||
 | 
					            elif True == key.startswith('mayassign'):
 | 
				
			||||||
 | 
					                runway = self.findRunway(icao, key.replace('mayassign', '').upper())
 | 
				
			||||||
 | 
					                assignments = Airport.parseAssignment(planning[key])
 | 
				
			||||||
 | 
					                Airport.updateRunwayAssignment(self.RunwayAssignmentsMay, runway, assignments)
 | 
				
			||||||
 | 
					                mayFound = True
 | 
				
			||||||
 | 
					            elif True == key.startswith('optimization'):
 | 
				
			||||||
 | 
					                star, maxTTG, ttgRatio = self.parseOptimization(key, planning[key])
 | 
				
			||||||
 | 
					                Airport.updateOptimizationParameters(self.OptimizationParameters, star, maxTTG, ttgRatio)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # find the max delays
 | 
				
			||||||
 | 
					        if True == mayFound:
 | 
				
			||||||
 | 
					            if 'maxdelaymay' not in planning:
 | 
				
			||||||
 | 
					                sys.stderr.write('maxDelaymay needs to be defined')
 | 
				
			||||||
 | 
					                sys.exit(-1)
 | 
				
			||||||
 | 
					            self.MaxDelayMay = timedelta(minutes=int(planning['maxdelaymay']))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def parseWebUI(self, webui):
 | 
				
			||||||
 | 
					        self.IafColorization = {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        for key in webui:
 | 
				
			||||||
 | 
					            if 'iafcolorization' == key:
 | 
				
			||||||
 | 
					                elements = list(filter(None, webui[key].split(':')))
 | 
				
			||||||
 | 
					                for i in range(0, len(elements), 4):
 | 
				
			||||||
 | 
					                    self.IafColorization[elements[i]] = [ int(elements[i + 1]), int(elements[i + 2]), int(elements[i + 3]) ]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def __init__(self, filepath : str, icao : str):
 | 
				
			||||||
        config = configparser.ConfigParser()
 | 
					        config = configparser.ConfigParser()
 | 
				
			||||||
        config.read(filepath)
 | 
					        config.read(filepath)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        dataConfig = None
 | 
					        dataConfig = None
 | 
				
			||||||
        planningConfig = None
 | 
					        planningConfig = None
 | 
				
			||||||
 | 
					        rhcConfig = None
 | 
				
			||||||
 | 
					        webUiConfig = None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # search the required sections
 | 
					        # search the required sections
 | 
				
			||||||
        for key in config:
 | 
					        for key in config:
 | 
				
			||||||
@@ -45,6 +318,10 @@ class Airport:
 | 
				
			|||||||
                dataConfig = config['DATA']
 | 
					                dataConfig = config['DATA']
 | 
				
			||||||
            elif 'PLANNING' == key:
 | 
					            elif 'PLANNING' == key:
 | 
				
			||||||
                planningConfig = config['PLANNING']
 | 
					                planningConfig = config['PLANNING']
 | 
				
			||||||
 | 
					            elif 'RHC' == key:
 | 
				
			||||||
 | 
					                rhcConfig = config['RHC']
 | 
				
			||||||
 | 
					            elif 'WEBUI' == key:
 | 
				
			||||||
 | 
					                webUiConfig = config['WEBUI']
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # find the GNG-file data
 | 
					        # find the GNG-file data
 | 
				
			||||||
        sctFile, eseFile = Airport.findGngData(dataConfig, os.path.dirname(filepath))
 | 
					        sctFile, eseFile = Airport.findGngData(dataConfig, os.path.dirname(filepath))
 | 
				
			||||||
@@ -61,6 +338,40 @@ class Airport:
 | 
				
			|||||||
            sys.stderr.write('No valid planning configuration found')
 | 
					            sys.stderr.write('No valid planning configuration found')
 | 
				
			||||||
            sys.exit(-1)
 | 
					            sys.exit(-1)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # parse the RHC information
 | 
				
			||||||
 | 
					        if None == rhcConfig:
 | 
				
			||||||
 | 
					            sys.stderr.write('No RHC configuration found')
 | 
				
			||||||
 | 
					            sys.exit(-1)
 | 
				
			||||||
 | 
					        self.RecedingHorizonControl = RHC(rhcConfig)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # check if thw WebUI information is available
 | 
				
			||||||
 | 
					        if None == webUiConfig:
 | 
				
			||||||
 | 
					            sys.stderr.write('No WEBUI configuration found')
 | 
				
			||||||
 | 
					            sys.exit(-1)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # parse the GNG data
 | 
					        # parse the GNG data
 | 
				
			||||||
        print('Used GNG-Data: ' + eseFile)
 | 
					        print('Used GNG-Data: ' + eseFile)
 | 
				
			||||||
        self.gngData = SctEseFormat(sctFile, eseFile, icao, requiredArrivalRoutes)
 | 
					        self.GngData = SctEseFormat(sctFile, eseFile, icao, requiredArrivalRoutes)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # get the GAFOR id
 | 
				
			||||||
 | 
					        if None == dataConfig.get('gaforid'):
 | 
				
			||||||
 | 
					            sys.stderr.write('No GAFOR-ID found!')
 | 
				
			||||||
 | 
					            sys.exit(-1)
 | 
				
			||||||
 | 
					        self.GaforId = int(dataConfig['gaforid'])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # get the default sequencing data
 | 
				
			||||||
 | 
					        self.parseDefaultSequencingConfiguration(icao, planningConfig)
 | 
				
			||||||
 | 
					        self.parseConstraints(planningConfig)
 | 
				
			||||||
 | 
					        self.parseRunwayAssignment(icao, planningConfig)
 | 
				
			||||||
 | 
					        self.parseWebUI(webUiConfig)
 | 
				
			||||||
 | 
					        self.assignmentUpdate(self.DefaultSequencing)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def assignmentUpdate(self, sequenceConfig : AirportSequencing):
 | 
				
			||||||
 | 
					        # initializes the default sequence data
 | 
				
			||||||
 | 
					        for active in sequenceConfig.ActiveArrivalRunways:
 | 
				
			||||||
 | 
					            if active.Runway in self.RunwayAssignmentsShall:
 | 
				
			||||||
 | 
					                active.ShallAssignments = self.RunwayAssignmentsShall[active.Runway]
 | 
				
			||||||
 | 
					            if active.Runway in self.RunwayAssignmentsShould:
 | 
				
			||||||
 | 
					                active.ShouldAssignments = self.RunwayAssignmentsShould[active.Runway]
 | 
				
			||||||
 | 
					            if active.Runway in self.RunwayAssignmentsMay:
 | 
				
			||||||
 | 
					                active.MayAssignments = self.RunwayAssignmentsMay[active.Runway]
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										101
									
								
								aman/config/AirportSequencing.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										101
									
								
								aman/config/AirportSequencing.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,101 @@
 | 
				
			|||||||
 | 
					#!/usr/bin/env python
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from datetime import datetime as dt
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import pytz
 | 
				
			||||||
 | 
					import time
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from aman.config.RunwaySequencing import RunwaySequencing
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class AirportSequencing:
 | 
				
			||||||
 | 
					    def __init__(self, icao : str):
 | 
				
			||||||
 | 
					        self.Airport = icao
 | 
				
			||||||
 | 
					        self.ActiveArrivalRunways = []
 | 
				
			||||||
 | 
					        self.RunwayDependencies = []
 | 
				
			||||||
 | 
					        self.LastUpdateTimestamp = dt.utcfromtimestamp(int(time.time())).replace(tzinfo = pytz.UTC)
 | 
				
			||||||
 | 
					        self.UseShallShouldMay = True
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def clearData(self):
 | 
				
			||||||
 | 
					        self.ActiveArrivalRunways.clear()
 | 
				
			||||||
 | 
					        self.RunwayDependencies.clear()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def activateRunway(self, runway : RunwaySequencing):
 | 
				
			||||||
 | 
					        for active in self.ActiveArrivalRunways:
 | 
				
			||||||
 | 
					            if active.Runway.Name == runway.Runway.Name:
 | 
				
			||||||
 | 
					                self.ActiveArrivalRunways[runway.Runway.Name] = runway
 | 
				
			||||||
 | 
					                return
 | 
				
			||||||
 | 
					        self.ActiveArrivalRunways.append(runway)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def runway(self, index : int):
 | 
				
			||||||
 | 
					        if index >= len(self.ActiveArrivalRunways):
 | 
				
			||||||
 | 
					            return None
 | 
				
			||||||
 | 
					        return self.ActiveArrivalRunways[index].Runway
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def runwayIndex(self, identifier : str):
 | 
				
			||||||
 | 
					        for i in range(0, len(self.ActiveArrivalRunways)):
 | 
				
			||||||
 | 
					            if self.ActiveArrivalRunways[i].Runway.Name == identifier:
 | 
				
			||||||
 | 
					                return i
 | 
				
			||||||
 | 
					        return -1
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def deactivateRunway(self, identifier : str):
 | 
				
			||||||
 | 
					        index = self.runwayIndex(identifier)
 | 
				
			||||||
 | 
					        if 0 <= index:
 | 
				
			||||||
 | 
					            self.ActiveArrivalRunways.pop(index)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            # remove the dependencies
 | 
				
			||||||
 | 
					            for i in range(self.RunwayDependencies - 1, -1, -1):
 | 
				
			||||||
 | 
					                if index == self.RunwayDependencies[i][0] or index == self.RunwayDependencies[i][1]:
 | 
				
			||||||
 | 
					                    self.RunwayDependencies.pop(i)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def addDependency(self, first : str, second : str):
 | 
				
			||||||
 | 
					        idxFirst = self.runwayIndex(first)
 | 
				
			||||||
 | 
					        idxSecond = self.runwayIndex(second)
 | 
				
			||||||
 | 
					        if 0 > idxFirst or 0 > idxSecond:
 | 
				
			||||||
 | 
					            return False
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        foundFirst = False
 | 
				
			||||||
 | 
					        foundSecond = False
 | 
				
			||||||
 | 
					        for dependency in self.RunwayDependencies:
 | 
				
			||||||
 | 
					            if idxFirst == dependency[0] and idxSecond == dependency[1]:
 | 
				
			||||||
 | 
					                foundFirst = True
 | 
				
			||||||
 | 
					            elif idxFirst == dependency[1] and idxSecond == dependency[0]:
 | 
				
			||||||
 | 
					                foundSecond = True
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if False == foundFirst:
 | 
				
			||||||
 | 
					            self.RunwayDependencies.append([ idxFirst, idxSecond ])
 | 
				
			||||||
 | 
					        if False == foundSecond:
 | 
				
			||||||
 | 
					            self.RunwayDependencies.append([ idxSecond, idxFirst ])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return True
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def removeDependency(self, first : str, second : str):
 | 
				
			||||||
 | 
					        idxFirst = self.runwayIndex(first)
 | 
				
			||||||
 | 
					        idxSecond = self.runwayIndex(second)
 | 
				
			||||||
 | 
					        if 0 > idxFirst or 0 > idxSecond:
 | 
				
			||||||
 | 
					            return
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        for i in range(self.RunwayDependencies - 1, -1, -1):
 | 
				
			||||||
 | 
					            dependency = self.RunwayDependencies[i]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            # check for all the pairs
 | 
				
			||||||
 | 
					            if idxFirst == dependency[0] and idxSecond == dependency[1]:
 | 
				
			||||||
 | 
					                self.RunwayDependencies.pop(i)
 | 
				
			||||||
 | 
					            elif idxSecond == dependency[0] and idxSecond == dependency[0]:
 | 
				
			||||||
 | 
					                self.RunwayDependencies.pop(i)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def findRunway(self, identifier : str):
 | 
				
			||||||
 | 
					        for runway in self.ActiveArrivalRunways:
 | 
				
			||||||
 | 
					            if runway.Runway.Name == identifier:
 | 
				
			||||||
 | 
					                return runway
 | 
				
			||||||
 | 
					        return None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def findDependentRunways(self, identifier : str):
 | 
				
			||||||
 | 
					        # runway is unknown
 | 
				
			||||||
 | 
					        index = self.runwayIndex(identifier)
 | 
				
			||||||
 | 
					        if 0 > index:
 | 
				
			||||||
 | 
					            return []
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # search the dependency pair
 | 
				
			||||||
 | 
					        dependencies = [self.ActiveArrivalRunways[self.RunwayDependencies[i][1]] for i in range(0, len(self.RunwayDependencies)) if index == self.RunwayDependencies[i][0]]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return dependencies
 | 
				
			||||||
							
								
								
									
										41
									
								
								aman/config/RHC.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										41
									
								
								aman/config/RHC.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,41 @@
 | 
				
			|||||||
 | 
					#!/usr/bin/env python
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from datetime import timedelta
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import configparser;
 | 
				
			||||||
 | 
					import sys
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class RHC():
 | 
				
			||||||
 | 
					    def __init__(self, config : configparser.ConfigParser):
 | 
				
			||||||
 | 
					        # latest scheduling fix in minutes
 | 
				
			||||||
 | 
					        self.FixedBeforeArrival = None
 | 
				
			||||||
 | 
					        # number of seconds per window
 | 
				
			||||||
 | 
					        self.WindowSize = None
 | 
				
			||||||
 | 
					        # number of horizon windows for optimization iteration
 | 
				
			||||||
 | 
					        self.WindowOverlap = None
 | 
				
			||||||
 | 
					        # distance until IAF to add an aircraft to the optimization
 | 
				
			||||||
 | 
					        self.MaximumIafDistance = None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # search the required sections
 | 
				
			||||||
 | 
					        for key in config:
 | 
				
			||||||
 | 
					            if 'windowsize' == key:
 | 
				
			||||||
 | 
					                self.WindowSize = int(config['windowsize'])
 | 
				
			||||||
 | 
					            elif 'windowoverlap' == key:
 | 
				
			||||||
 | 
					                self.WindowOverlap = int(config['windowoverlap'])
 | 
				
			||||||
 | 
					            elif 'fixedbeforearrival' == key:
 | 
				
			||||||
 | 
					                self.FixedBeforeArrival = timedelta(minutes = int(config['fixedbeforearrival']))
 | 
				
			||||||
 | 
					            elif 'maximumiafdistance' == key:
 | 
				
			||||||
 | 
					                self.MaximumIafDistance = int(config['maximumiafdistance'])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if self.WindowSize is None:
 | 
				
			||||||
 | 
					            sys.stderr.write('No window size configuration found!')
 | 
				
			||||||
 | 
					            sys.exit(-1)
 | 
				
			||||||
 | 
					        if self.WindowOverlap is None:
 | 
				
			||||||
 | 
					            sys.stderr.write('No window overlap configuration found!')
 | 
				
			||||||
 | 
					            sys.exit(-1)
 | 
				
			||||||
 | 
					        if self.FixedBeforeArrival is None:
 | 
				
			||||||
 | 
					            sys.stderr.write('No fixed before IAF configuration found!')
 | 
				
			||||||
 | 
					            sys.exit(-1)
 | 
				
			||||||
 | 
					        if self.MaximumIafDistance is None:
 | 
				
			||||||
 | 
					            sys.stderr.write('No maximum IAF distance found!')
 | 
				
			||||||
 | 
					            sys.exit(-1)
 | 
				
			||||||
							
								
								
									
										17
									
								
								aman/config/RunwaySequencing.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								aman/config/RunwaySequencing.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,17 @@
 | 
				
			|||||||
 | 
					#!/usr/bin/env python
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from enum import Enum
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from aman.types.Runway import Runway
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class RunwayAssignmentType(Enum):
 | 
				
			||||||
 | 
					    AircraftType = 1
 | 
				
			||||||
 | 
					    GateAssignment = 2
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class RunwaySequencing:
 | 
				
			||||||
 | 
					    def __init__(self, runway : Runway):
 | 
				
			||||||
 | 
					        self.Runway = runway
 | 
				
			||||||
 | 
					        self.Spacing = 3
 | 
				
			||||||
 | 
					        self.ShallAssignments = {}
 | 
				
			||||||
 | 
					        self.ShouldAssignments = {}
 | 
				
			||||||
 | 
					        self.MayAssignments = {}
 | 
				
			||||||
@@ -8,6 +8,9 @@ class Server():
 | 
				
			|||||||
        self.Address = None
 | 
					        self.Address = None
 | 
				
			||||||
        self.PortReceiver = None
 | 
					        self.PortReceiver = None
 | 
				
			||||||
        self.PortNotification = None
 | 
					        self.PortNotification = None
 | 
				
			||||||
 | 
					        self.WebUiUrl = None
 | 
				
			||||||
 | 
					        self.WebUiSequenceNotification = None
 | 
				
			||||||
 | 
					        self.WebUiConfigurationReceiver = None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # search the required sections
 | 
					        # search the required sections
 | 
				
			||||||
        for key in config:
 | 
					        for key in config:
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -6,8 +6,6 @@ import sys
 | 
				
			|||||||
class Weather():
 | 
					class Weather():
 | 
				
			||||||
    def __init__(self, config : configparser.ConfigParser):
 | 
					    def __init__(self, config : configparser.ConfigParser):
 | 
				
			||||||
        self.Provider = None
 | 
					        self.Provider = None
 | 
				
			||||||
        self.PortReceiver = None
 | 
					 | 
				
			||||||
        self.PortNotification = None
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # search the required sections
 | 
					        # search the required sections
 | 
				
			||||||
        for key in config:
 | 
					        for key in config:
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,8 +1,10 @@
 | 
				
			|||||||
#!/usr/bin/env python
 | 
					#!/usr/bin/env python
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import copy
 | 
				
			||||||
import sys
 | 
					import sys
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from aman.types.ArrivalRoute import ArrivalRoute
 | 
					from aman.types.ArrivalRoute import ArrivalRoute
 | 
				
			||||||
 | 
					from aman.types.Runway import Runway
 | 
				
			||||||
from aman.types.Waypoint import Waypoint
 | 
					from aman.types.Waypoint import Waypoint
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class SctEseFormat:
 | 
					class SctEseFormat:
 | 
				
			||||||
@@ -30,11 +32,22 @@ class SctEseFormat:
 | 
				
			|||||||
        if len(split) <= longitudeIdx:
 | 
					        if len(split) <= longitudeIdx:
 | 
				
			||||||
            sys.stderr.write('Invalid waypoint format: ' + waypoint)
 | 
					            sys.stderr.write('Invalid waypoint format: ' + waypoint)
 | 
				
			||||||
            sys.exit(-1)
 | 
					            sys.exit(-1)
 | 
				
			||||||
        return Waypoint(split[nameIdx], Waypoint.dms2dd(split[latitudeIdx]), Waypoint.dms2dd(split[longitudeIdx]))
 | 
					        return Waypoint(name = split[nameIdx], latitude = split[latitudeIdx], longitude = split[longitudeIdx])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def extractWaypoints(self, sctFilepath : str):
 | 
					    def parseRunway(runway : str):
 | 
				
			||||||
 | 
					        split = list(filter(None, runway.split(' ')))
 | 
				
			||||||
 | 
					        if 9 != len(split) or '' == split[8]:
 | 
				
			||||||
 | 
					            return None, None, None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        waypoint0 = Waypoint(name = split[0], latitude = split[4], longitude = split[5])
 | 
				
			||||||
 | 
					        waypoint1 = Waypoint(name = split[1], latitude = split[6], longitude = split[7])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return split[8], Runway(waypoint0, waypoint1), Runway(waypoint1, waypoint0)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def extractSctInformation(self, sctFilepath : str):
 | 
				
			||||||
        config = SctEseFormat.readFile(sctFilepath)
 | 
					        config = SctEseFormat.readFile(sctFilepath)
 | 
				
			||||||
        foundAirports = False
 | 
					        foundAirports = False
 | 
				
			||||||
 | 
					        foundRunways = False
 | 
				
			||||||
        foundVOR = False
 | 
					        foundVOR = False
 | 
				
			||||||
        foundNDB = False
 | 
					        foundNDB = False
 | 
				
			||||||
        foundFix = False
 | 
					        foundFix = False
 | 
				
			||||||
@@ -48,6 +61,8 @@ class SctEseFormat:
 | 
				
			|||||||
                foundFix = True
 | 
					                foundFix = True
 | 
				
			||||||
            elif 'AIRPORT' == key:
 | 
					            elif 'AIRPORT' == key:
 | 
				
			||||||
                foundAirports = True
 | 
					                foundAirports = True
 | 
				
			||||||
 | 
					            elif 'RUNWAY' == key:
 | 
				
			||||||
 | 
					                foundRunways = True
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if False == foundVOR:
 | 
					        if False == foundVOR:
 | 
				
			||||||
            sys.stderr.write('Unable to find VOR-entries in the sector file')
 | 
					            sys.stderr.write('Unable to find VOR-entries in the sector file')
 | 
				
			||||||
@@ -61,27 +76,39 @@ class SctEseFormat:
 | 
				
			|||||||
        if False == foundAirports:
 | 
					        if False == foundAirports:
 | 
				
			||||||
            sys.stderr.write('Unable to find AIRPORT-entries in the sector file')
 | 
					            sys.stderr.write('Unable to find AIRPORT-entries in the sector file')
 | 
				
			||||||
            sys.exit(-1)
 | 
					            sys.exit(-1)
 | 
				
			||||||
 | 
					        if False == foundRunways:
 | 
				
			||||||
 | 
					            sys.stderr.write('Unable to find RUNWAY-entries in the sector file')
 | 
				
			||||||
 | 
					            sys.exit(-1)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # extract all waypoints
 | 
					        # extract all waypoints
 | 
				
			||||||
        for waypoint in config['VOR']:
 | 
					        for waypoint in config['VOR']:
 | 
				
			||||||
            waypoint = SctEseFormat.parseWaypoint(waypoint, 0, 2, 3)
 | 
					            waypoint = SctEseFormat.parseWaypoint(waypoint, 0, 2, 3)
 | 
				
			||||||
            self.waypoints.setdefault(waypoint.name, []).append(waypoint)
 | 
					            self.Waypoints.setdefault(waypoint.Name, []).append(waypoint)
 | 
				
			||||||
        for waypoint in config['NDB']:
 | 
					        for waypoint in config['NDB']:
 | 
				
			||||||
            waypoint = SctEseFormat.parseWaypoint(waypoint, 0, 1, 2)
 | 
					            waypoint = SctEseFormat.parseWaypoint(waypoint, 0, 1, 2)
 | 
				
			||||||
            self.waypoints.setdefault(waypoint.name, []).append(waypoint)
 | 
					            self.Waypoints.setdefault(waypoint.Name, []).append(waypoint)
 | 
				
			||||||
        for waypoint in config['FIXES']:
 | 
					        for waypoint in config['FIXES']:
 | 
				
			||||||
            waypoint = SctEseFormat.parseWaypoint(waypoint, 0, 1, 2)
 | 
					            waypoint = SctEseFormat.parseWaypoint(waypoint, 0, 1, 2)
 | 
				
			||||||
            self.waypoints.setdefault(waypoint.name, []).append(waypoint)
 | 
					            self.Waypoints.setdefault(waypoint.Name, []).append(waypoint)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # extract the airports
 | 
					        # extract the airports
 | 
				
			||||||
        for airport in config['AIRPORT']:
 | 
					        for airport in config['AIRPORT']:
 | 
				
			||||||
            airport  = SctEseFormat.parseWaypoint(airport,0, 2, 3)
 | 
					            airport  = SctEseFormat.parseWaypoint(airport, 0, 2, 3)
 | 
				
			||||||
            self.airports.setdefault(airport.name, []).append(airport)
 | 
					            self.Airports.setdefault(airport.Name, []).append(airport)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # extract the runways
 | 
				
			||||||
 | 
					        for runway in config['RUNWAY']:
 | 
				
			||||||
 | 
					            airport, runway0, runway1 = SctEseFormat.parseRunway(runway)
 | 
				
			||||||
 | 
					            if None != airport:
 | 
				
			||||||
 | 
					                if not airport in self.Runways:
 | 
				
			||||||
 | 
					                    self.Runways.setdefault(airport, [])
 | 
				
			||||||
 | 
					                self.Runways[airport].append(runway0)
 | 
				
			||||||
 | 
					                self.Runways[airport].append(runway1)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def parseArrivalRoute(self, route : str, airport : Waypoint):
 | 
					    def parseArrivalRoute(self, route : str, airport : Waypoint):
 | 
				
			||||||
        # split the route and validate that it is a STAR for the airport
 | 
					        # split the route and validate that it is a STAR for the airport
 | 
				
			||||||
        split = route.split(':')
 | 
					        split = route.split(':')
 | 
				
			||||||
        if 5 != len(split) or 'STAR' != split[0] or split[1] != airport.name:
 | 
					        if 5 != len(split) or 'STAR' != split[0] or split[1] != airport.Name:
 | 
				
			||||||
            return None
 | 
					            return None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # find all waypoints
 | 
					        # find all waypoints
 | 
				
			||||||
@@ -89,7 +116,7 @@ class SctEseFormat:
 | 
				
			|||||||
        route = list(filter(None, split[4].split(' ')))
 | 
					        route = list(filter(None, split[4].split(' ')))
 | 
				
			||||||
        for waypoint in route:
 | 
					        for waypoint in route:
 | 
				
			||||||
            # find the waypoint in the route
 | 
					            # find the waypoint in the route
 | 
				
			||||||
            coordinates = self.waypoints[waypoint]
 | 
					            coordinates = self.Waypoints[waypoint]
 | 
				
			||||||
            # no waypoint with this name defined
 | 
					            # no waypoint with this name defined
 | 
				
			||||||
            if None == coordinates:
 | 
					            if None == coordinates:
 | 
				
			||||||
                sys.stderr.write('Unable to find waypoint ' + waypoint)
 | 
					                sys.stderr.write('Unable to find waypoint ' + waypoint)
 | 
				
			||||||
@@ -111,10 +138,10 @@ class SctEseFormat:
 | 
				
			|||||||
                    sys.stderr.write('Unable to find a close waypoint for ' + waypoint)
 | 
					                    sys.stderr.write('Unable to find a close waypoint for ' + waypoint)
 | 
				
			||||||
                    sys.exit(-1)
 | 
					                    sys.exit(-1)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                waypoints.append(nearest)
 | 
					                waypoints.append(copy.deepcopy(nearest))
 | 
				
			||||||
            # extend the list of waypoints
 | 
					            # extend the list of waypoints
 | 
				
			||||||
            else:
 | 
					            else:
 | 
				
			||||||
                waypoints.append(coordinates[0])
 | 
					                waypoints.append(copy.deepcopy(coordinates[0]))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # create the arrival route
 | 
					        # create the arrival route
 | 
				
			||||||
        return ArrivalRoute(split[3], split[2], waypoints)
 | 
					        return ArrivalRoute(split[3], split[2], waypoints)
 | 
				
			||||||
@@ -124,10 +151,10 @@ class SctEseFormat:
 | 
				
			|||||||
        foundSidsStars = False
 | 
					        foundSidsStars = False
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # search the airport in the extracted list
 | 
					        # search the airport in the extracted list
 | 
				
			||||||
        if not airport in self.airports:
 | 
					        if not airport in self.Airports:
 | 
				
			||||||
            sys.stderr.write(airport + 'in self.airports', 'Unable to find the requested airport')
 | 
					            sys.stderr.write('Unable to find the requested airport')
 | 
				
			||||||
            sys.exit(-1)
 | 
					            sys.exit(-1)
 | 
				
			||||||
        airport = self.airports[airport][0]
 | 
					        airport = self.Airports[airport][0]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        for key in config:
 | 
					        for key in config:
 | 
				
			||||||
            if 'SIDSSTARS' == key:
 | 
					            if 'SIDSSTARS' == key:
 | 
				
			||||||
@@ -140,13 +167,14 @@ class SctEseFormat:
 | 
				
			|||||||
        # parse all arrival routes
 | 
					        # parse all arrival routes
 | 
				
			||||||
        for line in config['SIDSSTARS']:
 | 
					        for line in config['SIDSSTARS']:
 | 
				
			||||||
            route = self.parseArrivalRoute(line, airport)
 | 
					            route = self.parseArrivalRoute(line, airport)
 | 
				
			||||||
            if None != route and route.name in allowedRoutes:
 | 
					            if None != route and route.Name in allowedRoutes:
 | 
				
			||||||
                self.arrivalRoutes.setdefault(route.runway, []).append(route)
 | 
					                self.ArrivalRoutes.setdefault(route.Runway, []).append(route)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def __init__(self, sctFilepath : str, eseFilepath : str, airport : str, allowedRoutes : list):
 | 
					    def __init__(self, sctFilepath : str, eseFilepath : str, airport : str, allowedRoutes : list):
 | 
				
			||||||
        self.arrivalRoutes = {}
 | 
					        self.ArrivalRoutes = {}
 | 
				
			||||||
        self.waypoints = {}
 | 
					        self.Waypoints = {}
 | 
				
			||||||
        self.airports = {}
 | 
					        self.Airports = {}
 | 
				
			||||||
 | 
					        self.Runways = {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        self.extractWaypoints(sctFilepath)
 | 
					        self.extractSctInformation(sctFilepath)
 | 
				
			||||||
        self.extractArrivalRoutes(eseFilepath, airport, allowedRoutes)
 | 
					        self.extractArrivalRoutes(eseFilepath, airport, allowedRoutes)
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										256
									
								
								aman/sys/RecedingHorizonControl.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										256
									
								
								aman/sys/RecedingHorizonControl.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,256 @@
 | 
				
			|||||||
 | 
					#!/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)
 | 
				
			||||||
							
								
								
									
										33
									
								
								aman/sys/RecedingHorizonWindow.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								aman/sys/RecedingHorizonWindow.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,33 @@
 | 
				
			|||||||
 | 
					#!/usr/bin/env python
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from aman.types.Inbound import Inbound
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class RecedingHorizonWindow:
 | 
				
			||||||
 | 
					    def __init__(self, startTime, endTime):
 | 
				
			||||||
 | 
					        self.StartTime = startTime
 | 
				
			||||||
 | 
					        self.EndTime = endTime
 | 
				
			||||||
 | 
					        self.Inbounds = []
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def isInWindow(self, inbound : Inbound):
 | 
				
			||||||
 | 
					        for report in self.Inbounds:
 | 
				
			||||||
 | 
					            if report.Callsign == inbound.Callsign:
 | 
				
			||||||
 | 
					                return True
 | 
				
			||||||
 | 
					        return False
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def inbound(self, callsign : str):
 | 
				
			||||||
 | 
					        for report in self.Inbounds:
 | 
				
			||||||
 | 
					            if report.Callsign == callsign:
 | 
				
			||||||
 | 
					                return report
 | 
				
			||||||
 | 
					        return None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def insert(self, inbound : Inbound):
 | 
				
			||||||
 | 
					        for i in range(0, len(self.Inbounds)):
 | 
				
			||||||
 | 
					            if self.Inbounds[i].Callsign == inbound.Callsign:
 | 
				
			||||||
 | 
					                return
 | 
				
			||||||
 | 
					        self.Inbounds.append(inbound)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def remove(self, callsign : str):
 | 
				
			||||||
 | 
					        for i in range(0, len(self.Inbounds)):
 | 
				
			||||||
 | 
					            if self.Inbounds[i].Callsign == callsign:
 | 
				
			||||||
 | 
					                self.Inbounds.pop(i)
 | 
				
			||||||
 | 
					                return
 | 
				
			||||||
@@ -7,13 +7,16 @@ import scipy.interpolate
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
class WeatherModel:
 | 
					class WeatherModel:
 | 
				
			||||||
    def __init__(self, gaforId, weather : Weather):
 | 
					    def __init__(self, gaforId, weather : Weather):
 | 
				
			||||||
        self.gafor = gaforId
 | 
					        self.Gafor = gaforId
 | 
				
			||||||
        self.weather = weather
 | 
					        self.Weather = weather
 | 
				
			||||||
        self.windDirectionModel = None
 | 
					        self.Altitudes = None
 | 
				
			||||||
        self.windSpeedModel = None
 | 
					        self.Directions = None
 | 
				
			||||||
        self.lastWeatherUpdate = None
 | 
					        self.Windspeeds = None
 | 
				
			||||||
        self.minimumAltitude = 1000000
 | 
					        self.WindDirectionModel = None
 | 
				
			||||||
        self.maximumAltitude = -1
 | 
					        self.WindSpeedModel = None
 | 
				
			||||||
 | 
					        self.LastWeatherUpdate = None
 | 
				
			||||||
 | 
					        self.MinimumAltitude = 1000000
 | 
				
			||||||
 | 
					        self.MaximumAltitude = -1
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # create the density interpolation model
 | 
					        # create the density interpolation model
 | 
				
			||||||
        # the density model is based on https://aerotoolbox.com/atmcalc/
 | 
					        # the density model is based on https://aerotoolbox.com/atmcalc/
 | 
				
			||||||
@@ -95,54 +98,87 @@ class WeatherModel:
 | 
				
			|||||||
        return ias * math.sqrt(1.225 / self.densityModel(altitude).item())
 | 
					        return ias * math.sqrt(1.225 / self.densityModel(altitude).item())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def updateWindModel(self):
 | 
					    def updateWindModel(self):
 | 
				
			||||||
        if None == self.lastWeatherUpdate or self.lastWeatherUpdate != self.weather.provider.updateTime:
 | 
					        if None == self.Weather or None == self.Weather.Provider:
 | 
				
			||||||
            self.lastWeatherUpdate = self.weather.provider.updateTime
 | 
					            return
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            self.minimumAltitude = 1000000
 | 
					        if None == self.LastWeatherUpdate or self.LastWeatherUpdate != self.Weather.Provider.UpdateTime:
 | 
				
			||||||
            self.maximumAltitude = -1
 | 
					            self.MinimumAltitude = 1000000
 | 
				
			||||||
            self.windDirectionModel = None
 | 
					            self.MaximumAltitude = -1
 | 
				
			||||||
            self.windSpeedModel = None
 | 
					            self.WindDirectionModel = None
 | 
				
			||||||
 | 
					            self.WindSpeedModel = None
 | 
				
			||||||
 | 
					            self.Altitudes = None
 | 
				
			||||||
 | 
					            self.Directions = None
 | 
				
			||||||
 | 
					            self.Windspeeds = None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            if None != self.weather.provider.windData and self.gafor in self.weather.provider.windData:
 | 
					            if None != self.Weather.Provider.WindData and self.Gafor in self.Weather.Provider.WindData:
 | 
				
			||||||
                altitudes = []
 | 
					                self.Altitudes = []
 | 
				
			||||||
                directions = []
 | 
					                self.Directions = []
 | 
				
			||||||
                speeds = []
 | 
					                self.Windspeeds = []
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                # collect the data for the wind model
 | 
					                # collect the data for the wind model
 | 
				
			||||||
                for level in self.weather.provider.windData[self.gafor]:
 | 
					                for level in self.Weather.Provider.WindData[self.Gafor]:
 | 
				
			||||||
                    altitudes.append(level[0])
 | 
					                    self.Altitudes.append(level[0])
 | 
				
			||||||
                    directions.append(level[1])
 | 
					                    self.Directions.append(level[1])
 | 
				
			||||||
                    speeds.append(level[2])
 | 
					                    self.Windspeeds.append(level[2])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    # define the thresholds for later boundary checks
 | 
					                    # define the thresholds for later boundary checks
 | 
				
			||||||
                    if self.minimumAltitude > level[0]:
 | 
					                    if self.MinimumAltitude > level[0]:
 | 
				
			||||||
                        self.minimumAltitude = level[0]
 | 
					                        self.MinimumAltitude = level[0]
 | 
				
			||||||
                    if self.maximumAltitude < level[0]:
 | 
					                    if self.MaximumAltitude < level[0]:
 | 
				
			||||||
                        self.maximumAltitude = level[0]
 | 
					                        self.MaximumAltitude = level[0]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                # calculate the models
 | 
					                # calculate the models
 | 
				
			||||||
                if 1 < len(altitudes):
 | 
					                if 1 < len(self.Altitudes):
 | 
				
			||||||
                    self.windDirectionModel = scipy.interpolate.interp1d(altitudes, directions)
 | 
					                    self.WindDirectionModel = scipy.interpolate.interp1d(self.Altitudes, self.Directions)
 | 
				
			||||||
                    self.windSpeedModel = scipy.interpolate.interp1d(altitudes, speeds)
 | 
					                    self.WindSpeedModel = scipy.interpolate.interp1d(self.Altitudes, self.Windspeeds)
 | 
				
			||||||
 | 
					                    self.LastWeatherUpdate = self.Weather.Provider.UpdateTime
 | 
				
			||||||
 | 
					            else:
 | 
				
			||||||
 | 
					                self.LastWeatherUpdate = None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def calculateGS(self, altitude : int, ias : int, heading : int):
 | 
					    def interpolateWindData(self, altitude : int):
 | 
				
			||||||
        self.updateWindModel()
 | 
					        self.updateWindModel()
 | 
				
			||||||
        tas = self.calculateTAS(altitude, ias)
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # initialize the wind data
 | 
					        # initialized the wind data
 | 
				
			||||||
        if None != self.windDirectionModel and None != self.windSpeedModel:
 | 
					        if None != self.WindDirectionModel and None != self.WindSpeedModel:
 | 
				
			||||||
            direction = 0.0
 | 
					            direction = 0.0
 | 
				
			||||||
            speed = 0.0
 | 
					            speed = 0.0
 | 
				
			||||||
            if None != self.windSpeedModel and None != self.windDirectionModel:
 | 
					            if None != self.WindSpeedModel and None != self.WindDirectionModel:
 | 
				
			||||||
                if self.maximumAltitude <= altitude:
 | 
					                if self.MaximumAltitude <= altitude:
 | 
				
			||||||
                    altitude = self.maximumAltitude - 1
 | 
					                    altitude = self.MaximumAltitude - 1
 | 
				
			||||||
                if self.minimumAltitude >= altitude:
 | 
					                if self.MinimumAltitude >= altitude:
 | 
				
			||||||
                    altitude = self.minimumAltitude + 1
 | 
					                    altitude = self.MinimumAltitude + 1
 | 
				
			||||||
                direction = self.windDirectionModel(altitude).item()
 | 
					                direction = self.WindDirectionModel(altitude).item()
 | 
				
			||||||
                speed = self.windSpeedModel(altitude).item()
 | 
					                speed = self.WindSpeedModel(altitude).item()
 | 
				
			||||||
        else:
 | 
					        else:
 | 
				
			||||||
            speed = 0
 | 
					            speed = 0
 | 
				
			||||||
            direction = 0
 | 
					            direction = 0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # calculate the ground speed based on the headwind component
 | 
					        return speed, direction
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def calculateGS(self, altitude : int, ias : int, heading : int):
 | 
				
			||||||
 | 
					        speed, direction = self.interpolateWindData(altitude)
 | 
				
			||||||
 | 
					        tas = self.calculateTAS(altitude, ias)
 | 
				
			||||||
        return tas + speed * math.cos(math.radians(direction) - math.radians(heading))
 | 
					        return tas + speed * math.cos(math.radians(direction) - math.radians(heading))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def convertGSToTAS(self, altitude : int, gs : int, heading : int):
 | 
				
			||||||
 | 
					        speed, direction = self.interpolateWindData(altitude)
 | 
				
			||||||
 | 
					        return gs - speed * math.cos(math.radians(direction) - math.radians(heading))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def estimateCourse(self, altitude : int, gs : int, heading : int):
 | 
				
			||||||
 | 
					        tas = self.convertGSToTAS(altitude, gs, heading)
 | 
				
			||||||
 | 
					        speed, direction = self.interpolateWindData(altitude)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        aca = heading - direction
 | 
				
			||||||
 | 
					        wca = speed * aca / tas
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if 0 <= aca:
 | 
				
			||||||
 | 
					            course = heading + wca
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            course = heading - wca
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        while 0 > course:
 | 
				
			||||||
 | 
					            course += 360
 | 
				
			||||||
 | 
					        while 360 < course:
 | 
				
			||||||
 | 
					            course -= 360
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return course
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,53 +1,140 @@
 | 
				
			|||||||
#!/usr/bin/env python
 | 
					#!/usr/bin/env python
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from threading import Thread, Lock
 | 
					from threading import Thread, Lock
 | 
				
			||||||
 | 
					import sys
 | 
				
			||||||
import time
 | 
					import time
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from aman.com import Weather
 | 
				
			||||||
 | 
					from aman.com.Euroscope import Euroscope
 | 
				
			||||||
from aman.config.Airport import Airport
 | 
					from aman.config.Airport import Airport
 | 
				
			||||||
 | 
					from aman.config.AirportSequencing import AirportSequencing
 | 
				
			||||||
 | 
					from aman.sys.aco.Colony import Colony
 | 
				
			||||||
 | 
					from aman.sys.aco.Configuration import Configuration
 | 
				
			||||||
 | 
					from aman.sys.aco.Node import Node
 | 
				
			||||||
 | 
					from aman.sys.WeatherModel import WeatherModel
 | 
				
			||||||
 | 
					from aman.sys.RecedingHorizonControl import RecedingHorizonControl
 | 
				
			||||||
 | 
					from aman.types.Inbound import Inbound
 | 
				
			||||||
 | 
					from aman.types.PerformanceData import PerformanceData
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class Worker(Thread):
 | 
					class Worker(Thread):
 | 
				
			||||||
    def __init__(self):
 | 
					    def __init__(self, icao : str, configuration : Airport, weather : Weather,
 | 
				
			||||||
 | 
					                 performance : PerformanceData, euroscope : Euroscope):
 | 
				
			||||||
        Thread.__init__(self)
 | 
					        Thread.__init__(self)
 | 
				
			||||||
        self.stopThread = None
 | 
					        self.StopThread = None
 | 
				
			||||||
        self.icao = None
 | 
					        self.Icao = icao
 | 
				
			||||||
        self.configuration = None
 | 
					        self.Configuration = configuration
 | 
				
			||||||
        self.arrivalRoutes = None
 | 
					        self.SequencingConfiguration = configuration.DefaultSequencing
 | 
				
			||||||
        self.updateLock = None
 | 
					        self.PerformanceData = performance
 | 
				
			||||||
        self.reportQueue = {}
 | 
					        self.UpdateLock = Lock()
 | 
				
			||||||
 | 
					        self.ReportQueue = {}
 | 
				
			||||||
 | 
					        if None != weather:
 | 
				
			||||||
 | 
					            self.WeatherModel = WeatherModel(configuration.GaforId, weather)
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            self.WeatherModel = WeatherModel(0, None)
 | 
				
			||||||
 | 
					        self.RecedingHorizonControl = RecedingHorizonControl(configuration.RecedingHorizonControl)
 | 
				
			||||||
 | 
					        self.Euroscope = euroscope
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def __del__(self):
 | 
					        # merge the constraint information with the GNG information
 | 
				
			||||||
        self.release()
 | 
					        for runway in self.Configuration.GngData.ArrivalRoutes:
 | 
				
			||||||
 | 
					            for star in self.Configuration.GngData.ArrivalRoutes[runway]:
 | 
				
			||||||
 | 
					                for name in self.Configuration.ArrivalRouteConstraints:
 | 
				
			||||||
 | 
					                    if name == star.Name:
 | 
				
			||||||
 | 
					                        for constraint in self.Configuration.ArrivalRouteConstraints[name]:
 | 
				
			||||||
 | 
					                            foundWaypoint = False
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def acquire(self, icao : str, configuration : Airport):
 | 
					                            for waypoint in star.Route:
 | 
				
			||||||
        self.stopThread = None
 | 
					                                if constraint.Name == waypoint.Name:
 | 
				
			||||||
        self.icao = icao
 | 
					                                    waypoint.Altitude = constraint.Altitude
 | 
				
			||||||
        self.configuration = configuration
 | 
					                                    waypoint.Speed = constraint.Speed
 | 
				
			||||||
        self.arrivalRoutes = configuration.gngData.arrivalRoutes
 | 
					                                    waypoint.BaseTurn = constraint.BaseTurn
 | 
				
			||||||
        self.updateLock = Lock()
 | 
					                                    waypoint.FinalTurn = constraint.FinalTurn
 | 
				
			||||||
        self.reportQueue = {}
 | 
					                                    foundWaypoint = True
 | 
				
			||||||
 | 
					                                    break
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                            if False == foundWaypoint:
 | 
				
			||||||
 | 
					                                sys.stderr.write('Unable to find ' + constraint.Name + ' in ' + name)
 | 
				
			||||||
 | 
					                                sys.exit(-1)
 | 
				
			||||||
 | 
					                        break
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.setDaemon(True)
 | 
				
			||||||
        self.start()
 | 
					        self.start()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def acquireLock(self):
 | 
					    def acquireLock(self):
 | 
				
			||||||
        if None != self.updateLock:
 | 
					        if None != self.UpdateLock:
 | 
				
			||||||
            self.updateLock.acquire()
 | 
					            self.UpdateLock.acquire()
 | 
				
			||||||
 | 
					 | 
				
			||||||
    def release(self):
 | 
					 | 
				
			||||||
        self.stopThread = True
 | 
					 | 
				
			||||||
        self.join()
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def releaseLock(self):
 | 
					    def releaseLock(self):
 | 
				
			||||||
        if None != self.updateLock:
 | 
					        if None != self.UpdateLock:
 | 
				
			||||||
            self.updateLock.release()
 | 
					            self.UpdateLock.release()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def run(self):
 | 
					    def run(self):
 | 
				
			||||||
        counter = 0
 | 
					        counter = 0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        while None == self.stopThread:
 | 
					        while None == self.StopThread:
 | 
				
			||||||
            time.sleep(1)
 | 
					            time.sleep(1)
 | 
				
			||||||
            counter += 1
 | 
					            counter += 1
 | 
				
			||||||
            if 0 != (counter % 60):
 | 
					            if 0 != (counter % 60):
 | 
				
			||||||
                continue
 | 
					                continue
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            # TODO handle the report queue and update internal information
 | 
					            self.acquireLock()
 | 
				
			||||||
            # TODO execute planning, etc.
 | 
					
 | 
				
			||||||
            continue
 | 
					            # perform some book-keeping
 | 
				
			||||||
 | 
					            self.RecedingHorizonControl.cleanupWindows()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            # update the aircraft information in RHC
 | 
				
			||||||
 | 
					            for callsign in self.ReportQueue:
 | 
				
			||||||
 | 
					                report = self.ReportQueue[callsign]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                if '' != report.initialApproachFix:
 | 
				
			||||||
 | 
					                    inbound = Inbound(report, self.PerformanceData)
 | 
				
			||||||
 | 
					                    Node(inbound, inbound.ReportTime, self.WeatherModel, self.Configuration, self.SequencingConfiguration)
 | 
				
			||||||
 | 
					                    if None != inbound.EnrouteArrivalTime:
 | 
				
			||||||
 | 
					                        self.RecedingHorizonControl.updateReport(inbound)
 | 
				
			||||||
 | 
					                    else:
 | 
				
			||||||
 | 
					                        print('Unable to find all data of ' + report.aircraft.callsign)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            self.ReportQueue.clear()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            # search the ACO relevant aircrafts
 | 
				
			||||||
 | 
					            relevantInbounds, earliestArrivalTime = self.RecedingHorizonControl.optimizationRelevantInbounds()
 | 
				
			||||||
 | 
					            if None != relevantInbounds:
 | 
				
			||||||
 | 
					                start = time.process_time()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                # get the last landing aircrafts per runway before the RHC stage to check for constraints
 | 
				
			||||||
 | 
					                # this is required to handle the overlap between windows
 | 
				
			||||||
 | 
					                runways, iafs = self.RecedingHorizonControl.latestFixedInbounds(self.Configuration, self.SequencingConfiguration)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                # configure the ACO run
 | 
				
			||||||
 | 
					                acoConfig = Configuration(constraints = self.SequencingConfiguration, config = self.Configuration,
 | 
				
			||||||
 | 
					                                          earliest = earliestArrivalTime, weather = self.WeatherModel,
 | 
				
			||||||
 | 
					                                          preceedingRunways = runways, preceedingIafs = iafs,
 | 
				
			||||||
 | 
					                                          ants = 5 * len(relevantInbounds), generations = 5 * len(relevantInbounds))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                # run the optimizer outside the locking functions
 | 
				
			||||||
 | 
					                self.releaseLock()
 | 
				
			||||||
 | 
					                # perform the ACO run
 | 
				
			||||||
 | 
					                aco = Colony(relevantInbounds, acoConfig)
 | 
				
			||||||
 | 
					                aco.optimize()
 | 
				
			||||||
 | 
					                self.acquireLock()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                if None != aco.Result:
 | 
				
			||||||
 | 
					                    for node in aco.Result:
 | 
				
			||||||
 | 
					                        self.RecedingHorizonControl.resequenceInbound(node.Inbound)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                # measure the exuction time of the overall optimization process
 | 
				
			||||||
 | 
					                executionTime = time.process_time() - start
 | 
				
			||||||
 | 
					                if 60.0 <= executionTime:
 | 
				
			||||||
 | 
					                    print('Optimized ' + str(len(aco.Result)) + ' inbounds in ' + str(executionTime) + ' seconds')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            self.releaseLock()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def inboundSequence(self):
 | 
				
			||||||
 | 
					        self.acquireLock()
 | 
				
			||||||
 | 
					        sequence = self.RecedingHorizonControl.sequence()
 | 
				
			||||||
 | 
					        self.releaseLock()
 | 
				
			||||||
 | 
					        return sequence
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def configure(self, configuration : AirportSequencing):
 | 
				
			||||||
 | 
					        self.acquireLock()
 | 
				
			||||||
 | 
					        self.SequencingConfiguration = configuration
 | 
				
			||||||
 | 
					        self.releaseLock()
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										122
									
								
								aman/sys/aco/Ant.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										122
									
								
								aman/sys/aco/Ant.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,122 @@
 | 
				
			|||||||
 | 
					#!/usr/bin/env python
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from datetime import datetime, timedelta
 | 
				
			||||||
 | 
					import math
 | 
				
			||||||
 | 
					import numpy as np
 | 
				
			||||||
 | 
					import random
 | 
				
			||||||
 | 
					import bisect
 | 
				
			||||||
 | 
					import itertools
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from aman.sys.aco.Configuration import Configuration
 | 
				
			||||||
 | 
					from aman.sys.aco.RunwayManager import RunwayManager
 | 
				
			||||||
 | 
					from aman.sys.aco.Node import Node
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# This class implements a single ant of the following paper:
 | 
				
			||||||
 | 
					# https://sci-hub.mksa.top/10.1109/cec.2019.8790135
 | 
				
			||||||
 | 
					class Ant:
 | 
				
			||||||
 | 
					    def __init__(self, pheromoneTable : np.array, configuration : Configuration, nodes):
 | 
				
			||||||
 | 
					        self.Configuration = configuration
 | 
				
			||||||
 | 
					        self.Nodes = nodes
 | 
				
			||||||
 | 
					        self.RunwayManager = RunwayManager(self.Configuration)
 | 
				
			||||||
 | 
					        self.InboundSelected = [ False ] * len(self.Nodes)
 | 
				
			||||||
 | 
					        self.InboundScore = np.zeros([ len(self.Nodes), 1 ])
 | 
				
			||||||
 | 
					        self.PheromoneMatrix = pheromoneTable
 | 
				
			||||||
 | 
					        self.SequenceDelay = timedelta(seconds = 0)
 | 
				
			||||||
 | 
					        self.Sequence = None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Implements function (5)
 | 
				
			||||||
 | 
					    def heuristicInformation(self, current : int):
 | 
				
			||||||
 | 
					        _, _, _, eta, _ = self.RunwayManager.selectArrivalRunway(self.Nodes[current], self.Configuration.EarliestArrivalTime)
 | 
				
			||||||
 | 
					        if None == eta:
 | 
				
			||||||
 | 
					            return -1.0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        inboundDelay = eta - self.Nodes[current].Inbound.InitialArrivalTime
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # calculate the fraction with a mix of the unused runway time and the delay of single aircrafts
 | 
				
			||||||
 | 
					        heuristic = inboundDelay.total_seconds() / 60.0
 | 
				
			||||||
 | 
					        heuristic = (1.0 / (heuristic or 1)) ** self.Configuration.Beta
 | 
				
			||||||
 | 
					        return heuristic
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Implements functions (3), (6)
 | 
				
			||||||
 | 
					    def selectNextLandingIndex(self):
 | 
				
			||||||
 | 
					        q = float(random.randint(0, 100)) / 100
 | 
				
			||||||
 | 
					        weights = []
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if q <= self.Configuration.PseudoRandomSelectionRate:
 | 
				
			||||||
 | 
					            for i in range(0, len(self.InboundSelected)):
 | 
				
			||||||
 | 
					                if False == self.InboundSelected[i]:
 | 
				
			||||||
 | 
					                    weights.append(self.heuristicInformation(i))
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            # roulette selection strategy
 | 
				
			||||||
 | 
					            pheromoneScale = 0.0
 | 
				
			||||||
 | 
					            for i in range(0, len(self.InboundSelected)):
 | 
				
			||||||
 | 
					                if False == self.InboundSelected[i]:
 | 
				
			||||||
 | 
					                    pheromoneScale += self.heuristicInformation(i)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            for i in range(0, len(self.InboundSelected)):
 | 
				
			||||||
 | 
					                if False == self.InboundSelected[i]:
 | 
				
			||||||
 | 
					                    weights.append(self.heuristicInformation(i) / (pheromoneScale or 1))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # something was wrong in the runway selection
 | 
				
			||||||
 | 
					        if -1.0 in weights:
 | 
				
			||||||
 | 
					            return None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        total = sum(weights)
 | 
				
			||||||
 | 
					        cumdist = list(itertools.accumulate(weights)) + [total]
 | 
				
			||||||
 | 
					        candidateIndex = bisect.bisect(cumdist, random.random() * total)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        for i in range(0, len(self.InboundSelected)):
 | 
				
			||||||
 | 
					            if False == self.InboundSelected[i]:
 | 
				
			||||||
 | 
					                if 0 == candidateIndex:
 | 
				
			||||||
 | 
					                    return i
 | 
				
			||||||
 | 
					                else:
 | 
				
			||||||
 | 
					                    candidateIndex -= 1
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def associateInbound(self, node : Node, earliestArrivalTime : datetime):
 | 
				
			||||||
 | 
					        # prepare the statistics
 | 
				
			||||||
 | 
					        _, _, rwy, eta, _ = self.RunwayManager.selectArrivalRunway(node, self.Configuration.EarliestArrivalTime)
 | 
				
			||||||
 | 
					        eta = max(earliestArrivalTime, eta)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        node.Inbound.PlannedRunway = rwy
 | 
				
			||||||
 | 
					        node.Inbound.PlannedStar = node.ArrivalCandidates[rwy.Name].Star
 | 
				
			||||||
 | 
					        node.Inbound.PlannedArrivalTime = eta
 | 
				
			||||||
 | 
					        node.Inbound.PlannedArrivalRoute = node.ArrivalCandidates[rwy.Name].ArrivalRoute
 | 
				
			||||||
 | 
					        node.Inbound.InitialArrivalTime = node.ArrivalCandidates[rwy.Name].InitialArrivalTime
 | 
				
			||||||
 | 
					        self.RunwayManager.registerNode(node, rwy.Name)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        delay = node.Inbound.PlannedArrivalTime - node.Inbound.InitialArrivalTime 
 | 
				
			||||||
 | 
					        if 0.0 < delay.total_seconds():
 | 
				
			||||||
 | 
					            return delay, rwy
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            return timedelta(seconds = 0), rwy
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def findSolution(self, first : int):
 | 
				
			||||||
 | 
					        self.Sequence = []
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # select the first inbound
 | 
				
			||||||
 | 
					        self.InboundSelected[first] = True
 | 
				
			||||||
 | 
					        delay, _ = self.associateInbound(self.Nodes[first], self.Configuration.EarliestArrivalTime)
 | 
				
			||||||
 | 
					        self.Sequence.append(first)
 | 
				
			||||||
 | 
					        self.SequenceDelay += delay
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        while 1:
 | 
				
			||||||
 | 
					            index = self.selectNextLandingIndex()
 | 
				
			||||||
 | 
					            if None == index:
 | 
				
			||||||
 | 
					                break
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            self.InboundSelected[index] = True
 | 
				
			||||||
 | 
					            delay, _ = self.associateInbound(self.Nodes[index], self.Configuration.EarliestArrivalTime)
 | 
				
			||||||
 | 
					            self.SequenceDelay += delay
 | 
				
			||||||
 | 
					            self.Sequence.append(index)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            # update the local pheromone
 | 
				
			||||||
 | 
					            update = (1.0 - self.Configuration.PropagationRatio) * self.PheromoneMatrix[self.Sequence[-2], self.Sequence[-1]]
 | 
				
			||||||
 | 
					            update += self.Configuration.PropagationRatio * self.Configuration.ThetaZero
 | 
				
			||||||
 | 
					            self.PheromoneMatrix[self.Sequence[-2], self.Sequence[-1]] = max(self.Configuration.ThetaZero, update)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # validate that nothing went wrong
 | 
				
			||||||
 | 
					        if len(self.Sequence) != len(self.Nodes):
 | 
				
			||||||
 | 
					            self.SequenceDelay = None
 | 
				
			||||||
 | 
					            self.Sequence = None
 | 
				
			||||||
							
								
								
									
										155
									
								
								aman/sys/aco/Colony.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										155
									
								
								aman/sys/aco/Colony.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,155 @@
 | 
				
			|||||||
 | 
					#!/usr/bin/env python
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from datetime import datetime as dt
 | 
				
			||||||
 | 
					from datetime import datetime, timedelta
 | 
				
			||||||
 | 
					import numpy as np
 | 
				
			||||||
 | 
					import pytz
 | 
				
			||||||
 | 
					import random
 | 
				
			||||||
 | 
					import sys
 | 
				
			||||||
 | 
					import time
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from aman.sys.aco.Ant import Ant
 | 
				
			||||||
 | 
					from aman.sys.aco.Configuration import Configuration
 | 
				
			||||||
 | 
					from aman.sys.aco.Node import Node
 | 
				
			||||||
 | 
					from aman.sys.aco.RunwayManager import RunwayManager
 | 
				
			||||||
 | 
					from aman.types.Inbound import Inbound
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# This class implements the ant colony of the following paper:
 | 
				
			||||||
 | 
					# https://sci-hub.mksa.top/10.1109/cec.2019.8790135
 | 
				
			||||||
 | 
					class Colony:
 | 
				
			||||||
 | 
					    def associateInbound(rwyManager : RunwayManager, node : Node, earliestArrivalTime : datetime):
 | 
				
			||||||
 | 
					        type, expectedRwy, rwy, eta, _ = rwyManager.selectArrivalRunway(node, earliestArrivalTime)
 | 
				
			||||||
 | 
					        if None == eta:
 | 
				
			||||||
 | 
					            return False
 | 
				
			||||||
 | 
					        eta = max(earliestArrivalTime, eta)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        node.Inbound.PlannedRunway = rwy
 | 
				
			||||||
 | 
					        node.Inbound.PlannedStar = node.ArrivalCandidates[rwy.Name].Star
 | 
				
			||||||
 | 
					        node.Inbound.PlannedArrivalRoute = node.ArrivalCandidates[rwy.Name].ArrivalRoute
 | 
				
			||||||
 | 
					        node.Inbound.PlannedArrivalTime = eta
 | 
				
			||||||
 | 
					        node.Inbound.InitialArrivalTime = node.ArrivalCandidates[rwy.Name].InitialArrivalTime
 | 
				
			||||||
 | 
					        node.Inbound.PlannedTrackmiles = node.ArrivalCandidates[rwy.Name].Trackmiles
 | 
				
			||||||
 | 
					        node.Inbound.AssignmentMode = type
 | 
				
			||||||
 | 
					        node.Inbound.ExpectedRunway = expectedRwy
 | 
				
			||||||
 | 
					        rwyManager.registerNode(node, rwy.Name)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return True
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def calculateInitialCosts(rwyManager : RunwayManager, nodes, earliestArrivalTime : datetime):
 | 
				
			||||||
 | 
					        overallDelay = timedelta(seconds = 0)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # assume that the nodes are sorted in FCFS order
 | 
				
			||||||
 | 
					        for node in nodes:
 | 
				
			||||||
 | 
					            if False == Colony.associateInbound(rwyManager, node, earliestArrivalTime):
 | 
				
			||||||
 | 
					                return None
 | 
				
			||||||
 | 
					            overallDelay += node.Inbound.PlannedArrivalTime - node.Inbound.InitialArrivalTime
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return overallDelay
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def __init__(self, inbounds, configuration : Configuration):
 | 
				
			||||||
 | 
					        self.Configuration = configuration
 | 
				
			||||||
 | 
					        self.ResultDelay = None
 | 
				
			||||||
 | 
					        self.FcfsDelay = None
 | 
				
			||||||
 | 
					        self.Result = None
 | 
				
			||||||
 | 
					        self.Nodes = []
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # create the new planning instances
 | 
				
			||||||
 | 
					        currentTime = dt.utcfromtimestamp(int(time.time())).replace(tzinfo = pytz.UTC)
 | 
				
			||||||
 | 
					        for inbound in inbounds:
 | 
				
			||||||
 | 
					            self.Nodes.append(Node(inbound, currentTime, self.Configuration.WeatherModel, self.Configuration.AirportConfiguration, self.Configuration.RunwayConstraints))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        rwyManager = RunwayManager(self.Configuration)
 | 
				
			||||||
 | 
					        delay = Colony.calculateInitialCosts(rwyManager, self.Nodes, self.Configuration.EarliestArrivalTime)
 | 
				
			||||||
 | 
					        if None == delay:
 | 
				
			||||||
 | 
					            return
 | 
				
			||||||
 | 
					        self.FcfsDelay = delay
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # run the optimization in every cycle to ensure optimal spacings based on TTG
 | 
				
			||||||
 | 
					        if 0.0 >= delay.total_seconds():
 | 
				
			||||||
 | 
					            delay = timedelta(seconds = 1.0)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # initial value for the optimization
 | 
				
			||||||
 | 
					        self.Configuration.ThetaZero = 1.0 / (len(self.Nodes) * (delay.total_seconds() / 60.0))
 | 
				
			||||||
 | 
					        self.PheromoneMatrix = np.ones(( len(self.Nodes), len(self.Nodes) ), dtype=float) * self.Configuration.ThetaZero
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def sequenceAndPredictInbound(self, rwyManager : RunwayManager, node : Node):
 | 
				
			||||||
 | 
					        self.Result.append(node)
 | 
				
			||||||
 | 
					        Colony.associateInbound(rwyManager, node, self.Configuration.EarliestArrivalTime)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        reqTimeDelta = self.Result[-1].Inbound.EnrouteArrivalTime - self.Result[-1].Inbound.PlannedArrivalTime
 | 
				
			||||||
 | 
					        self.Result[-1].Inbound.PlannedArrivalRoute[0].PTA = self.Result[-1].Inbound.PlannedArrivalRoute[0].ETA - reqTimeDelta
 | 
				
			||||||
 | 
					        for i in range(1, len(self.Result[-1].Inbound.PlannedArrivalRoute)):
 | 
				
			||||||
 | 
					            prev = self.Result[-1].Inbound.PlannedArrivalRoute[i - 1]
 | 
				
			||||||
 | 
					            current = self.Result[-1].Inbound.PlannedArrivalRoute[i]
 | 
				
			||||||
 | 
					            current.PTA = prev.PTA + (current.ETA - prev.ETA)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def optimize(self):
 | 
				
			||||||
 | 
					        if None == self.FcfsDelay:
 | 
				
			||||||
 | 
					            return False
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # define the tracking variables
 | 
				
			||||||
 | 
					        bestSequence = None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # run the optimization loops
 | 
				
			||||||
 | 
					        for _ in range(0, self.Configuration.ExplorationRuns):
 | 
				
			||||||
 | 
					            # select the first inbound
 | 
				
			||||||
 | 
					            index = random.randint(1, len(self.Nodes)) - 1
 | 
				
			||||||
 | 
					            candidates = []
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            for _ in range(0, self.Configuration.AntCount):
 | 
				
			||||||
 | 
					                # let the ant find a solution
 | 
				
			||||||
 | 
					                ant = Ant(self.PheromoneMatrix, self.Configuration, self.Nodes)
 | 
				
			||||||
 | 
					                ant.findSolution(index)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                # fallback to check if findSolution was successful
 | 
				
			||||||
 | 
					                if None == ant.SequenceDelay or None == ant.Sequence:
 | 
				
			||||||
 | 
					                    sys.stderr.write('Invalid ANT run detected!')
 | 
				
			||||||
 | 
					                    break
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                candidates.append(
 | 
				
			||||||
 | 
					                    [
 | 
				
			||||||
 | 
					                        ant.SequenceDelay,
 | 
				
			||||||
 | 
					                        ant.Sequence
 | 
				
			||||||
 | 
					                    ]
 | 
				
			||||||
 | 
					                )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            # find the best solution in all candidates of this generation
 | 
				
			||||||
 | 
					            bestCandidate = None
 | 
				
			||||||
 | 
					            for candidate in candidates:
 | 
				
			||||||
 | 
					                if None == bestCandidate or candidate[0] < bestCandidate[0]:
 | 
				
			||||||
 | 
					                    bestCandidate = candidate
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                if None != bestSequence:
 | 
				
			||||||
 | 
					                    dTheta = 1.0 / ((bestSequence[0].total_seconds() / 60.0) or 1.0)
 | 
				
			||||||
 | 
					                    for i in range(1, len(bestSequence[1])):
 | 
				
			||||||
 | 
					                        update = (1.0 - self.Configuration.Epsilon) * self.PheromoneMatrix[bestSequence[1][i - 1], bestSequence[1][i]] +  self.Configuration.Epsilon * dTheta
 | 
				
			||||||
 | 
					                        self.PheromoneMatrix[bestSequence[1][i - 1], bestSequence[1][i]] = max(update, self.Configuration.ThetaZero)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            # check if we find a new best candidate
 | 
				
			||||||
 | 
					            if None != bestCandidate:
 | 
				
			||||||
 | 
					                if None == bestSequence or bestCandidate[0] < bestSequence[0]:
 | 
				
			||||||
 | 
					                    bestSequence = bestCandidate
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                # found the optimal solution
 | 
				
			||||||
 | 
					                if 1 >= bestSequence[0].total_seconds():
 | 
				
			||||||
 | 
					                    break
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # create the final sequence
 | 
				
			||||||
 | 
					        self.Result = []
 | 
				
			||||||
 | 
					        rwyManager = RunwayManager(self.Configuration)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # use the optimized sequence
 | 
				
			||||||
 | 
					        if None != bestSequence and self.FcfsDelay >= bestSequence[0]:
 | 
				
			||||||
 | 
					            # create the resulting sequence
 | 
				
			||||||
 | 
					            self.ResultDelay = bestSequence[0]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            # finalize the sequence
 | 
				
			||||||
 | 
					            for idx in bestSequence[1]:
 | 
				
			||||||
 | 
					                self.sequenceAndPredictInbound(rwyManager, self.Nodes[idx])
 | 
				
			||||||
 | 
					        # use the FCFS sequence
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            self.ResultDelay = self.FcfsDelay
 | 
				
			||||||
 | 
					            for node in self.Nodes:
 | 
				
			||||||
 | 
					                self.sequenceAndPredictInbound(rwyManager, node)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return True
 | 
				
			||||||
							
								
								
									
										23
									
								
								aman/sys/aco/Configuration.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								aman/sys/aco/Configuration.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,23 @@
 | 
				
			|||||||
 | 
					#!/usr/bin/env python
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from datetime import timedelta
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class Configuration:
 | 
				
			||||||
 | 
					    def __init__(self, **kwargs):
 | 
				
			||||||
 | 
					        # the AMAN specific information
 | 
				
			||||||
 | 
					        self.RunwayConstraints = kwargs.get('constraints', None)
 | 
				
			||||||
 | 
					        self.PreceedingRunwayInbounds = kwargs.get('preceedingRunways', None)
 | 
				
			||||||
 | 
					        self.PreceedingIafInbounds = kwargs.get('preceedingIafs', None)
 | 
				
			||||||
 | 
					        self.EarliestArrivalTime = kwargs.get('earliest', None)
 | 
				
			||||||
 | 
					        self.WeatherModel = kwargs.get('weather', None)
 | 
				
			||||||
 | 
					        self.AirportConfiguration = kwargs.get('config', None)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # the ACO specific information
 | 
				
			||||||
 | 
					        self.AntCount = kwargs.get('ants', 20)
 | 
				
			||||||
 | 
					        self.ExplorationRuns = kwargs.get('generations', 20)
 | 
				
			||||||
 | 
					        self.PheromoneEvaporationRate = 0.9
 | 
				
			||||||
 | 
					        self.PseudoRandomSelectionRate = 0.9
 | 
				
			||||||
 | 
					        self.PropagationRatio = 0.9
 | 
				
			||||||
 | 
					        self.Epsilon = 0.1
 | 
				
			||||||
 | 
					        self.Beta = 2.0
 | 
				
			||||||
 | 
					        self.ThetaZero = None
 | 
				
			||||||
							
								
								
									
										71
									
								
								aman/sys/aco/Constraints.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										71
									
								
								aman/sys/aco/Constraints.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,71 @@
 | 
				
			|||||||
 | 
					#!/usr/bin/env python
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class SpacingConstraints:
 | 
				
			||||||
 | 
					    def __init__(self):
 | 
				
			||||||
 | 
					        self.WtcSpacing = {}
 | 
				
			||||||
 | 
					        self.WtcSpacing['L'] = {}
 | 
				
			||||||
 | 
					        self.WtcSpacing['M'] = {}
 | 
				
			||||||
 | 
					        self.WtcSpacing['H'] = {}
 | 
				
			||||||
 | 
					        self.WtcSpacing['J'] = {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.WtcSpacing['L']['L'] = 3.0
 | 
				
			||||||
 | 
					        self.WtcSpacing['L']['M'] = 3.0
 | 
				
			||||||
 | 
					        self.WtcSpacing['L']['H'] = 3.0
 | 
				
			||||||
 | 
					        self.WtcSpacing['L']['J'] = 3.0
 | 
				
			||||||
 | 
					        self.WtcSpacing['M']['L'] = 5.0
 | 
				
			||||||
 | 
					        self.WtcSpacing['M']['M'] = 3.0
 | 
				
			||||||
 | 
					        self.WtcSpacing['M']['H'] = 3.0
 | 
				
			||||||
 | 
					        self.WtcSpacing['M']['J'] = 3.0
 | 
				
			||||||
 | 
					        self.WtcSpacing['H']['L'] = 6.0
 | 
				
			||||||
 | 
					        self.WtcSpacing['H']['M'] = 5.0
 | 
				
			||||||
 | 
					        self.WtcSpacing['H']['H'] = 4.0
 | 
				
			||||||
 | 
					        self.WtcSpacing['H']['J'] = 4.0
 | 
				
			||||||
 | 
					        self.WtcSpacing['J']['L'] = 8.0
 | 
				
			||||||
 | 
					        self.WtcSpacing['J']['M'] = 7.0
 | 
				
			||||||
 | 
					        self.WtcSpacing['J']['H'] = 6.0
 | 
				
			||||||
 | 
					        self.WtcSpacing['J']['J'] = 6.0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.RecatSpacing = {}
 | 
				
			||||||
 | 
					        self.RecatSpacing['A'] = {}
 | 
				
			||||||
 | 
					        self.RecatSpacing['B'] = {}
 | 
				
			||||||
 | 
					        self.RecatSpacing['C'] = {}
 | 
				
			||||||
 | 
					        self.RecatSpacing['D'] = {}
 | 
				
			||||||
 | 
					        self.RecatSpacing['E'] = {}
 | 
				
			||||||
 | 
					        self.RecatSpacing['F'] = {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.RecatSpacing['A']['A'] = 3.0
 | 
				
			||||||
 | 
					        self.RecatSpacing['A']['B'] = 3.0
 | 
				
			||||||
 | 
					        self.RecatSpacing['A']['C'] = 3.0
 | 
				
			||||||
 | 
					        self.RecatSpacing['A']['D'] = 3.0
 | 
				
			||||||
 | 
					        self.RecatSpacing['A']['E'] = 3.0
 | 
				
			||||||
 | 
					        self.RecatSpacing['A']['F'] = 3.0
 | 
				
			||||||
 | 
					        self.RecatSpacing['B']['A'] = 4.0
 | 
				
			||||||
 | 
					        self.RecatSpacing['B']['B'] = 4.0
 | 
				
			||||||
 | 
					        self.RecatSpacing['B']['C'] = 4.0
 | 
				
			||||||
 | 
					        self.RecatSpacing['B']['D'] = 4.0
 | 
				
			||||||
 | 
					        self.RecatSpacing['B']['E'] = 4.0
 | 
				
			||||||
 | 
					        self.RecatSpacing['B']['F'] = 4.0
 | 
				
			||||||
 | 
					        self.RecatSpacing['C']['A'] = 5.0
 | 
				
			||||||
 | 
					        self.RecatSpacing['C']['B'] = 5.0
 | 
				
			||||||
 | 
					        self.RecatSpacing['C']['C'] = 5.0
 | 
				
			||||||
 | 
					        self.RecatSpacing['C']['D'] = 5.0
 | 
				
			||||||
 | 
					        self.RecatSpacing['C']['E'] = 5.0
 | 
				
			||||||
 | 
					        self.RecatSpacing['C']['F'] = 5.0
 | 
				
			||||||
 | 
					        self.RecatSpacing['D']['A'] = 6.0
 | 
				
			||||||
 | 
					        self.RecatSpacing['D']['B'] = 4.0
 | 
				
			||||||
 | 
					        self.RecatSpacing['D']['C'] = 3.0
 | 
				
			||||||
 | 
					        self.RecatSpacing['D']['D'] = 3.0
 | 
				
			||||||
 | 
					        self.RecatSpacing['D']['E'] = 2.5
 | 
				
			||||||
 | 
					        self.RecatSpacing['D']['F'] = 2.5
 | 
				
			||||||
 | 
					        self.RecatSpacing['E']['A'] = 7.0
 | 
				
			||||||
 | 
					        self.RecatSpacing['E']['B'] = 5.0
 | 
				
			||||||
 | 
					        self.RecatSpacing['E']['C'] = 4.0
 | 
				
			||||||
 | 
					        self.RecatSpacing['E']['D'] = 4.0
 | 
				
			||||||
 | 
					        self.RecatSpacing['E']['E'] = 3.0
 | 
				
			||||||
 | 
					        self.RecatSpacing['E']['F'] = 3.0
 | 
				
			||||||
 | 
					        self.RecatSpacing['F']['A'] = 8.0
 | 
				
			||||||
 | 
					        self.RecatSpacing['F']['B'] = 6.0
 | 
				
			||||||
 | 
					        self.RecatSpacing['F']['C'] = 5.0
 | 
				
			||||||
 | 
					        self.RecatSpacing['F']['D'] = 5.0
 | 
				
			||||||
 | 
					        self.RecatSpacing['F']['E'] = 4.0
 | 
				
			||||||
 | 
					        self.RecatSpacing['F']['F'] = 3.0
 | 
				
			||||||
							
								
								
									
										222
									
								
								aman/sys/aco/Node.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										222
									
								
								aman/sys/aco/Node.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,222 @@
 | 
				
			|||||||
 | 
					#!/usr/bin/env python
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import math
 | 
				
			||||||
 | 
					import sys
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from datetime import datetime, timedelta
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from aman.config.Airport import Airport
 | 
				
			||||||
 | 
					from aman.config.AirportSequencing import AirportSequencing
 | 
				
			||||||
 | 
					from aman.formats.SctEseFormat import SctEseFormat
 | 
				
			||||||
 | 
					from aman.sys.WeatherModel import WeatherModel
 | 
				
			||||||
 | 
					from aman.types.ArrivalData import ArrivalData
 | 
				
			||||||
 | 
					from aman.types.ArrivalRoute import ArrivalRoute
 | 
				
			||||||
 | 
					from aman.types.ArrivalWaypoint import ArrivalWaypoint
 | 
				
			||||||
 | 
					from aman.types.Runway import Runway
 | 
				
			||||||
 | 
					from aman.types.Inbound import Inbound
 | 
				
			||||||
 | 
					from aman.types.Waypoint import Waypoint
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class Node:
 | 
				
			||||||
 | 
					    def findArrivalRoute(iaf : str, runway : Runway, navData : SctEseFormat):
 | 
				
			||||||
 | 
					        for arrivalRunway in navData.ArrivalRoutes:
 | 
				
			||||||
 | 
					            if arrivalRunway == runway.Name:
 | 
				
			||||||
 | 
					                stars = navData.ArrivalRoutes[arrivalRunway]
 | 
				
			||||||
 | 
					                for star in stars:
 | 
				
			||||||
 | 
					                    if 0 != len(star.Route) and iaf == star.Iaf.Name:
 | 
				
			||||||
 | 
					                        return star
 | 
				
			||||||
 | 
					        return None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def updateArrivalWaypoint(self, arrivalRoute, flightTime, altitude, indicatedAirspeed, groundSpeed):
 | 
				
			||||||
 | 
					        arrivalRoute[-1].FlightTime = timedelta(seconds = flightTime)
 | 
				
			||||||
 | 
					        arrivalRoute[-1].ETA = self.PredictionTime + arrivalRoute[-1].FlightTime
 | 
				
			||||||
 | 
					        arrivalRoute[-1].PTA = arrivalRoute[-1].ETA
 | 
				
			||||||
 | 
					        arrivalRoute[-1].Altitude = altitude
 | 
				
			||||||
 | 
					        arrivalRoute[-1].IndicatedAirspeed = indicatedAirspeed
 | 
				
			||||||
 | 
					        arrivalRoute[-1].GroundSpeed = groundSpeed
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def arrivalEstimation(self, runway : Runway, star : ArrivalRoute, weather : WeatherModel):
 | 
				
			||||||
 | 
					        # calculate remaining trackmiles
 | 
				
			||||||
 | 
					        trackmiles = self.PredictedDistanceToIAF
 | 
				
			||||||
 | 
					        start = star.Route[0]
 | 
				
			||||||
 | 
					        turnIndices = [ -1, -1 ]
 | 
				
			||||||
 | 
					        constraints = []
 | 
				
			||||||
 | 
					        for i in range(0, len(star.Route)):
 | 
				
			||||||
 | 
					            # identified the base turn
 | 
				
			||||||
 | 
					            if True == star.Route[i].BaseTurn:
 | 
				
			||||||
 | 
					                turnIndices[0] = i
 | 
				
			||||||
 | 
					            # identified the final turn
 | 
				
			||||||
 | 
					            elif -1 != turnIndices[0] and True == star.Route[i].FinalTurn:
 | 
				
			||||||
 | 
					                turnIndices[1] = i
 | 
				
			||||||
 | 
					            # skip waypoints until the final turn point is found
 | 
				
			||||||
 | 
					            elif -1 != turnIndices[0] and -1 == turnIndices[1]:
 | 
				
			||||||
 | 
					                continue
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            trackmiles += start.haversine(star.Route[i])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            # check if a new constraint is defined
 | 
				
			||||||
 | 
					            altitude = -1
 | 
				
			||||||
 | 
					            speed = -1
 | 
				
			||||||
 | 
					            if None != star.Route[i].Altitude:
 | 
				
			||||||
 | 
					                altitude = star.Route[i].Altitude
 | 
				
			||||||
 | 
					            if None != star.Route[i].Speed:
 | 
				
			||||||
 | 
					                speed = star.Route[i].Speed
 | 
				
			||||||
 | 
					            if -1 != altitude or -1 != speed:
 | 
				
			||||||
 | 
					                constraints.append([ trackmiles, altitude, speed ])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            start = star.Route[i]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # add the remaining distance from the last waypoint to the runway threshold
 | 
				
			||||||
 | 
					        trackmiles += start.haversine(runway.Start)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if turnIndices[0] > turnIndices[1] or (-1 == turnIndices[1] and -1 != turnIndices[0]):
 | 
				
			||||||
 | 
					            sys.stderr.write('Invalid constraint definition found for ' + star.Name)
 | 
				
			||||||
 | 
					            sys.exit(-1)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # calculate descend profile
 | 
				
			||||||
 | 
					        currentHeading = Waypoint(latitude = self.Inbound.Report.position.latitude, longitude = self.Inbound.Report.position.longitude).bearing(star.Route[0])
 | 
				
			||||||
 | 
					        currentIAS = self.Inbound.PerformanceData.ias(self.Inbound.Report.dynamics.altitude, trackmiles)
 | 
				
			||||||
 | 
					        currentPosition = [ self.Inbound.Report.dynamics.altitude, self.Inbound.Report.dynamics.groundSpeed ]
 | 
				
			||||||
 | 
					        distanceToWaypoint = self.PredictedDistanceToIAF
 | 
				
			||||||
 | 
					        flightTimeSeconds = 0
 | 
				
			||||||
 | 
					        flightTimeOnStarSeconds = 0
 | 
				
			||||||
 | 
					        nextWaypointIndex = 0
 | 
				
			||||||
 | 
					        flownDistance = 0.0
 | 
				
			||||||
 | 
					        arrivalRoute = [ ArrivalWaypoint(waypoint = star.Route[0], trackmiles = distanceToWaypoint) ]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        while True:
 | 
				
			||||||
 | 
					            # check if a constraint cleanup is needed and if a speed-update is needed
 | 
				
			||||||
 | 
					            if 0 != len(constraints) and flownDistance >= constraints[0][0]:
 | 
				
			||||||
 | 
					                if -1 != constraints[0][2]:
 | 
				
			||||||
 | 
					                    currentIAS = min(constraints[0][2], self.Inbound.PerformanceData.ias(self.Inbound.Report.dynamics.altitude, trackmiles - flownDistance))
 | 
				
			||||||
 | 
					                    currentPosition[1] = min(weather.calculateGS(currentPosition[0], currentIAS, currentHeading), currentPosition[1])
 | 
				
			||||||
 | 
					                constraints.pop(0)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            # search next altitude constraint
 | 
				
			||||||
 | 
					            altitudeDistance = 0
 | 
				
			||||||
 | 
					            nextAltitude = 0
 | 
				
			||||||
 | 
					            for constraint in constraints:
 | 
				
			||||||
 | 
					                if -1 != constraint[1]:
 | 
				
			||||||
 | 
					                    altitudeDistance = constraint[0]
 | 
				
			||||||
 | 
					                    nextAltitude = constraint[1]
 | 
				
			||||||
 | 
					                    break
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            # check if update of altitude and speed is needed on 3° glide
 | 
				
			||||||
 | 
					            if currentPosition[0] > nextAltitude and ((currentPosition[0] - nextAltitude) / 1000 * 3) > (altitudeDistance - flownDistance):
 | 
				
			||||||
 | 
					                oldGroundspeed = currentPosition[1]
 | 
				
			||||||
 | 
					                descendRate = (currentPosition[1] / 60) / 3 * 1000 / 6
 | 
				
			||||||
 | 
					                newAltitude = currentPosition[0] - descendRate
 | 
				
			||||||
 | 
					                if 0 > newAltitude:
 | 
				
			||||||
 | 
					                    newAltitude = 0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                currentPosition = [ newAltitude, min(weather.calculateGS(newAltitude, currentIAS, currentHeading), currentPosition[1]) ]
 | 
				
			||||||
 | 
					                distance = (currentPosition[1] + oldGroundspeed) / 2 / 60 / 6
 | 
				
			||||||
 | 
					            else:
 | 
				
			||||||
 | 
					                distance = currentPosition[1] / 60 / 6
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            # update the statistics
 | 
				
			||||||
 | 
					            distanceToWaypoint -= distance
 | 
				
			||||||
 | 
					            flownDistance += distance
 | 
				
			||||||
 | 
					            newIAS = min(currentIAS, self.Inbound.PerformanceData.ias(currentPosition[0], trackmiles - flownDistance))
 | 
				
			||||||
 | 
					            if newIAS < currentIAS:
 | 
				
			||||||
 | 
					                currentPosition[1] = min(weather.calculateGS(currentPosition[0], newIAS, currentHeading), currentPosition[1])
 | 
				
			||||||
 | 
					                currentIAS = newIAS
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            flightTimeSeconds += 10
 | 
				
			||||||
 | 
					            if flownDistance >= self.PredictedDistanceToIAF:
 | 
				
			||||||
 | 
					                flightTimeOnStarSeconds += 10
 | 
				
			||||||
 | 
					            if flownDistance >= trackmiles:
 | 
				
			||||||
 | 
					                if None == arrivalRoute[-1].FlightTime:
 | 
				
			||||||
 | 
					                    self.updateArrivalWaypoint(arrivalRoute, flightTimeSeconds, currentPosition[0], currentIAS, currentPosition[1])
 | 
				
			||||||
 | 
					                break
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            # check if we follow a new waypoint pair
 | 
				
			||||||
 | 
					            if 0 >= distanceToWaypoint:
 | 
				
			||||||
 | 
					                lastWaypointIndex = nextWaypointIndex
 | 
				
			||||||
 | 
					                nextWaypointIndex += 1
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                self.updateArrivalWaypoint(arrivalRoute, flightTimeSeconds, currentPosition[0], currentIAS, currentPosition[1])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                # check if a skip from base to final turn waypoints is needed
 | 
				
			||||||
 | 
					                if -1 != turnIndices[0] and nextWaypointIndex > turnIndices[0] and nextWaypointIndex < turnIndices[1]:
 | 
				
			||||||
 | 
					                    nextWaypointIndex = turnIndices[1]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                # update the statistics
 | 
				
			||||||
 | 
					                if nextWaypointIndex < len(star.Route):
 | 
				
			||||||
 | 
					                    distanceToWaypoint = star.Route[lastWaypointIndex].haversine(star.Route[nextWaypointIndex])
 | 
				
			||||||
 | 
					                    currentHeading = star.Route[lastWaypointIndex].bearing(star.Route[nextWaypointIndex])
 | 
				
			||||||
 | 
					                    currentPosition[1] = min(weather.calculateGS(currentPosition[0], currentIAS, currentHeading), currentPosition[1])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    arrivalRoute.append(ArrivalWaypoint(waypoint = star.Route[nextWaypointIndex], trackmiles = arrivalRoute[-1].Trackmiles + distanceToWaypoint))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return timedelta(seconds = flightTimeSeconds), trackmiles, arrivalRoute, timedelta(seconds = flightTimeOnStarSeconds)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def __init__(self, inbound : Inbound, referenceTime : datetime, weatherModel : WeatherModel,
 | 
				
			||||||
 | 
					                 airportConfig : Airport, sequencingConfig : AirportSequencing):
 | 
				
			||||||
 | 
					        self.PredictedDistanceToIAF = inbound.Report.distanceToIAF
 | 
				
			||||||
 | 
					        self.PredictedCoordinate = [ inbound.CurrentPosition.latitude, inbound.CurrentPosition.longitude ]
 | 
				
			||||||
 | 
					        self.PredictionTime = referenceTime
 | 
				
			||||||
 | 
					        self.ArrivalCandidates = None
 | 
				
			||||||
 | 
					        self.Inbound = inbound
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if None == referenceTime or None == sequencingConfig:
 | 
				
			||||||
 | 
					            return
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # predict the distance to IAF
 | 
				
			||||||
 | 
					        timePrediction = (referenceTime - inbound.ReportTime).total_seconds()
 | 
				
			||||||
 | 
					        if 0 != timePrediction and 0 != len(sequencingConfig.ActiveArrivalRunways):
 | 
				
			||||||
 | 
					            # calculate current motion information
 | 
				
			||||||
 | 
					            course = weatherModel.estimateCourse(inbound.Report.dynamics.altitude, inbound.Report.dynamics.groundSpeed, inbound.Report.dynamics.heading)
 | 
				
			||||||
 | 
					            tempWaypoint = Waypoint(longitude = inbound.CurrentPosition.longitude, latitude = inbound.CurrentPosition.latitude)
 | 
				
			||||||
 | 
					            gs = inbound.Report.dynamics.groundSpeed * 0.514444 # ground speed in m/s
 | 
				
			||||||
 | 
					            distance = gs * timePrediction
 | 
				
			||||||
 | 
					            prediction = tempWaypoint.project(course, distance)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            # calculate the bearing between the current position and the IAF
 | 
				
			||||||
 | 
					            star = Node.findArrivalRoute(inbound.Report.initialApproachFix, sequencingConfig.ActiveArrivalRunways[0].Runway, airportConfig.GngData)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            # calculate the distance based on the flown distance and update the predicted distance
 | 
				
			||||||
 | 
					            if None != star:
 | 
				
			||||||
 | 
					                bearing = Waypoint(longitude = prediction[1], latitude = prediction[0]).bearing(star.Route[0])
 | 
				
			||||||
 | 
					                correctedDistance = math.cos(abs(bearing - course)) * distance * 0.000539957
 | 
				
			||||||
 | 
					                self.PredictedDistanceToIAF -= correctedDistance
 | 
				
			||||||
 | 
					                if 0.0 > self.PredictedDistanceToIAF:
 | 
				
			||||||
 | 
					                    self.PredictedDistanceToIAF = 0.0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                self.PredictedCoordinate = prediction
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        setEnrouteTime = None == self.Inbound.EnrouteArrivalTime
 | 
				
			||||||
 | 
					        self.ArrivalCandidates = {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # calculate the timings for the different arrival runways
 | 
				
			||||||
 | 
					        for identifier in sequencingConfig.ActiveArrivalRunways:
 | 
				
			||||||
 | 
					            star = Node.findArrivalRoute(self.Inbound.Report.initialApproachFix, identifier.Runway, airportConfig.GngData)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if None != star:
 | 
				
			||||||
 | 
					                flightTime, trackmiles, arrivalRoute, flightTimeOnStar = self.arrivalEstimation(identifier.Runway, star, weatherModel)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                # use the the distance to the IAF for optimizations
 | 
				
			||||||
 | 
					                timeUntilIAF = flightTime - flightTimeOnStar
 | 
				
			||||||
 | 
					                if 0.0 > timeUntilIAF.total_seconds():
 | 
				
			||||||
 | 
					                    timeUntilIAF = timedelta(seconds = 0)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                # the best TTL is the longest path with the slowest speed
 | 
				
			||||||
 | 
					                ttgMax = 60
 | 
				
			||||||
 | 
					                ttgRatio = 0.05
 | 
				
			||||||
 | 
					                if star.Name in airportConfig.OptimizationParameters:
 | 
				
			||||||
 | 
					                    ttgMax = airportConfig.OptimizationParameters[star.Name][0]
 | 
				
			||||||
 | 
					                    ttgRatio = airportConfig.OptimizationParameters[star.Name][1]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                ttg = timedelta(seconds = timeUntilIAF.total_seconds() * ttgRatio)
 | 
				
			||||||
 | 
					                if (ttg.total_seconds() > ttgMax):
 | 
				
			||||||
 | 
					                    ttg = timedelta(seconds = ttgMax)
 | 
				
			||||||
 | 
					                if None == self.Inbound.MaximumTimeToGain or ttg > self.Inbound.MaximumTimeToGain:
 | 
				
			||||||
 | 
					                    self.Inbound.MaximumTimeToGain = ttg
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                ita = self.Inbound.ReportTime + flightTime
 | 
				
			||||||
 | 
					                earliest = ita - self.Inbound.MaximumTimeToGain
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                self.ArrivalCandidates[identifier.Runway.Name] = ArrivalData(star = star, ita = earliest, route = arrivalRoute,
 | 
				
			||||||
 | 
					                                                                             trackmiles = trackmiles)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                if True == setEnrouteTime and (None == self.Inbound.EnrouteArrivalTime or ita < self.Inbound.EnrouteArrivalTime):
 | 
				
			||||||
 | 
					                    self.Inbound.EnrouteArrivalTime = ita
 | 
				
			||||||
							
								
								
									
										208
									
								
								aman/sys/aco/RunwayManager.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										208
									
								
								aman/sys/aco/RunwayManager.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,208 @@
 | 
				
			|||||||
 | 
					#!/usr/bin/env python
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import copy
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from datetime import datetime, timedelta
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from aman.config.RunwaySequencing import RunwayAssignmentType
 | 
				
			||||||
 | 
					from aman.sys.aco.Configuration import Configuration
 | 
				
			||||||
 | 
					from aman.sys.aco.Constraints import SpacingConstraints
 | 
				
			||||||
 | 
					from aman.sys.aco.Node import Node
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class RunwayManager:
 | 
				
			||||||
 | 
					    def __init__(self, configuration : Configuration):
 | 
				
			||||||
 | 
					        self.Spacings = SpacingConstraints()
 | 
				
			||||||
 | 
					        self.Configuration = configuration
 | 
				
			||||||
 | 
					        self.RunwayInbounds = copy.deepcopy(configuration.PreceedingRunwayInbounds)
 | 
				
			||||||
 | 
					        self.IafInbounds = copy.deepcopy(configuration.PreceedingIafInbounds)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def calculateEarliestArrivalTime(self, runway : str, node : Node, earliestArrivalTime : datetime):
 | 
				
			||||||
 | 
					        constrainedETA = None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if None != self.RunwayInbounds[runway]:
 | 
				
			||||||
 | 
					            # get the WTC based ETA
 | 
				
			||||||
 | 
					            if None == self.RunwayInbounds[runway].Inbound.WTC or None == node.Inbound.WTC:
 | 
				
			||||||
 | 
					                spacingWTC = 3
 | 
				
			||||||
 | 
					            else:
 | 
				
			||||||
 | 
					                if self.RunwayInbounds[runway].Inbound.WTC not in self.Spacings.WtcSpacing:
 | 
				
			||||||
 | 
					                    spacingWTC = 3
 | 
				
			||||||
 | 
					                elif node.Inbound.WTC not in self.Spacings.WtcSpacing[self.RunwayInbounds[runway].Inbound.WTC]:
 | 
				
			||||||
 | 
					                    spacingWTC = self.Spacings.WtcSpacing[self.RunwayInbounds[runway].Inbound.WTC]['L']
 | 
				
			||||||
 | 
					                else:
 | 
				
			||||||
 | 
					                    spacingWTC = self.Spacings.WtcSpacing[self.RunwayInbounds[runway].Inbound.WTC][node.Inbound.WTC]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            # get the runway time spacing
 | 
				
			||||||
 | 
					            spacingRunway = self.Configuration.RunwayConstraints.findRunway(runway).Spacing
 | 
				
			||||||
 | 
					            constrainedETA = self.RunwayInbounds[runway].Inbound.PlannedArrivalTime + timedelta(minutes = max(spacingWTC, spacingRunway) / (node.Inbound.PerformanceData.SpeedApproach / 60))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # calculate the arrival times for the dependent inbounds
 | 
				
			||||||
 | 
					        for dependentRunway in self.Configuration.RunwayConstraints.findDependentRunways(runway):
 | 
				
			||||||
 | 
					            if None != self.RunwayInbounds[dependentRunway.Runway.Name]:
 | 
				
			||||||
 | 
					                candidate = self.RunwayInbounds[dependentRunway.Runway.Name].Inbound.PlannedArrivalTime + timedelta(minutes = 3 / (node.Inbound.PerformanceData.SpeedApproach / 60))
 | 
				
			||||||
 | 
					                if None == constrainedETA or candidate > constrainedETA:
 | 
				
			||||||
 | 
					                    constrainedETA = candidate
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if None == constrainedETA:
 | 
				
			||||||
 | 
					            eta = max(node.ArrivalCandidates[runway].InitialArrivalTime, earliestArrivalTime)
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            eta = max(node.ArrivalCandidates[runway].InitialArrivalTime, max(constrainedETA, earliestArrivalTime))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return eta, eta - node.ArrivalCandidates[runway].InitialArrivalTime
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def selectShallShouldMayArrivalRunway(self, node : Node, runways, earliestArrivalTime : datetime):
 | 
				
			||||||
 | 
					        candidate = None
 | 
				
			||||||
 | 
					        delay = None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        for runway in runways:
 | 
				
			||||||
 | 
					            eta, _ = self.calculateEarliestArrivalTime(runway.Runway.Name, node, earliestArrivalTime)
 | 
				
			||||||
 | 
					            if None == delay:
 | 
				
			||||||
 | 
					                delay = eta - node.ArrivalCandidates[runway.Runway.Name].InitialArrivalTime
 | 
				
			||||||
 | 
					                candidate = runway
 | 
				
			||||||
 | 
					            elif delay > (eta - node.ArrivalCandidates[runway.Runway.Name].InitialArrivalTime):
 | 
				
			||||||
 | 
					                delay = eta- node.ArrivalCandidates[runway.Runway.Name].InitialArrivalTime
 | 
				
			||||||
 | 
					                candidate = runway
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return candidate
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def executeShallShouldMayAssignment(self, node : Node, earliestArrivalTime : datetime):
 | 
				
			||||||
 | 
					        shallRunways = []
 | 
				
			||||||
 | 
					        shouldRunways = []
 | 
				
			||||||
 | 
					        mayRunways = []
 | 
				
			||||||
 | 
					        expectedRunway = None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        for runway in self.Configuration.RunwayConstraints.ActiveArrivalRunways:
 | 
				
			||||||
 | 
					            # test the shall assignments
 | 
				
			||||||
 | 
					            if RunwayAssignmentType.AircraftType in runway.ShallAssignments:
 | 
				
			||||||
 | 
					                if node.Inbound.Report.aircraft.type in runway.ShallAssignments[RunwayAssignmentType.AircraftType]:
 | 
				
			||||||
 | 
					                    shallRunways.append(runway)
 | 
				
			||||||
 | 
					                    expectedRunway = runway.Runway.Name
 | 
				
			||||||
 | 
					            if RunwayAssignmentType.GateAssignment in runway.ShallAssignments:
 | 
				
			||||||
 | 
					                if node.Inbound.Report.plannedGate in runway.ShallAssignments[RunwayAssignmentType.GateAssignment]:
 | 
				
			||||||
 | 
					                    shallRunways.append(runway)
 | 
				
			||||||
 | 
					                    expectedRunway = runway.Runway.Name
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            # test the should assignments
 | 
				
			||||||
 | 
					            if RunwayAssignmentType.AircraftType in runway.ShouldAssignments:
 | 
				
			||||||
 | 
					                if node.Inbound.Report.aircraft.type in runway.ShouldAssignments[RunwayAssignmentType.AircraftType]:
 | 
				
			||||||
 | 
					                    shouldRunways.append(runway)
 | 
				
			||||||
 | 
					                    expectedRunway = runway.Runway.Name
 | 
				
			||||||
 | 
					            if RunwayAssignmentType.GateAssignment in runway.ShouldAssignments:
 | 
				
			||||||
 | 
					                if node.Inbound.Report.plannedGate in runway.ShouldAssignments[RunwayAssignmentType.GateAssignment]:
 | 
				
			||||||
 | 
					                    shouldRunways.append(runway)
 | 
				
			||||||
 | 
					                    expectedRunway = runway.Runway.Name
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            # test the may assignments
 | 
				
			||||||
 | 
					            if RunwayAssignmentType.AircraftType in runway.MayAssignments:
 | 
				
			||||||
 | 
					                if node.Inbound.Report.aircraft.type in runway.MayAssignments[RunwayAssignmentType.AircraftType]:
 | 
				
			||||||
 | 
					                    eta, _ = self.calculateEarliestArrivalTime(runway.Runway.Name, node, earliestArrivalTime)
 | 
				
			||||||
 | 
					                    if (eta - node.ArrivalCandidates[runway.Runway.Name].InitialArrivalTime) <= self.Configuration.AirportConfiguration.MaxDelayMay:
 | 
				
			||||||
 | 
					                        mayRunways.append(runway)
 | 
				
			||||||
 | 
					                        expectedRunway = runway.Runway.Name
 | 
				
			||||||
 | 
					            if RunwayAssignmentType.GateAssignment in runway.MayAssignments:
 | 
				
			||||||
 | 
					                if node.Inbound.Report.plannedGate in runway.MayAssignments[RunwayAssignmentType.GateAssignment]:
 | 
				
			||||||
 | 
					                    eta, _ = self.calculateEarliestArrivalTime(runway.Runway.Name, node, earliestArrivalTime)
 | 
				
			||||||
 | 
					                    if (eta - node.ArrivalCandidates[runway.Runway.Name].InitialArrivalTime) <= self.Configuration.AirportConfiguration.MaxDelayMay:
 | 
				
			||||||
 | 
					                        mayRunways.append(runway)
 | 
				
			||||||
 | 
					                        expectedRunway = runway.Runway.Name
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        runway = self.selectShallShouldMayArrivalRunway(node, shallRunways, earliestArrivalTime)
 | 
				
			||||||
 | 
					        if None != runway:
 | 
				
			||||||
 | 
					            return 'shall', expectedRunway, [ runway ]
 | 
				
			||||||
 | 
					        runway = self.selectShallShouldMayArrivalRunway(node, shouldRunways, earliestArrivalTime)
 | 
				
			||||||
 | 
					        if None != runway:
 | 
				
			||||||
 | 
					            return 'should', expectedRunway, [ runway ]
 | 
				
			||||||
 | 
					        runway = self.selectShallShouldMayArrivalRunway(node, mayRunways, earliestArrivalTime)
 | 
				
			||||||
 | 
					        if None != runway:
 | 
				
			||||||
 | 
					            return 'may', expectedRunway, [ runway ]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return 'other', None, self.Configuration.RunwayConstraints.ActiveArrivalRunways
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def selectArrivalRunway(self, node : Node, earliestArrivalTime : datetime):
 | 
				
			||||||
 | 
					        availableRunways = self.Configuration.RunwayConstraints.ActiveArrivalRunways
 | 
				
			||||||
 | 
					        if 0 == len(availableRunways):
 | 
				
			||||||
 | 
					            return None, None, None, None, None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        expectedRunway = None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if True == self.Configuration.RunwayConstraints.UseShallShouldMay and None == node.Inbound.RequestedRunway:
 | 
				
			||||||
 | 
					            type, expectedRunway, availableRunways = self.executeShallShouldMayAssignment(node, earliestArrivalTime)
 | 
				
			||||||
 | 
					        elif None != node.Inbound.RequestedRunway:
 | 
				
			||||||
 | 
					            for runway in availableRunways:
 | 
				
			||||||
 | 
					                if node.Inbound.RequestedRunway == runway.Runway.Name:
 | 
				
			||||||
 | 
					                    availableRunways = [ runway ]
 | 
				
			||||||
 | 
					                    type = 'other'
 | 
				
			||||||
 | 
					                    break
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if 0 == len(availableRunways):
 | 
				
			||||||
 | 
					            runway = self.Configuration.RunwayConstraints.ActiveArrivalRunways[0]
 | 
				
			||||||
 | 
					            eta, delta = self.calculateEarliestArrivalTime(runway.Runway.Name, node, earliestArrivalTime)
 | 
				
			||||||
 | 
					            return 'other', None, runway, eta, delta
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # start with the beginning
 | 
				
			||||||
 | 
					        selectedRunway = None
 | 
				
			||||||
 | 
					        lostTime = None
 | 
				
			||||||
 | 
					        eta = None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # get the runway with the earliest ETA
 | 
				
			||||||
 | 
					        for runway in availableRunways:
 | 
				
			||||||
 | 
					            candidate, delta = self.calculateEarliestArrivalTime(runway.Runway.Name, node, earliestArrivalTime)
 | 
				
			||||||
 | 
					            if None == eta or eta > candidate:
 | 
				
			||||||
 | 
					                selectedRunway = runway.Runway
 | 
				
			||||||
 | 
					                lostTime = delta
 | 
				
			||||||
 | 
					                eta = candidate
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # find the corresponding IAF
 | 
				
			||||||
 | 
					        iaf = node.ArrivalCandidates[selectedRunway.Name].ArrivalRoute[0].Waypoint.Name
 | 
				
			||||||
 | 
					        if iaf in self.IafInbounds:
 | 
				
			||||||
 | 
					            delta = 100000.0
 | 
				
			||||||
 | 
					            targetLevel = None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            # find the planned level
 | 
				
			||||||
 | 
					            for level in self.IafInbounds[iaf]:
 | 
				
			||||||
 | 
					                difference = abs(level - node.ArrivalCandidates[selectedRunway.Name].ArrivalRoute[0].Altitude)
 | 
				
			||||||
 | 
					                if difference < delta:
 | 
				
			||||||
 | 
					                    delta = difference
 | 
				
			||||||
 | 
					                    targetLevel = level
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if targetLevel in self.IafInbounds[iaf]:
 | 
				
			||||||
 | 
					                # check if we have to lose time to ensure the IAF spacing
 | 
				
			||||||
 | 
					                # the function assumes that model allows only TTG during flight to IAF
 | 
				
			||||||
 | 
					                if None != self.IafInbounds[iaf][targetLevel]:
 | 
				
			||||||
 | 
					                    if None != self.IafInbounds[iaf][targetLevel].Inbound.PlannedArrivalRoute:
 | 
				
			||||||
 | 
					                        # ETA at IAF of preceeding traffic
 | 
				
			||||||
 | 
					                        plannedDelta = self.IafInbounds[iaf][targetLevel].Inbound.PlannedArrivalTime - self.IafInbounds[iaf][targetLevel].Inbound.EnrouteArrivalTime
 | 
				
			||||||
 | 
					                        iafETAPreceeding = self.IafInbounds[iaf][targetLevel].Inbound.PlannedArrivalRoute[0].ETA + plannedDelta
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                        # ETA at IAF of current inbound
 | 
				
			||||||
 | 
					                        plannedDelta = eta - node.Inbound.EnrouteArrivalTime
 | 
				
			||||||
 | 
					                        iafETACurrent = node.ArrivalCandidates[selectedRunway.Name].ArrivalRoute[0].ETA
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                        # required time delte to ensure IAF spacing
 | 
				
			||||||
 | 
					                        timeSpacing = timedelta(hours = self.Configuration.AirportConfiguration.IafSpacing / node.ArrivalCandidates[selectedRunway.Name].ArrivalRoute[0].GroundSpeed)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                        # we are too close to preceeding traffic
 | 
				
			||||||
 | 
					                        currentTimeSpacing = iafETACurrent - iafETAPreceeding
 | 
				
			||||||
 | 
					                        if timeSpacing > currentTimeSpacing:
 | 
				
			||||||
 | 
					                            eta = eta + (timeSpacing - currentTimeSpacing)
 | 
				
			||||||
 | 
					                            lostTime += (timeSpacing - currentTimeSpacing)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return type, expectedRunway, selectedRunway, eta, lostTime
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def registerNode(self, node : Node, runway : str):
 | 
				
			||||||
 | 
					        self.RunwayInbounds[runway] = node
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					         # find the corresponding IAF
 | 
				
			||||||
 | 
					        iaf = node.ArrivalCandidates[runway].ArrivalRoute[0].Waypoint.Name
 | 
				
			||||||
 | 
					        if iaf in self.IafInbounds:
 | 
				
			||||||
 | 
					            delta = 100000.0
 | 
				
			||||||
 | 
					            targetLevel = None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            # find the planned level
 | 
				
			||||||
 | 
					            for level in self.IafInbounds[iaf]:
 | 
				
			||||||
 | 
					                difference = abs(level - node.ArrivalCandidates[runway].ArrivalRoute[0].Altitude)
 | 
				
			||||||
 | 
					                if difference < delta:
 | 
				
			||||||
 | 
					                    delta = difference
 | 
				
			||||||
 | 
					                    targetLevel = level
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if targetLevel in self.IafInbounds[iaf]:
 | 
				
			||||||
 | 
					                self.IafInbounds[iaf][targetLevel] = node
 | 
				
			||||||
							
								
								
									
										0
									
								
								aman/sys/aco/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								aman/sys/aco/__init__.py
									
									
									
									
									
										Normal file
									
								
							@@ -1,7 +1,9 @@
 | 
				
			|||||||
#!/usr/bin/env python
 | 
					#!/usr/bin/env python
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import argparse
 | 
					import argparse
 | 
				
			||||||
 | 
					from datetime import datetime
 | 
				
			||||||
import os
 | 
					import os
 | 
				
			||||||
 | 
					import sys
 | 
				
			||||||
from typing import Tuple
 | 
					from typing import Tuple
 | 
				
			||||||
import zmq.auth
 | 
					import zmq.auth
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -10,14 +12,12 @@ import zmq.auth
 | 
				
			|||||||
# @return The public and private key tuple
 | 
					# @return The public and private key tuple
 | 
				
			||||||
def KeyPairCreator(directory: str, server: bool) -> Tuple[str, str]:
 | 
					def KeyPairCreator(directory: str, server: bool) -> Tuple[str, str]:
 | 
				
			||||||
    if not server:
 | 
					    if not server:
 | 
				
			||||||
        print('Creating a new pair for a client...')
 | 
					 | 
				
			||||||
        target = 'client'
 | 
					        target = 'client'
 | 
				
			||||||
    else:
 | 
					    else:
 | 
				
			||||||
        print('Creating a new pair for the server...')
 | 
					 | 
				
			||||||
        target = 'server'
 | 
					        target = 'server'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public, private = zmq.auth.create_certificates(directory, target)
 | 
					    public, private = zmq.auth.create_certificates(directory, target)
 | 
				
			||||||
    return (public, private)
 | 
					    return public, private
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def str2bool(value):
 | 
					def str2bool(value):
 | 
				
			||||||
    if isinstance(value, bool):
 | 
					    if isinstance(value, bool):
 | 
				
			||||||
@@ -29,16 +29,71 @@ def str2bool(value):
 | 
				
			|||||||
    else:
 | 
					    else:
 | 
				
			||||||
        raise argparse.ArgumentTypeError('Boolean value expected')
 | 
					        raise argparse.ArgumentTypeError('Boolean value expected')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def findIdentificationKey(path, publicKey : bool):
 | 
				
			||||||
 | 
					    if True == publicKey:
 | 
				
			||||||
 | 
					        identifier = 'public-key = '
 | 
				
			||||||
 | 
					    else:
 | 
				
			||||||
 | 
					        identifier = 'secret-key = '
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    with open(path) as file:
 | 
				
			||||||
 | 
					        key = ''
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        for line in file:
 | 
				
			||||||
 | 
					            if identifier in line:
 | 
				
			||||||
 | 
					                elements = line.split('=')
 | 
				
			||||||
 | 
					                for idx in range(1, len(elements)):
 | 
				
			||||||
 | 
					                    if 0 == len(key):
 | 
				
			||||||
 | 
					                        key = elements[idx][2:-1]
 | 
				
			||||||
 | 
					                        key = key + elements[idx][-1]
 | 
				
			||||||
 | 
					                    else:
 | 
				
			||||||
 | 
					                        key = key + '=' + elements[idx]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                return key[0:-2]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
if __name__ == '__main__':
 | 
					if __name__ == '__main__':
 | 
				
			||||||
    # create the commandline parser
 | 
					    # create the commandline parser
 | 
				
			||||||
    parser = argparse.ArgumentParser(description='Create a new key-value pair')
 | 
					    parser = argparse.ArgumentParser(description='Create a new key-value pair')
 | 
				
			||||||
    parser.add_argument('directory', help='Directory where to store the key pair')
 | 
					    parser.add_argument('--directory', type=str, help='Directory where to store the key pair')
 | 
				
			||||||
 | 
					    parser.add_argument('--publickey', nargs='?', type=str, default=os.getcwd(), help='Full path to the public key of the server')
 | 
				
			||||||
    parser.add_argument('--server', default=False, action='store_true', help="Creates server key pair")
 | 
					    parser.add_argument('--server', default=False, action='store_true', help="Creates server key pair")
 | 
				
			||||||
    args = parser.parse_args()
 | 
					    args = parser.parse_args()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # validate the arguments
 | 
				
			||||||
 | 
					    if False == args.server and not os.path.exists(args.publickey):
 | 
				
			||||||
 | 
					        sys.stderr.write('The public key of the server cannot be found')
 | 
				
			||||||
 | 
					        sys.exit(-1)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # create the directory if it does not exist
 | 
					    # create the directory if it does not exist
 | 
				
			||||||
    if not os.path.exists(args.directory):
 | 
					    if not os.path.exists(args.directory):
 | 
				
			||||||
        os.makedirs(args.directory)
 | 
					        os.makedirs(args.directory)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # create the keys
 | 
					    # create the keys
 | 
				
			||||||
    KeyPairCreator(args.directory, args.server)
 | 
					    _, private = KeyPairCreator(args.directory, args.server)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if False == args.server:
 | 
				
			||||||
 | 
					        publicServer  = findIdentificationKey(args.publickey, True)
 | 
				
			||||||
 | 
					        publicClient  = findIdentificationKey(private, True)
 | 
				
			||||||
 | 
					        privateClient = findIdentificationKey(private, False)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if None == publicServer:
 | 
				
			||||||
 | 
					            sys.stderr.write('The public key of the server cannot be found in the defined file')
 | 
				
			||||||
 | 
					            sys.exit(-1)
 | 
				
			||||||
 | 
					        if None == publicClient:
 | 
				
			||||||
 | 
					            sys.stderr.write('Unable to extract the created public key')
 | 
				
			||||||
 | 
					            sys.exit(-1)
 | 
				
			||||||
 | 
					        if None == privateClient:
 | 
				
			||||||
 | 
					            sys.stderr.write('Unable to extract the created private key')
 | 
				
			||||||
 | 
					            sys.exit(-1)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # rename keys
 | 
				
			||||||
 | 
					        timestamp = str(datetime.now(tz=None))
 | 
				
			||||||
 | 
					        timestamp = timestamp.replace(' ', '_')
 | 
				
			||||||
 | 
					        timestamp = timestamp.replace(':', '-')
 | 
				
			||||||
 | 
					        os.rename(os.path.join(args.directory, 'client.key'), os.path.join(args.directory, timestamp + '.key'))
 | 
				
			||||||
 | 
					        os.rename(os.path.join(args.directory, 'client.key_secret'), os.path.join(args.directory, timestamp + '.key_secret'))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        print(publicServer)
 | 
				
			||||||
 | 
					        print(publicClient)
 | 
				
			||||||
 | 
					        print(privateClient)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -118,13 +118,9 @@ if __name__ == '__main__':
 | 
				
			|||||||
    print('Found ' + str(len(links)) + ' aircrafts')
 | 
					    print('Found ' + str(len(links)) + ' aircrafts')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    aircrafts = []
 | 
					    aircrafts = []
 | 
				
			||||||
    parsed = 0
 | 
					 | 
				
			||||||
    for link in links:
 | 
					    for link in links:
 | 
				
			||||||
        valid, aircraft = parsePerformanceData(link)
 | 
					        valid, aircraft = parsePerformanceData(link)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        parsed += 1
 | 
					 | 
				
			||||||
        print('Parsed ' + str(parsed) + ' of ' + str(len(links)), end='\r')
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if False == valid:
 | 
					        if False == valid:
 | 
				
			||||||
            print('Unable to find performance data for ' + link)
 | 
					            print('Unable to find performance data for ' + link)
 | 
				
			||||||
            continue
 | 
					            continue
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										30
									
								
								aman/types/ArrivalData.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								aman/types/ArrivalData.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,30 @@
 | 
				
			|||||||
 | 
					#!/usr/bin/env python
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from datetime import datetime, timedelta
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from aman.types.ArrivalRoute import ArrivalRoute
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class ArrivalData:
 | 
				
			||||||
 | 
					    def __init__(self, **kargs):
 | 
				
			||||||
 | 
					        self.Star = None
 | 
				
			||||||
 | 
					        self.InitialArrivalTime = None
 | 
				
			||||||
 | 
					        self.ArrivalRoute = None
 | 
				
			||||||
 | 
					        self.Trackmiles = None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        for key, value in kargs.items():
 | 
				
			||||||
 | 
					            if 'star' == key:
 | 
				
			||||||
 | 
					                if True == isinstance(value, ArrivalRoute):
 | 
				
			||||||
 | 
					                    self.Star = value
 | 
				
			||||||
 | 
					                else:
 | 
				
			||||||
 | 
					                    raise Exception('Invalid type for star')
 | 
				
			||||||
 | 
					            elif 'ita' == key:
 | 
				
			||||||
 | 
					                if True == isinstance(value, datetime):
 | 
				
			||||||
 | 
					                    self.InitialArrivalTime = value
 | 
				
			||||||
 | 
					                else:
 | 
				
			||||||
 | 
					                    raise Exception('Invalid type for ita')
 | 
				
			||||||
 | 
					            elif 'route' == key:
 | 
				
			||||||
 | 
					                self.ArrivalRoute = value
 | 
				
			||||||
 | 
					            elif 'trackmiles' == key:
 | 
				
			||||||
 | 
					                self.Trackmiles = value
 | 
				
			||||||
 | 
					            else:
 | 
				
			||||||
 | 
					                raise Exception('Unknown key: ' + key)
 | 
				
			||||||
@@ -2,10 +2,10 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
class ArrivalRoute:
 | 
					class ArrivalRoute:
 | 
				
			||||||
    def __init__(self, name : str, runway : str, waypoints : list):
 | 
					    def __init__(self, name : str, runway : str, waypoints : list):
 | 
				
			||||||
        self.name = name
 | 
					        self.Name = name
 | 
				
			||||||
        self.runway = runway
 | 
					        self.Runway = runway
 | 
				
			||||||
        self.iaf = waypoints[0]
 | 
					        self.Iaf = waypoints[0]
 | 
				
			||||||
        self.route = waypoints
 | 
					        self.Route = waypoints
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def __str__(self):
 | 
					    def __str__(self):
 | 
				
			||||||
        return 'Name: ' + self.name + ', IAF: ' + self.iaf.name + ', RWY: ' + self.runway
 | 
					        return 'Name: ' + self.Name + ', IAF: ' + self.Iaf.Name + ', RWY: ' + self.Runway
 | 
				
			||||||
							
								
								
									
										34
									
								
								aman/types/ArrivalWaypoint.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								aman/types/ArrivalWaypoint.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,34 @@
 | 
				
			|||||||
 | 
					#!/usr/bin/env python
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class ArrivalWaypoint():
 | 
				
			||||||
 | 
					    def __init__(self, **kwargs):
 | 
				
			||||||
 | 
					        self.Waypoint = None
 | 
				
			||||||
 | 
					        self.FlightTime = None
 | 
				
			||||||
 | 
					        self.Trackmiles = None
 | 
				
			||||||
 | 
					        self.IndicatedAirspeed = None
 | 
				
			||||||
 | 
					        self.GroundSpeed = None
 | 
				
			||||||
 | 
					        self.Altitude = None
 | 
				
			||||||
 | 
					        self.ETA = None
 | 
				
			||||||
 | 
					        self.PTA = None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        for key, value in kwargs.items():
 | 
				
			||||||
 | 
					            if 'waypoint' == key.lower():
 | 
				
			||||||
 | 
					                self.Waypoint = value
 | 
				
			||||||
 | 
					            elif 'flighttime' == key.lower():
 | 
				
			||||||
 | 
					                self.FlightTime = value
 | 
				
			||||||
 | 
					            elif 'eta' == key.lower():
 | 
				
			||||||
 | 
					                self.ETA = value
 | 
				
			||||||
 | 
					            elif 'pta' == key.lower():
 | 
				
			||||||
 | 
					                self.PTA = value
 | 
				
			||||||
 | 
					            elif 'trackmiles' == key.lower():
 | 
				
			||||||
 | 
					                self.Trackmiles = value
 | 
				
			||||||
 | 
					            elif 'altitude' == key.lower():
 | 
				
			||||||
 | 
					                self.Altitude = value
 | 
				
			||||||
 | 
					            elif 'groundspeed' == key.lower():
 | 
				
			||||||
 | 
					                self.GroundSpeed = value
 | 
				
			||||||
 | 
					            elif 'indicated' == key.lower():
 | 
				
			||||||
 | 
					                self.IndicatedAirspeed = value
 | 
				
			||||||
 | 
					            else:
 | 
				
			||||||
 | 
					                raise Exception('Invalid constructor argument: ' + key)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
@@ -1,7 +1,45 @@
 | 
				
			|||||||
#!/usr/bin/env python
 | 
					#!/usr/bin/env python
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import pytz
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from datetime import datetime
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from aman.com import AircraftReport_pb2
 | 
					from aman.com import AircraftReport_pb2
 | 
				
			||||||
 | 
					from aman.sys.WeatherModel import WeatherModel
 | 
				
			||||||
 | 
					from aman.types.PerformanceData import PerformanceData
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class Inbound:
 | 
					class Inbound:
 | 
				
			||||||
    def __init__(self, report : AircraftReport_pb2.AircraftReport):
 | 
					    def __init__(self, report : AircraftReport_pb2.AircraftReport, performanceData : PerformanceData):
 | 
				
			||||||
        self.report = report
 | 
					        self.Report = report
 | 
				
			||||||
 | 
					        self.Callsign = report.aircraft.callsign
 | 
				
			||||||
 | 
					        self.CurrentPosition = report.position
 | 
				
			||||||
 | 
					        self.ReportTime = datetime.strptime(report.reportTime + '+0000', '%Y%m%d%H%M%S%z').replace(tzinfo = pytz.UTC)
 | 
				
			||||||
 | 
					        self.EnrouteArrivalTime = None
 | 
				
			||||||
 | 
					        self.InitialArrivalTime = None
 | 
				
			||||||
 | 
					        self.RequestedRunway = None
 | 
				
			||||||
 | 
					        self.MaximumTimeToGain = None
 | 
				
			||||||
 | 
					        self.PlannedArrivalTime = None
 | 
				
			||||||
 | 
					        self.PlannedRunway = None
 | 
				
			||||||
 | 
					        self.PlannedStar = None
 | 
				
			||||||
 | 
					        self.PlannedArrivalRoute = None
 | 
				
			||||||
 | 
					        self.PlannedTrackmiles = None
 | 
				
			||||||
 | 
					        self.FixedSequence = False
 | 
				
			||||||
 | 
					        self.ExpectedRunway = None
 | 
				
			||||||
 | 
					        self.AssignmentMode = None
 | 
				
			||||||
 | 
					        self.HasValidSequence = False
 | 
				
			||||||
 | 
					        self.WTC = None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # analyze the WTC
 | 
				
			||||||
 | 
					        wtc = report.aircraft.wtc.upper()
 | 
				
			||||||
 | 
					        if 'L' == wtc or 'M' == wtc or 'H' == wtc or 'J' == wtc:
 | 
				
			||||||
 | 
					            self.WTC = wtc
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # analyze the requested runway
 | 
				
			||||||
 | 
					        if '' != report.requestedRunway:
 | 
				
			||||||
 | 
					            self.RequestedRunway = report.requestedRunway
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # search performance data -> fallback to A320
 | 
				
			||||||
 | 
					        if self.Report.aircraft.type in performanceData.Aircrafts:
 | 
				
			||||||
 | 
					            self.PerformanceData = performanceData.Aircrafts[self.Report.aircraft.type]
 | 
				
			||||||
 | 
					        if None == self.PerformanceData:
 | 
				
			||||||
 | 
					            self.PerformanceData = performanceData.Aircrafts['A320']
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -2,17 +2,29 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
class PerformanceData:
 | 
					class PerformanceData:
 | 
				
			||||||
    def __init__(self, icao : str):
 | 
					    def __init__(self, icao : str):
 | 
				
			||||||
        self.icao = icao
 | 
					        self.Icao = icao
 | 
				
			||||||
        self.speedAboveFL240 = 0.0
 | 
					        self.SpeedAboveFL240 = 0.0
 | 
				
			||||||
        self.speedAboveFL100 = 0.0
 | 
					        self.SpeedAboveFL100 = 0.0
 | 
				
			||||||
        self.speedBelowFL100 = 0.0
 | 
					        self.SpeedBelowFL100 = 0.0
 | 
				
			||||||
        self.speedApproach = 0.0
 | 
					        self.SpeedApproach = 0.0
 | 
				
			||||||
        self.rodAboveFL240 = 0.0
 | 
					        self.RodAboveFL240 = 0.0
 | 
				
			||||||
        self.rodAboveFL100 = 0.0
 | 
					        self.RodAboveFL100 = 0.0
 | 
				
			||||||
        self.rodBelowFL100 = 2000.0
 | 
					        self.RodBelowFL100 = 2000.0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def ias(self, altitude, distance):
 | 
				
			||||||
 | 
					        if 24000 < altitude:
 | 
				
			||||||
 | 
					            return self.SpeedAboveFL240
 | 
				
			||||||
 | 
					        elif 10000 < altitude:
 | 
				
			||||||
 | 
					            return self.SpeedAboveFL100
 | 
				
			||||||
 | 
					        elif 10000 >= altitude and 5 < distance:
 | 
				
			||||||
 | 
					            return self.SpeedBelowFL100
 | 
				
			||||||
 | 
					        elif 5 >= distance:
 | 
				
			||||||
 | 
					            return self.SpeedApproach
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            return self.SpeedBelowFL100
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def __str__(self):
 | 
					    def __str__(self):
 | 
				
			||||||
        return 'ICAO: ' + self.icao + ', FL240: ' + str(self.rodAboveFL240) + '@' + str(self.speedAboveFL240) + \
 | 
					        return 'ICAO: ' + self.icao + ', FL240: ' + str(self.RodAboveFL240) + '@' + str(self.SpeedAboveFL240) + \
 | 
				
			||||||
               ', +FL100: ' + str(self.rodAboveFL100) + '@' + str(self.speedAboveFL100) + \
 | 
					               ', +FL100: ' + str(self.RodAboveFL100) + '@' + str(self.SpeedAboveFL100) + \
 | 
				
			||||||
               ', -FL100: ' + str(self.rodBelowFL100) + '@' + str(self.speedBelowFL100) + \
 | 
					               ', -FL100: ' + str(self.RodBelowFL100) + '@' + str(self.SpeedBelowFL100) + \
 | 
				
			||||||
               ', Vapp: ' + str(self.speedApproach)
 | 
					               ', Vapp: ' + str(self.SpeedApproach)
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										12
									
								
								aman/types/Runway.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								aman/types/Runway.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,12 @@
 | 
				
			|||||||
 | 
					#!/usr/bin/env python
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from aman.types.Waypoint import Waypoint
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class Runway:
 | 
				
			||||||
 | 
					    def __init__(self, start : Waypoint, end : Waypoint):
 | 
				
			||||||
 | 
					        self.Name = start.Name
 | 
				
			||||||
 | 
					        self.Start = start
 | 
				
			||||||
 | 
					        self.End = end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def heading(self):
 | 
				
			||||||
 | 
					        return self.Start.bearing(self.End)
 | 
				
			||||||
@@ -1,7 +1,7 @@
 | 
				
			|||||||
#!/usr/bin/env python
 | 
					#!/usr/bin/env python
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from sklearn.metrics.pairwise import haversine_distances
 | 
					 | 
				
			||||||
import numpy as np
 | 
					import numpy as np
 | 
				
			||||||
 | 
					import pyproj
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class Waypoint:
 | 
					class Waypoint:
 | 
				
			||||||
    def dms2dd(coordinate : str):
 | 
					    def dms2dd(coordinate : str):
 | 
				
			||||||
@@ -20,14 +20,59 @@ class Waypoint:
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        return dd
 | 
					        return dd
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def __init__(self, name : str, latitude : float, longitude : float):
 | 
					    def coordinateArgument(value):
 | 
				
			||||||
        self.name = name
 | 
					        if True == isinstance(value, str):
 | 
				
			||||||
        self.coordinate = np.array([ latitude, longitude ])
 | 
					            return Waypoint.dms2dd(value)
 | 
				
			||||||
 | 
					        elif True == isinstance(value, (float, int)):
 | 
				
			||||||
 | 
					            return float(value)
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            raise Exception('Invalid constructor argument')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def __init__(self, **kwargs):
 | 
				
			||||||
 | 
					        self.Name = None
 | 
				
			||||||
 | 
					        self.Coordinate = None
 | 
				
			||||||
 | 
					        self.Altitude = None
 | 
				
			||||||
 | 
					        self.Speed = None
 | 
				
			||||||
 | 
					        self.BaseTurn = False
 | 
				
			||||||
 | 
					        self.FinalTurn = False
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        latitude = None
 | 
				
			||||||
 | 
					        longitude = None
 | 
				
			||||||
 | 
					        for key, value in kwargs.items():
 | 
				
			||||||
 | 
					            if 'name' == key.lower():
 | 
				
			||||||
 | 
					                self.Name = str(value)
 | 
				
			||||||
 | 
					            elif 'latitude' == key.lower():
 | 
				
			||||||
 | 
					                latitude = Waypoint.coordinateArgument(value)
 | 
				
			||||||
 | 
					            elif 'longitude' == key.lower():
 | 
				
			||||||
 | 
					                longitude = Waypoint.coordinateArgument(value)
 | 
				
			||||||
 | 
					            elif 'altitude' == key.lower():
 | 
				
			||||||
 | 
					                self.Altitude = int(value)
 | 
				
			||||||
 | 
					            elif 'speed' == key.lower():
 | 
				
			||||||
 | 
					                self.Speed = int(value)
 | 
				
			||||||
 | 
					            elif 'base' == key:
 | 
				
			||||||
 | 
					                self.BaseTurn = bool(value)
 | 
				
			||||||
 | 
					            elif 'final' == key:
 | 
				
			||||||
 | 
					                self.FinalTurn = bool(value)
 | 
				
			||||||
 | 
					            else:
 | 
				
			||||||
 | 
					                raise Exception('Invalid constructor argument: ' + key)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if None != latitude and None != longitude:
 | 
				
			||||||
 | 
					            self.Coordinate = np.array([ latitude, longitude ])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def __str__(self):
 | 
					    def __str__(self):
 | 
				
			||||||
        return 'Name: ' + self.name + ', Lat: ' + str(self.coordinate[0]) + ', Lon: ' + str(self.coordinate[1])
 | 
					        return 'Name: ' + self.Name + ', Lat: ' + str(self.Coordinate[0]) + ', Lon: ' + str(self.Coordinate[1])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def haversine(self, other):
 | 
					    def haversine(self, other):
 | 
				
			||||||
        self_radians = [np.radians(_) for _ in self.coordinate]
 | 
					        geodesic = pyproj.Geod(ellps='WGS84')
 | 
				
			||||||
        other_radians = [np.radians(_) for _ in other.coordinate]
 | 
					        _, _, distance = geodesic.inv(self.Coordinate[1], self.Coordinate[0], other.Coordinate[1], other.Coordinate[0])
 | 
				
			||||||
        return 6371.0 * haversine_distances([self_radians, other_radians])[0][1]
 | 
					        return distance / 1000.0 * 0.539957
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def bearing(self, other):
 | 
				
			||||||
 | 
					        geodesic = pyproj.Geod(ellps='WGS84')
 | 
				
			||||||
 | 
					        forward, _, _ = geodesic.inv(self.Coordinate[1], self.Coordinate[0], other.Coordinate[1], other.Coordinate[0])
 | 
				
			||||||
 | 
					        return forward
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def project(self, bearing, distance):
 | 
				
			||||||
 | 
					        geodesic = pyproj.Geod(ellps='WGS84')
 | 
				
			||||||
 | 
					        longitude, latitude, _ = geodesic.fwd(self.Coordinate[1], self.Coordinate[0], bearing, distance)
 | 
				
			||||||
 | 
					        return np.array([ latitude, longitude ])        
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,22 +0,0 @@
 | 
				
			|||||||
# Recat departure separation in seconds
 | 
					 | 
				
			||||||
# x = CAT A -> CAT F
 | 
					 | 
				
			||||||
# y = CAT A -> CAT F
 | 
					 | 
				
			||||||
# https://www.skybrary.aero/index.php/RECAT_-_Wake_Turbulence_Re-categorisation
 | 
					 | 
				
			||||||
recatDeparture = [
 | 
					 | 
				
			||||||
    [0, 100, 120, 140, 160, 180],
 | 
					 | 
				
			||||||
    [0, 0, 0, 100, 120, 140],
 | 
					 | 
				
			||||||
    [0, 0, 0, 80, 100, 120],
 | 
					 | 
				
			||||||
    [0, 0, 0, 0, 0, 120],
 | 
					 | 
				
			||||||
    [0, 0, 0, 0, 0, 100],
 | 
					 | 
				
			||||||
    [0, 0, 0, 0, 0, 80],
 | 
					 | 
				
			||||||
]
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#Recat Arrival in NM
 | 
					 | 
				
			||||||
recatArrival = [
 | 
					 | 
				
			||||||
    [3, 4, 5, 5, 6, 8],
 | 
					 | 
				
			||||||
    [0, 3, 4, 4, 5, 7],
 | 
					 | 
				
			||||||
    [0, 0, 3, 3, 4, 6],
 | 
					 | 
				
			||||||
    [0, 0, 0, 0, 0, 5],
 | 
					 | 
				
			||||||
    [0, 0, 0, 0, 0, 4],
 | 
					 | 
				
			||||||
    [0, 0, 0, 0, 0, 3],
 | 
					 | 
				
			||||||
]
 | 
					 | 
				
			||||||
							
								
								
									
										10
									
								
								main.py
									
									
									
									
									
								
							
							
						
						
									
										10
									
								
								main.py
									
									
									
									
									
								
							@@ -1,10 +0,0 @@
 | 
				
			|||||||
from tcp.TCPServer import TCPServer
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
def main():
 | 
					 | 
				
			||||||
    server = TCPServer()
 | 
					 | 
				
			||||||
    server.run()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
if __name__ == "__main__":
 | 
					 | 
				
			||||||
    main()
 | 
					 | 
				
			||||||
							
								
								
									
										9
									
								
								setup.py
									
									
									
									
									
								
							
							
						
						
									
										9
									
								
								setup.py
									
									
									
									
									
								
							@@ -63,6 +63,7 @@ class build_py(_build_py):
 | 
				
			|||||||
        generateProtobuf('src/protobuf/AircraftReport.proto')
 | 
					        generateProtobuf('src/protobuf/AircraftReport.proto')
 | 
				
			||||||
        generateProtobuf('src/protobuf/AircraftSchedule.proto')
 | 
					        generateProtobuf('src/protobuf/AircraftSchedule.proto')
 | 
				
			||||||
        generateProtobuf('src/protobuf/BaseTypes.proto')
 | 
					        generateProtobuf('src/protobuf/BaseTypes.proto')
 | 
				
			||||||
 | 
					        generateProtobuf('src/protobuf/Communication.proto')
 | 
				
			||||||
        _build_py.run(self)
 | 
					        _build_py.run(self)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
with open('README.md', 'r') as f:
 | 
					with open('README.md', 'r') as f:
 | 
				
			||||||
@@ -77,13 +78,14 @@ setup(
 | 
				
			|||||||
        'aman.config',
 | 
					        'aman.config',
 | 
				
			||||||
        'aman.formats',
 | 
					        'aman.formats',
 | 
				
			||||||
        'aman.sys',
 | 
					        'aman.sys',
 | 
				
			||||||
 | 
					        'aman.sys.aco',
 | 
				
			||||||
        'aman.tools',
 | 
					        'aman.tools',
 | 
				
			||||||
        'aman.types'
 | 
					        'aman.types'
 | 
				
			||||||
    ],
 | 
					    ],
 | 
				
			||||||
    namespace_packages = [ 'aman' ],
 | 
					    namespace_packages = [ 'aman' ],
 | 
				
			||||||
    description = 'AMAN optimization backend',
 | 
					    description = 'AMAN optimization backend',
 | 
				
			||||||
    long_description = longDescription,
 | 
					    long_description = longDescription,
 | 
				
			||||||
    author = 'Sven Czarnian',
 | 
					    author = 'Sven Czarnian, Pascal Seeler',
 | 
				
			||||||
    author_email = 'devel@svcz.de',
 | 
					    author_email = 'devel@svcz.de',
 | 
				
			||||||
    license = 'GPLv3',
 | 
					    license = 'GPLv3',
 | 
				
			||||||
    cmdclass = { 'clean': clean, 'build_py': build_py },
 | 
					    cmdclass = { 'clean': clean, 'build_py': build_py },
 | 
				
			||||||
@@ -94,8 +96,9 @@ setup(
 | 
				
			|||||||
        'numpy',
 | 
					        'numpy',
 | 
				
			||||||
        'protobuf',
 | 
					        'protobuf',
 | 
				
			||||||
        'pyzmq',
 | 
					        'pyzmq',
 | 
				
			||||||
        'scikit-learn',
 | 
					 | 
				
			||||||
        'scipy',
 | 
					        'scipy',
 | 
				
			||||||
        'setuptools'
 | 
					        'setuptools',
 | 
				
			||||||
 | 
					        'flask',
 | 
				
			||||||
 | 
					        'flask-cors'
 | 
				
			||||||
    ]
 | 
					    ]
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 
 | 
				
			|||||||
 Submodule src/protobuf updated: 0c5ed87078...893e012b3f
									
								
							
		Reference in New Issue
	
	Block a user