75 Commits

Author SHA1 Message Date
Sven Czarnian
a1c48d7851 Merge branch 'feature/weather' into 'develop'
Feature/weather

See merge request nav/aman/aman-sys!2
2021-10-12 20:11:47 +00:00
Sven Czarnian
a86dfa01d8 add a weather model 2021-10-11 21:58:14 +02:00
Sven Czarnian
c6d22d2067 fix the protobuffer version 2021-10-11 21:57:47 +02:00
Sven Czarnian
23add20513 remove wrong imports 2021-10-11 21:57:09 +02:00
Sven Czarnian
fd324ea747 add some logging 2021-10-11 21:56:51 +02:00
Sven Czarnian
ebea408267 make the datetime code more readable 2021-10-11 21:56:39 +02:00
Sven Czarnian
8b43991c50 remove unreachable code 2021-10-10 08:27:19 +02:00
Sven Czarnian
11eae85e35 remove useless checks 2021-10-10 08:27:10 +02:00
Sven Czarnian
7e17bf0103 removed unused files 2021-10-10 08:26:49 +02:00
Sven Czarnian
667829b03d fix a crash during the update call 2021-09-25 09:03:55 +02:00
Sven Czarnian
dc2a435e8e fix a comment 2021-09-24 22:29:28 +02:00
Sven Czarnian
484be00e8c redefined the API to avoid GC issues during the destruction of the AMAN and its children 2021-09-24 22:28:19 +02:00
Sven Czarnian
b69c584fb4 use the weather system in the central AMAN class 2021-09-24 22:11:15 +02:00
Sven Czarnian
af52103ec8 fix an information 2021-09-24 22:10:58 +02:00
Sven Czarnian
11e76a3f24 refactor the code to abstract from the weather provider 2021-09-24 22:10:46 +02:00
Sven Czarnian
b54f7dfc50 fix a copy-paste error 2021-09-05 19:29:21 +02:00
Sven Czarnian
2687f543ad add the weather to the system configuration 2021-09-05 19:27:58 +02:00
Sven Czarnian
3743f31b84 introduce the weather configuration 2021-09-05 19:27:47 +02:00
Sven Czarnian
d851efcd4d add a crawler to parse DWD data 2021-09-05 18:59:30 +02:00
Sven Czarnian
9fd05aa932 fix code formatting 2021-09-05 18:59:16 +02:00
Sven Czarnian
d07751cf77 Merge branch 'feature/setup' into 'develop'
Feature/setup

See merge request nav/aman/aman-sys!1
2021-09-03 21:41:00 +00:00
Sven Czarnian
715433bac6 Merge remote-tracking branch 'origin/develop' into feature/setup
# Conflicts:
#	README.md
2021-09-03 23:39:42 +02:00
Sven Czarnian
9de9b813ba sort the received aircraft reports into the corresponding worker thread 2021-09-03 23:35:17 +02:00
Sven Czarnian
1561335e1b introduce a inbound class to prepare the collection of internal data 2021-09-03 23:34:26 +02:00
Sven Czarnian
b516333ede Merge branch 'feature/setup' of git.vatsim-germany.org:nav/aman-sys into feature/setup 2021-09-03 23:07:14 +02:00
Sven Czarnian
793d92ff83 get the arrival routes 2021-09-03 23:06:26 +02:00
Sven Czarnian
fa38924936 sort the arrival routes by the runways 2021-09-03 23:06:12 +02:00
Sven Czarnian
87d813d0a4 runways are not required. can be extracted out of required arrival routes 2021-09-03 23:05:57 +02:00
Sebastian Kramer
cf191a6ff1 Update .gitignore to reflect cache and build files 2021-09-03 15:38:05 +02:00
Sven Czarnian
aaa37a5f62 execute the planner every minute 2021-09-02 21:01:00 +02:00
Sven Czarnian
744ad71b6c initialize the workers for the airports and destroy them during shutdown 2021-09-02 20:59:41 +02:00
Sven Czarnian
479d7b2d44 add sys to the package definition 2021-09-02 20:59:17 +02:00
Sven Czarnian
64c238899a introduce a worker thread for the planning tasks 2021-09-02 20:59:02 +02:00
Sven Czarnian
518e80e2fe rename System class 2021-09-02 20:37:12 +02:00
Sven Czarnian
46cc87eb3b introduce a wrapper to provide a system class that manages the environment 2021-09-02 19:44:02 +02:00
Sven Czarnian
bd7cbe91ed add a configuration that parses all relevant airport data 2021-09-02 19:43:34 +02:00
Sven Czarnian
f6643d899f remove the obsolete configuration entries 2021-09-02 19:43:18 +02:00
Sven Czarnian
b10fae513e use paths to the keys relative to the configuration path 2021-09-02 19:43:05 +02:00
Sven Czarnian
d60d5cb716 replace exceptions by error messages 2021-09-02 19:35:20 +02:00
Sven Czarnian
b3b5b3e547 rename the configuration names 2021-09-02 19:35:02 +02:00
Sven Czarnian
9c7d8db006 parse the performance data information 2021-09-02 09:32:28 +02:00
Sven Czarnian
8cd5aa6baf introduce a tool that extracts the performance data out of Skybrary 2021-09-02 09:01:16 +02:00
Sven Czarnian
df455df689 introduce a class for performance data 2021-09-02 09:00:53 +02:00
Sven Czarnian
1374ad95c9 add a missing dependency for the crawler tool 2021-09-02 09:00:34 +02:00
Sven Czarnian
31150adb9e add a parser to read SCT/ESE-files 2021-08-30 21:57:56 +02:00
Sven Czarnian
e4715abda3 add some generic datatypes 2021-08-30 21:57:14 +02:00
Sven Czarnian
51b4013e6b fix a crash 2021-08-30 21:56:16 +02:00
Sven Czarnian
f4fbd6245b extend the external dependencies 2021-08-30 21:56:00 +02:00
Sven Czarnian
1d50f0e9af add code to replace the import commands to fix execution errors 2021-08-19 09:18:54 +02:00
Sven Czarnian
8f81b65df8 fux a warning 2021-08-19 09:01:18 +02:00
Sven Czarnian
c7738346bb receive aircraft reports 2021-08-17 17:42:21 +02:00
Sven Czarnian
4e8e8f15e4 update the interface 2021-08-17 17:42:07 +02:00
Sven Czarnian
5a2b9983b6 switch to classic API 2021-08-16 07:58:19 +02:00
Sven Czarnian
36d2bfa8a0 remove the useless destructor 2021-08-15 12:56:42 +02:00
Sven Czarnian
0fdcf8e99e use the destructor 2021-08-15 12:54:18 +02:00
Sven Czarnian
6b2072f43b introduce a receiver thread 2021-08-15 12:54:10 +02:00
Sven Czarnian
04b299730a delete the precompiled python cache 2021-08-15 09:01:39 +02:00
Sven Czarnian
8199b33d53 use one module for the complete ES communication 2021-08-15 09:00:04 +02:00
Sven Czarnian
153930e73c reorder dependencies 2021-08-15 08:59:42 +02:00
Sven Czarnian
7b26e27c9d introduce a configuration module 2021-08-15 08:59:32 +02:00
Sven Czarnian
a3f4f8f41b add argparse as a dependency 2021-08-14 21:31:18 +02:00
Sven Czarnian
59e458c70b add the long description 2021-08-14 21:31:06 +02:00
Sven Czarnian
a0c9676c78 format the setup call 2021-08-14 21:30:49 +02:00
Sven Czarnian
b92f437fcb extend the clean-function 2021-08-14 21:28:53 +02:00
Sven Czarnian
355a5463e5 introduce the initialization code for the aircraft data receiver 2021-08-14 21:28:38 +02:00
Sven Czarnian
d4c07824c6 update the documentation 2021-08-14 21:28:18 +02:00
Sven Czarnian
bbd45778db introduce a tool to create keys for the client and the server 2021-08-14 21:28:04 +02:00
Sven Czarnian
bd2d431c41 define the general package structure 2021-08-14 21:27:45 +02:00
Sven Czarnian
3076821b3a change the output location 2021-08-14 20:08:39 +02:00
Sven Czarnian
0f885c1e00 move the setup file to the correct location 2021-08-14 20:07:09 +02:00
Sven Czarnian
e112ee9694 initial setuptools definition 2021-08-14 19:35:25 +02:00
Sven Czarnian
7d540a9b85 add the protobuf-tool and license 2021-08-14 19:35:13 +02:00
Sven Czarnian
30729676ac add the protobuf-submodule 2021-08-14 19:34:59 +02:00
Sebastian Kramer
c80d230946 Some general considerations. 2021-08-13 10:41:11 +02:00
Sebastian Kramer
a0d4a1e0d3 Create initial tcp server 2021-08-12 10:21:20 +02:00
34 changed files with 1466 additions and 1 deletions

8
.gitignore vendored Normal file
View File

@@ -0,0 +1,8 @@
Backend.ini
main.py
.vscode/
*_pb2.py
__pycache__
*.egg-info
build/
dist/

4
.gitmodules vendored Normal file
View File

@@ -0,0 +1,4 @@
[submodule "src/protobuf"]
path = src/protobuf
url = git@git.vatsim-germany.org:nav/aman-com.git
branch = feature/protobuf

View File

@@ -1,2 +1,69 @@
# aman-sys # Arrival MANanager (AMAN)
## System description
AMAN is splitted up into four different components.
* aman-com defines the diffent message types
* aman-es implements an EuroScope plugin to communicate with [aman-sys](https://git.vatsim-germany.org/nav/aman-sys)
* aman-sys implements the backend system to plan an optimal arrival sequence for the different airports
* aman-web implements a web-interface to configure [aman-sys](https://git.vatsim-germany.org/nav/aman-sys) and visualize sequences
## Component description
AMAN uses [Protocol Buffers](https://developers.google.com/protocol-buffers)
for the message serialization and message definition between the EuroScope instance and the AMAN backend.
Additionally is [ZeroMQ](https://zeromq.org/) used for the communication abstraction layer.
This component provides the server backend with the planning and optimization system per airport.
It is designed as a python framework that can run on a webserver.
ZMQ based encryption and authentication methods are used to authenticate controllers.
## RHC-ACS-ASS Algorithm
Step 1: Initialization. Set up parameters for
the RHC, and set the current receding horizon k = 1.
Step 2: Find out all the M aircraft whose PLTs belong to
the kth receding horizon.
Step 3: Schedule the M aircraft in the kth receding horizon
by using an ACS.
Step 4: Assign the aircraft whose ALTs belong to kth scheduled window ω(k) to land on
the runway.
Step 5: Modify the PLT for those aircraft whose PLT belongs to ω(k) but the ALT does not belong to ω(k). The modification is to set their PLT to kTTI, making them belong to Ω(k + 1), such that they can be scheduled in the next receding horizon.
Step 6: Termination check. When all the aircraft have been assigned to land at the runway, the algorithm terminates. Otherwise, set k = k + 1 and go to Step 2 for the next receding horizon optimization.
In the preceding steps, Step 3 is the major process of the
algorithm. The flowchart is illustrated on the right side of Fig. 3,
and the details are given below.
Step 3.1: Schedule the M aircraft by the FCFS approach and
calculate the fitness value through (3). Calculate
the initial pheromone τ0 and set the pheromone for
each aircraft pair as τ0.
Step 3.2: For each ant, do the following.
a) Determine the first landing aircraft s and construct the whole landing sequence using the state transition rule as (5) and (6).
b) Perform the local pheromone updating as (9).
Step 3.3: Calculate the fitness of each ant and determine
the best solution. Moreover, the current best solution is compared with the historically best solution
to determine the historically best solution.
Step 3.4: Perform the global pheromone updating as (10).
# Additional libraries
* [ZeroMQ](https://github.com/zeromq) - GNU GPLv3
* [Protocol Buffers](https://github.com/protocolbuffers/protobuf) - BSD-3
# License
AMAN is released under the [GNU General Public License v3](LICENSE)

97
aman/AMAN.py Normal file
View File

@@ -0,0 +1,97 @@
#!/usr/bin/env python
import glob
import os
import sys
from aman.com import AircraftReport_pb2
from aman.com.Euroscope import Euroscope
from aman.com.Weather import Weather
from aman.config.AircraftPerformance import AircraftPerformance
from aman.config.Airport import Airport
from aman.config.System import System
from aman.sys.Worker import Worker
class AMAN:
def findConfigPath():
envvar = os.environ.get('AMAN_CONFIG_PATH')
if None == envvar:
print('No AMAN_CONFIG_PATH in environment variables found. Using execution directory.')
path = os.getcwd()
else:
print('AMAN_CONFIG_PATH found.')
path = envvar
print('Config-path: ' + path)
return path
def __init__(self):
# 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()
# read all system relevant configuration files
self.systemConfig = System(os.path.join(configPath, 'System.ini'))
print('Parsed System.ini')
# read the aircraft performance data
self.aircraftPerformance = AircraftPerformance(os.path.join(configPath, 'PerformanceData.ini'))
if None == self.aircraftPerformance:
sys.stderr.write('No aircraft performance data found!')
sys.exit(-1)
else:
print('Parsed PerformanceData.ini. Extracted ' + str(len(self.aircraftPerformance.aircrafts)) + ' aircrafts')
self.weather = Weather()
self.weather.acquire(self.systemConfig.Weather)
# find the airport configurations and create the workers
airportsPath = os.path.join(os.path.join(configPath, 'airports'), '*.ini')
for file in glob.glob(airportsPath):
icao = os.path.splitext(os.path.basename(file))[0]
print('Parsing planner configuration for ' + icao)
airportConfig = Airport(file, icao)
# initialize the worker thread
worker = Worker()
worker.acquire(icao, airportConfig)
self.workers.append(worker)
print('Started worker for ' + icao)
# create the EuroScope receiver
self.receiver = Euroscope()
self.receiver.acquire(configPath, self.systemConfig.Server, self)
def release(self):
if None != self.receiver:
self.receiver.release()
self.receiver = None
if None != self.weather:
self.weather.release()
self.weather = None
if None != self.workers:
for worker in self.workers:
worker.release()
self.workers = None
def updateAircraftReport(self, report : AircraftReport_pb2.AircraftReport):
# find the correct worker for the inbound
for worker in self.workers:
if worker.icao == report.destination:
worker.acquireLock()
worker.reportQueue[report.aircraft.callsign] = report
worker.releaseLock()
break

0
aman/__init__.py Normal file
View File

157
aman/com/DwdCrawler.py Normal file
View File

@@ -0,0 +1,157 @@
#!/usr/bin/env python
import datetime
import time
import urllib.request
from bs4 import BeautifulSoup
from datetime import datetime as dt
# @brief Checks the DWD pages for wind information
# Format:
# Provides next update tine (updateTime) of the DWD page in UTC
# Provides a list of wind information (windData)
# - organized as a list of tuples
# - first element of tuple: GAFOR-IDs for the following wind information
# - second element of tuple: list of tuples of wind data
# - first element of wind data tuple: minimum altitude AMSL for this wind information
# - second element of wind data tuple: wind direction
# - third element of wind data tuple: wind speed (KT)
class DwdCrawler():
def __init__(self):
self.updateTime = None
self.windData = None
def parseGaforAreas(areas : str):
areas = areas.replace(':', '')
areas = areas.split(' ')[1]
areaIds = []
# some IDs are lists
for segment in areas.split(','):
# check if we have range definitions or single IDs
borders = segment.split('-')
if 2 == len(borders):
areaIds.extend(range(int(borders[0]), int(borders[1]) + 1))
else:
areaIds.append(int(borders[0]))
return areaIds
def parseWindTableRow(row : str, table):
# get the columns
entries = row.split('|')
# check if the line is invalid or we have the header
if 2 > len(entries) or 'AMSL' in entries[0]:
return table
# parse the wind data
windData = entries[1].strip().split(' ')[0].split('/')
if 2 != len(windData):
return table
# extend the table
altitude = entries[0].strip()
if 'FL' in altitude:
altitude = int(altitude.replace('FL', '')) * 100
else:
altitude = int(altitude.replace('FT', ''))
row = ( altitude, int(windData[0]), int(windData[1].replace('KT', '')) )
table.append(row)
return table
def parseNextUpdateTime(line : str):
entries = line.split(' ')
if 4 <= len(entries):
utcIndex = 2
if 'UTC' in entries[len(entries) - 2]:
utcIndex = len(entries) - 3
elif 'UTC' in entries[len(entries) - 1]:
utcIndex = len(entries - 2)
currentUtc = dt.utcfromtimestamp(int(time.time()))
currentHour = int(currentUtc.strftime('%H'))
# check if we have a day overlap
if currentHour > int(entries[utcIndex].split('.')[0]):
nextDay = currentUtc + datetime.timedelta(days=1)
date = nextDay.strftime('%Y-%m-%d')
else:
date = currentUtc.strftime('%Y-%m-%d')
# create the new UTC update time
return dt.strptime(date + ' ' + entries[utcIndex] + '+0000', '%Y-%m-%d %H.%M%z')
def parseGaforPage(self, url : str):
with urllib.request.urlopen(url) as site:
data = site.read().decode('utf-8')
site.close()
parsed = BeautifulSoup(data, features='lxml')
# search the info about the GAFOR areas
content = None
for element in parsed.body.find_all('pre'):
content = element.text
# analyze the received data
if None != content:
windInformation = []
nextUpdate = None
windTable = []
areaIds = None
# find all relevant information
for line in content.splitlines():
if '' == line:
if 0 != len(windTable):
windInformation.append(( areaIds, windTable ))
areaIds = None
windTable = []
elif line.startswith('GAFOR-Gebiete'):
areaIds = DwdCrawler.parseGaforAreas(line)
windTable = []
elif None != areaIds:
windTable = DwdCrawler.parseWindTableRow(line, windTable)
elif 'Aktualisierung erfolgt um ' in line:
nextUpdate = DwdCrawler.parseNextUpdateTime(line)
# return the collected information
if 0 == len(windInformation) or None == nextUpdate:
return None, None
else:
return nextUpdate, windInformation
def receiveWindData(self):
self.updateTime = None
self.windData = None
with urllib.request.urlopen('https://www.dwd.de/DE/fachnutzer/luftfahrt/teaser/luftsportberichte/luftsportberichte_node.html') as site:
data = site.read().decode('utf-8')
site.close()
# find the pages of the GAFOR reports
pages = []
parsed = BeautifulSoup(data, features='lxml')
for link in parsed.body.find_all('a', title=True):
if 'node' in link['href'] and 'Flugwetterprognose' in link['title']:
# remove the jsession from the link
pages.append('https://www.dwd.de/' + link['href'].split(';')[0])
# receive the wind data
self.updateTime = None
self.windData = []
for page in pages:
next, wind = self.parseGaforPage(page)
if None != next:
if None == self.updateTime or self.updateTime > next:
self.updateTime = next
self.windData.extend(wind)
# indicate that new wind data is available
if None != self.updateTime:
return True
else:
return False

121
aman/com/Euroscope.py Normal file
View File

@@ -0,0 +1,121 @@
#!/usr/bin/env python
import ctypes
import glob
import os
import sys
import time
import zmq
import zmq.auth
from aman.com import AircraftReport_pb2
from aman.config.Server import Server
from threading import Thread, _active
class ReceiverThread(Thread):
def __init__(self, socket, aman):
Thread.__init__(self)
self.socket = socket
self.aman = aman
def run(self):
while True:
try:
msg = self.socket.recv(zmq.NOBLOCK)
# parse the received message
report = AircraftReport_pb2.AircraftReport()
report.ParseFromString(msg)
# try to associate the received aircraft to an airport
self.aman.updateAircraftReport(report)
except zmq.ZMQError as error:
if zmq.EAGAIN == error.errno:
time.sleep(0.5)
continue
else:
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
class Euroscope:
def __init__(self):
self.context = None
self.receiverSocket = None
self.receiverThread = None
self.notificationSocket = None
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
serverKeyPath = os.path.join(os.path.join(configPath, 'keys'), 'server')
if False == os.path.isdir(serverKeyPath):
sys.stderr.write('No directory for the server key found')
sys.exit(-1)
print('Path to the server key: ' + serverKeyPath)
clientKeyPath = os.path.join(os.path.join(configPath, 'keys'), 'clients')
if False == os.path.isdir(clientKeyPath):
sys.stderr.write('No directory for the client keys found')
sys.exit(-1)
print('Path to the client keys: ' + clientKeyPath)
# read the certificates
keyPairPath = glob.glob(os.path.join(serverKeyPath, '*.key_secret'))
if 1 != len(keyPairPath):
sys.stderr.write('No public-private keypair found for the server certificate')
sys.exit(-1)
keyPair = zmq.auth.load_certificate(keyPairPath[0])
# initialize the receiver
self.receiverSocket = zmq.Socket(self.context, zmq.SUB)
self.receiverSocket.setsockopt(zmq.CURVE_PUBLICKEY, keyPair[0])
self.receiverSocket.setsockopt(zmq.CURVE_SECRETKEY, keyPair[1])
self.receiverSocket.setsockopt(zmq.CURVE_SERVER, True)
self.receiverSocket.bind('tcp://' + config.Address + ':' + str(config.PortReceiver))
self.receiverSocket.setsockopt(zmq.SUBSCRIBE, b'')
self.receiverThread = ReceiverThread(self.receiverSocket, aman)
self.receiverThread.start()
print('Listening to tcp://' + config.Address + ':' + str(config.PortReceiver))
# initialize the notification
self.notificationSocket = zmq.Socket(self.context, zmq.PUB)
self.notificationSocket.setsockopt(zmq.CURVE_PUBLICKEY, keyPair[0])
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):
if None != self.receiverThread:
self.receiverThread.stopThread()
self.receiverThread.join()
self.receiverThread = None
if None != self.receiverSocket:
self.receiverSocket.close()
self.receiverSocket = None
if None != self.notificationSocket:
self.notificationSocket.close()
self.notificationSocket = None

56
aman/com/Weather.py Normal file
View File

@@ -0,0 +1,56 @@
#!/usr/bin/env python
import pytz
import sys
import time
from datetime import datetime as dt
from threading import Thread
from aman.com.DwdCrawler import DwdCrawler
import aman.config.Weather
class Weather(Thread):
def __init__(self):
Thread.__init__(self)
self.nextUpdate = None
self.lastUpdateTried = None
self.stopThread = False
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():
self.provider = DwdCrawler()
else:
sys.stderr.write('Invalid or unknown weather-provider defined')
sys.exit(-1)
self.start()
def release(self):
self.stopThread = True
self.join()
def currentClock():
clock = dt.utcfromtimestamp(int(time.time())).replace(tzinfo = pytz.UTC)
return clock
def run(self):
while False == self.stopThread and None != self.provider:
now = Weather.currentClock()
# check if an update is required
if None != self.provider.updateTime and self.provider.updateTime > now:
time.sleep(1)
continue
if None == self.lastUpdateTried or self.lastUpdateTried <= now:
if True == self.provider.receiveWindData():
self.nextUpdate = self.provider.updateTime
print('Received new wind data')

0
aman/com/__init__.py Normal file
View File

View File

@@ -0,0 +1,28 @@
#!/usr/bin/env python
import configparser
from aman.types.PerformanceData import PerformanceData
class AircraftPerformance:
def __init__(self, filepath : str):
config = configparser.ConfigParser()
config.read(filepath)
self.aircrafts = { }
# iterate over all entries
for key in config:
if 'DEFAULT' == key:
continue
aircraft = PerformanceData(key)
aircraft.speedAboveFL240 = config[key]['speedabovefl240']
aircraft.rodAboveFL240 = config[key]['rodabovefl240']
aircraft.speedAboveFL100 = config[key]['speedabovefl100']
aircraft.rodAboveFL100 = config[key]['rodabovefl100']
aircraft.speedBelowFL100 = config[key]['speedbelowfl100']
aircraft.rodBelowFL100 = config[key]['rodbelowfl100']
aircraft.speedApproach = config[key]['speedapproach']
self.aircrafts[aircraft.icao] = aircraft

66
aman/config/Airport.py Normal file
View File

@@ -0,0 +1,66 @@
#!/usr/bin/env python
import configparser
import glob
import os
import sys
from formats.SctEseFormat import SctEseFormat
class Airport:
def findGngData(data, path):
if None == data.get('gngwildcard'):
return None, None
# find the newest ESE file
files = glob.glob(os.path.join(path, data['gngwildcard'] + '.ese'))
latestEse = max(files, key=os.path.getctime)
# search for the corresponding SCT file
latestSct = os.path.splitext(latestEse)[0] + '.sct'
# check if the files exist
if False == os.path.isfile(latestEse) or False == os.path.isfile(latestSct):
return None, None
return latestSct, latestEse
def parsePlanning(self, planning):
if None == planning.get('routes'):
return []
return planning['routes'].split(':')
def __init__(self, filepath : str, icao : str):
self.arrivalRoutes = {}
config = configparser.ConfigParser()
config.read(filepath)
dataConfig = None
planningConfig = None
# search the required sections
for key in config:
if 'DATA' == key:
dataConfig = config['DATA']
elif 'PLANNING' == key:
planningConfig = config['PLANNING']
# find the GNG-file data
sctFile, eseFile = Airport.findGngData(dataConfig, os.path.dirname(filepath))
if None == sctFile or None == eseFile:
sys.stderr.write('No GNG-files found')
sys.exit(-1)
# parse the planning information
if None == planningConfig or False == self.parsePlanning(planningConfig):
sys.stderr.write('No planning configuration found')
sys.exit(-1)
requiredArrivalRoutes = self.parsePlanning(planningConfig)
if 0 == len(requiredArrivalRoutes):
sys.stderr.write('No valid planning configuration found')
sys.exit(-1)
# parse the GNG data
print('Used GNG-Data: ' + eseFile)
self.gngData = SctEseFormat(sctFile, eseFile, icao, requiredArrivalRoutes)

29
aman/config/Server.py Normal file
View File

@@ -0,0 +1,29 @@
#!/usr/bin/env python
import configparser;
import sys
class Server():
def __init__(self, config : configparser.ConfigParser):
self.Address = None
self.PortReceiver = None
self.PortNotification = None
# search the required sections
for key in config:
if 'address' == key:
self.Address = config['address']
elif 'portreceiver' == key:
self.PortReceiver = int(config['portreceiver'])
elif 'portnotification' == key:
self.PortNotification = int(config['portnotification'])
if self.Address is None:
sys.stderr.write('No server-address configuration found!')
sys.exit(-1)
if self.PortReceiver is None:
sys.stderr.write('No server-port-receiver configuration found!')
sys.exit(-1)
if self.PortNotification is None:
sys.stderr.write('No server-port-notification configuration found!')
sys.exit(-1)

30
aman/config/System.py Normal file
View File

@@ -0,0 +1,30 @@
#!/usr/bin/env python
import configparser
import sys
from aman.config.Server import Server
from aman.config.Weather import Weather
class System:
def __init__(self, filepath : str):
config = configparser.ConfigParser()
config.read(filepath)
# search the required sections
serverSectionAvailable = False
for key in config:
if 'SERVER' == key:
serverSectionAvailable = True
elif 'WEATHER' == key:
weatherSectionAvailable = True
if not serverSectionAvailable:
sys.stderr.write('No server-configuration section found!')
sys.exit(-1)
if not weatherSectionAvailable:
sys.stderr.write('No weather-configuration section found!')
sys.exit(1)
self.Server = Server(config['SERVER'])
self.Weather = Weather(config['WEATHER'])

19
aman/config/Weather.py Normal file
View File

@@ -0,0 +1,19 @@
#!/usr/bin/env python
import configparser;
import sys
class Weather():
def __init__(self, config : configparser.ConfigParser):
self.Provider = None
self.PortReceiver = None
self.PortNotification = None
# search the required sections
for key in config:
if 'provider' == key:
self.Provider = config['provider']
if self.Provider is None:
sys.stderr.write('No weather-provider configuration found!')
sys.exit(-1)

0
aman/config/__init__.py Normal file
View File

View File

@@ -0,0 +1,152 @@
#!/usr/bin/env python
import sys
from aman.types.ArrivalRoute import ArrivalRoute
from aman.types.Waypoint import Waypoint
class SctEseFormat:
def readFile(filename : str):
fileBlocks = {}
block = None
# read the file line by line and create segments based on []-entries
with open(filename) as file:
for line in file:
line = line.strip()
# found a new segment
if line.startswith('['):
block = line[1:-1]
fileBlocks.setdefault(block, [])
# append the last backend
elif None != block and 0 != len(line):
fileBlocks[block].append(line)
return fileBlocks
def parseWaypoint(waypoint : str, nameIdx : int, latitudeIdx : int, longitudeIdx : int):
split = list(filter(None, waypoint.split(' ')))
if len(split) <= longitudeIdx:
sys.stderr.write('Invalid waypoint format: ' + waypoint)
sys.exit(-1)
return Waypoint(split[nameIdx], Waypoint.dms2dd(split[latitudeIdx]), Waypoint.dms2dd(split[longitudeIdx]))
def extractWaypoints(self, sctFilepath : str):
config = SctEseFormat.readFile(sctFilepath)
foundAirports = False
foundVOR = False
foundNDB = False
foundFix = False
for key in config:
if 'VOR' == key:
foundVOR = True
elif 'NDB' == key:
foundNDB = True
elif 'FIXES' == key:
foundFix = True
elif 'AIRPORT' == key:
foundAirports = True
if False == foundVOR:
sys.stderr.write('Unable to find VOR-entries in the sector file')
sys.exit(-1)
if False == foundNDB:
sys.stderr.write('Unable to find NDB-entries in the sector file')
sys.exit(-1)
if False == foundFix:
sys.stderr.write('Unable to find FIX-entries in the sector file')
sys.exit(-1)
if False == foundAirports:
sys.stderr.write('Unable to find AIRPORT-entries in the sector file')
sys.exit(-1)
# extract all waypoints
for waypoint in config['VOR']:
waypoint = SctEseFormat.parseWaypoint(waypoint, 0, 2, 3)
self.waypoints.setdefault(waypoint.name, []).append(waypoint)
for waypoint in config['NDB']:
waypoint = SctEseFormat.parseWaypoint(waypoint, 0, 1, 2)
self.waypoints.setdefault(waypoint.name, []).append(waypoint)
for waypoint in config['FIXES']:
waypoint = SctEseFormat.parseWaypoint(waypoint, 0, 1, 2)
self.waypoints.setdefault(waypoint.name, []).append(waypoint)
# extract the airports
for airport in config['AIRPORT']:
airport = SctEseFormat.parseWaypoint(airport,0, 2, 3)
self.airports.setdefault(airport.name, []).append(airport)
def parseArrivalRoute(self, route : str, airport : Waypoint):
# split the route and validate that it is a STAR for the airport
split = route.split(':')
if 5 != len(split) or 'STAR' != split[0] or split[1] != airport.name:
return None
# find all waypoints
waypoints = []
route = list(filter(None, split[4].split(' ')))
for waypoint in route:
# find the waypoint in the route
coordinates = self.waypoints[waypoint]
# no waypoint with this name defined
if None == coordinates:
sys.stderr.write('Unable to find waypoint ' + waypoint)
sys.exit(-1)
# multiple waypoints, but use Haversine distance to distinct between candidates
elif 1 != len(coordinates):
minDistance = sys.float_info.max
nearest = None
# we assume that waypoints with the same name are not close each other
for coordinate in coordinates:
distance = coordinate.haversine(airport)
# found a closer waypoint
if minDistance > distance:
minDistance = distance
nearest = coordinate
if None == nearest:
sys.stderr.write('Unable to find a close waypoint for ' + waypoint)
sys.exit(-1)
waypoints.append(nearest)
# extend the list of waypoints
else:
waypoints.append(coordinates[0])
# create the arrival route
return ArrivalRoute(split[3], split[2], waypoints)
def extractArrivalRoutes(self, eseFilepath : str, airport : str, allowedRoutes : list):
config = SctEseFormat.readFile(eseFilepath)
foundSidsStars = False
# search the airport in the extracted list
if not airport in self.airports:
sys.stderr.write(airport + 'in self.airports', 'Unable to find the requested airport')
sys.exit(-1)
airport = self.airports[airport][0]
for key in config:
if 'SIDSSTARS' == key:
foundSidsStars = True
if False == foundSidsStars:
sys.stderr.write('Unable to find SIDSSTARS-entries in the sector file')
sys.exit(-1)
# parse all arrival routes
for line in config['SIDSSTARS']:
route = self.parseArrivalRoute(line, airport)
if None != route and route.name in allowedRoutes:
self.arrivalRoutes.setdefault(route.runway, []).append(route)
def __init__(self, sctFilepath : str, eseFilepath : str, airport : str, allowedRoutes : list):
self.arrivalRoutes = {}
self.waypoints = {}
self.airports = {}
self.extractWaypoints(sctFilepath)
self.extractArrivalRoutes(eseFilepath, airport, allowedRoutes)

0
aman/formats/__init__.py Normal file
View File

148
aman/sys/WeatherModel.py Normal file
View File

@@ -0,0 +1,148 @@
#!/usr/bin/env python
from aman.com.Weather import Weather
import math
import scipy.interpolate
class WeatherModel:
def __init__(self, gaforId, weather : Weather):
self.gafor = gaforId
self.weather = weather
self.windDirectionModel = None
self.windSpeedModel = None
self.lastWeatherUpdate = None
self.minimumAltitude = 1000000
self.maximumAltitude = -1
# create the density interpolation model
# the density model is based on https://aerotoolbox.com/atmcalc/
altitudes = [
50000,
45000,
40000,
38000,
36000,
34000,
32000,
30000,
28000,
26000,
24000,
22000,
20000,
18000,
16000,
15000,
14000,
13000,
12000,
11000,
10000,
9000,
8000,
7000,
6000,
5000,
4000,
3000,
2000,
1000,
0
]
densities = [
0.18648,
0.23714,
0.24617,
0.33199,
0.36518,
0.39444,
0.42546,
0.45831,
0.402506,
0.432497,
0.464169,
0.60954,
0.65269,
0.69815,
0.74598,
0.77082,
0.79628,
0.82238,
0.84914,
0.87655,
0.90464,
0.93341,
0.96287,
0.99304,
1.02393,
1.05555,
1.08791,
1.12102,
1.1549,
1.18955,
1.225
]
self.densityModel = scipy.interpolate.interp1d(altitudes, densities)
def calculateTAS(self, altitude : int, ias : int):
if altitude >= 50000:
altitude = 49999
if altitude <= 0:
altitude = 1
# calculation based on https://aerotoolbox.com/airspeed-conversions/
return ias * math.sqrt(1.225 / self.densityModel(altitude).item())
def updateWindModel(self):
if None == self.lastWeatherUpdate or self.lastWeatherUpdate != self.weather.provider.updateTime:
self.lastWeatherUpdate = self.weather.provider.updateTime
self.minimumAltitude = 1000000
self.maximumAltitude = -1
self.windDirectionModel = None
self.windSpeedModel = None
if None != self.weather.provider.windData and self.gafor in self.weather.provider.windData:
altitudes = []
directions = []
speeds = []
# collect the data for the wind model
for level in self.weather.provider.windData[self.gafor]:
altitudes.append(level[0])
directions.append(level[1])
speeds.append(level[2])
# define the thresholds for later boundary checks
if self.minimumAltitude > level[0]:
self.minimumAltitude = level[0]
if self.maximumAltitude < level[0]:
self.maximumAltitude = level[0]
# calculate the models
if 1 < len(altitudes):
self.windDirectionModel = scipy.interpolate.interp1d(altitudes, directions)
self.windSpeedModel = scipy.interpolate.interp1d(altitudes, speeds)
def calculateGS(self, altitude : int, ias : int, heading : int):
self.updateWindModel()
tas = self.calculateTAS(altitude, ias)
# initialize the wind data
if None != self.windDirectionModel and None != self.windSpeedModel:
direction = 0.0
speed = 0.0
if None != self.windSpeedModel and None != self.windDirectionModel:
if self.maximumAltitude <= altitude:
altitude = self.maximumAltitude - 1
if self.minimumAltitude >= altitude:
altitude = self.minimumAltitude + 1
direction = self.windDirectionModel(altitude).item()
speed = self.windSpeedModel(altitude).item()
else:
speed = 0
direction = 0
# calculate the ground speed based on the headwind component
return tas + speed * math.cos(math.radians(direction) - math.radians(heading))

53
aman/sys/Worker.py Normal file
View File

@@ -0,0 +1,53 @@
#!/usr/bin/env python
from threading import Thread, Lock
import time
from aman.config.Airport import Airport
class Worker(Thread):
def __init__(self):
Thread.__init__(self)
self.stopThread = None
self.icao = None
self.configuration = None
self.arrivalRoutes = None
self.updateLock = None
self.reportQueue = {}
def __del__(self):
self.release()
def acquire(self, icao : str, configuration : Airport):
self.stopThread = None
self.icao = icao
self.configuration = configuration
self.arrivalRoutes = configuration.gngData.arrivalRoutes
self.updateLock = Lock()
self.reportQueue = {}
self.start()
def acquireLock(self):
if None != self.updateLock:
self.updateLock.acquire()
def release(self):
self.stopThread = True
self.join()
def releaseLock(self):
if None != self.updateLock:
self.updateLock.release()
def run(self):
counter = 0
while None == self.stopThread:
time.sleep(1)
counter += 1
if 0 != (counter % 60):
continue
# TODO handle the report queue and update internal information
# TODO execute planning, etc.
continue

0
aman/sys/__init__.py Normal file
View File

View File

@@ -0,0 +1,44 @@
#!/usr/bin/env python
import argparse
import os
from typing import Tuple
import zmq.auth
# @brief Creates a new keypair for ZMQ encryption
# @param[in] directory The location where to store the keys
# @return The public and private key tuple
def KeyPairCreator(directory: str, server: bool) -> Tuple[str, str]:
if not server:
print('Creating a new pair for a client...')
target = 'client'
else:
print('Creating a new pair for the server...')
target = 'server'
public, private = zmq.auth.create_certificates(directory, target)
return (public, private)
def str2bool(value):
if isinstance(value, bool):
return value
elif value.lower() in ('yes', 'true', 't', 'y', '1'):
return True
elif value.lower() in ('no', 'false', 'f', 'n', '0'):
return False
else:
raise argparse.ArgumentTypeError('Boolean value expected')
if __name__ == '__main__':
# create the commandline parser
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('--server', default=False, action='store_true', help="Creates server key pair")
args = parser.parse_args()
# create the directory if it does not exist
if not os.path.exists(args.directory):
os.makedirs(args.directory)
# create the keys
KeyPairCreator(args.directory, args.server)

View File

@@ -0,0 +1,151 @@
#!/usr/bin/env python
import argparse
import configparser
import os
import urllib.request
from bs4 import BeautifulSoup
from aman.types.PerformanceData import PerformanceData
def findAircraftPages(rooturl : str, suburl : str):
aircrafts = []
with urllib.request.urlopen(rooturl + suburl) as site:
data = site.read().decode('utf-8')
site.close()
parsed = BeautifulSoup(data, features='lxml')
for link in parsed.body.find_all('a', title=True):
split = link['href'].split('/')
if 3 == len(split) and split[2] == link['title'] and 'Category' not in link['title'] and 'Special' not in link['href']:
aircrafts.append(rooturl + link['href'])
for link in parsed.body.find_all('a', attrs={ 'title': 'Category:Aircraft' }):
if 'previous' not in link.text:
aircrafts.extend(findAircraftPages(rooturl, link['href']))
return aircrafts
def findAndParseEntry(tableRow, startIdx, substring, default):
while 0 < startIdx:
if substring in tableRow[startIdx].text:
split = tableRow[startIdx].text.split(' ')
if 1 >= len(split):
return default, startIdx - 2
else:
return int(split[0]), startIdx - 2
else:
startIdx -= 1
return 0, -1
def findAndParseSpeedEntry(tableRow, startIdx, default):
return findAndParseEntry(tableRow, startIdx, 'kts', default)
def findAndParseRodEntry(tableRow, startIdx, default):
return findAndParseEntry(tableRow, startIdx, 'ft/min', default)
def parsePerformanceEntries(tableRowSpeeds, tableRowRODs):
speeds = []
rods = []
# parse the speed data
idx = len(tableRowSpeeds) - 1
while 0 < idx:
parsed = findAndParseSpeedEntry(tableRowSpeeds, idx, 140 if 0 == len(speeds) else 250)
if 0 < idx:
speeds.append(parsed[0])
idx = parsed[1]
# parse the ROD data
idx = len(tableRowRODs) - 1
while 0 < idx:
parsed = findAndParseRodEntry(tableRowRODs, idx, 2000)
if 0 < idx:
rods.append(parsed[0])
idx = parsed[1]
return speeds, rods
def parsePerformanceData(url : str):
with urllib.request.urlopen(url) as site:
data = site.read().decode('utf-8')
site.close()
# check if we find the ICAO code
parsed = BeautifulSoup(data, features='lxml')
icao = parsed.body.find('h5', attrs={ 'id' : 'siteSub', 'class' : 'subtitle'})
if None == icao or '' == icao.text:
return False, None
aircraft = PerformanceData(icao.text)
performanceTable = parsed.body.find('table', attrs={ 'class' : 'wikitable', 'style' : 'font-size: 90%;' })
if None == performanceTable or None == performanceTable.find_all('tr')[1] or None == performanceTable.find_all('tr')[2]:
return False, None
speeds, rods = parsePerformanceEntries(performanceTable.find_all('tr')[1].find_all('td'),
performanceTable.find_all('tr')[2].find_all('td'))
if 10 > len(speeds):
speeds.insert(1, speeds[1])
# create the speed data
if len(speeds) >= 4:
aircraft.speedApproach = speeds[0]
aircraft.speedBelowFL100 = speeds[1]
aircraft.speedAboveFL100 = speeds[2]
aircraft.speedAboveFL240 = speeds[3]
# create the ROD data
if len(rods) >= 3:
aircraft.rodBelowFL100 = rods[0]
aircraft.rodAboveFL100 = rods[1]
aircraft.rodAboveFL240 = rods[2]
return len(speeds) >= 4 and len(rods) >= 3, aircraft
if __name__ == '__main__':
# create the commandline parser
parser = argparse.ArgumentParser(description='Extract the aircraft performace data')
parser.add_argument('directory', help='Directory where to store the performance data configuration')
args = parser.parse_args()
# create the directory if it does not exist
if not os.path.exists(args.directory):
os.makedirs(args.directory)
# parse the aircrafts
links = findAircraftPages('https://www.skybrary.aero', '/index.php?title=Category:Aircraft')
print('Found ' + str(len(links)) + ' aircrafts')
aircrafts = []
parsed = 0
for link in links:
valid, aircraft = parsePerformanceData(link)
parsed += 1
print('Parsed ' + str(parsed) + ' of ' + str(len(links)), end='\r')
if False == valid:
print('Unable to find performance data for ' + link)
continue
aircrafts.append(aircraft)
print('Successfully parsed ' + str(len(aircrafts)) + ' of ' + str(len(links)) + ' aircrafts')
# create the configuration file
config = configparser.ConfigParser()
for aircraft in aircrafts:
config[aircraft.icao] = {
'speedAboveFL240' : aircraft.speedAboveFL240,
'rodAboveFL240' : aircraft.rodAboveFL240,
'speedAboveFL100' : aircraft.speedAboveFL100,
'rodAboveFL100' : aircraft.rodAboveFL100,
'speedBelowFL100' : aircraft.speedBelowFL100,
'rodBelowFL100' : aircraft.rodBelowFL100,
'speedApproach' : aircraft.speedApproach
}
# write the configuration data
with open(args.directory + '/PerformanceData.ini', 'w') as file:
config.write(file)

0
aman/tools/__init__.py Normal file
View File

View File

@@ -0,0 +1,11 @@
#!/usr/bin/env python
class ArrivalRoute:
def __init__(self, name : str, runway : str, waypoints : list):
self.name = name
self.runway = runway
self.iaf = waypoints[0]
self.route = waypoints
def __str__(self):
return 'Name: ' + self.name + ', IAF: ' + self.iaf.name + ', RWY: ' + self.runway

7
aman/types/Inbound.py Normal file
View File

@@ -0,0 +1,7 @@
#!/usr/bin/env python
from aman.com import AircraftReport_pb2
class Inbound:
def __init__(self, report : AircraftReport_pb2.AircraftReport):
self.report = report

View File

@@ -0,0 +1,18 @@
#!/usr/bin/env python
class PerformanceData:
def __init__(self, icao : str):
self.icao = icao
self.speedAboveFL240 = 0.0
self.speedAboveFL100 = 0.0
self.speedBelowFL100 = 0.0
self.speedApproach = 0.0
self.rodAboveFL240 = 0.0
self.rodAboveFL100 = 0.0
self.rodBelowFL100 = 2000.0
def __str__(self):
return 'ICAO: ' + self.icao + ', FL240: ' + str(self.rodAboveFL240) + '@' + str(self.speedAboveFL240) + \
', +FL100: ' + str(self.rodAboveFL100) + '@' + str(self.speedAboveFL100) + \
', -FL100: ' + str(self.rodBelowFL100) + '@' + str(self.speedBelowFL100) + \
', Vapp: ' + str(self.speedApproach)

33
aman/types/Waypoint.py Normal file
View File

@@ -0,0 +1,33 @@
#!/usr/bin/env python
from sklearn.metrics.pairwise import haversine_distances
import numpy as np
class Waypoint:
def dms2dd(coordinate : str):
split = coordinate.split('.')
if 4 != len(split):
return 0.0
direction = split[0][1]
degrees = float(split[0][1:])
minutes = float(split[1])
seconds = float(split[2]) * (float(split[3]) / 1000.0)
dd = degrees + minutes / 60.0 + seconds / (60 * 60)
if 'E' == direction or 'S' == direction:
dd *= -1.0
return dd
def __init__(self, name : str, latitude : float, longitude : float):
self.name = name
self.coordinate = np.array([ latitude, longitude ])
def __str__(self):
return 'Name: ' + self.name + ', Lat: ' + str(self.coordinate[0]) + ', Lon: ' + str(self.coordinate[1])
def haversine(self, other):
self_radians = [np.radians(_) for _ in self.coordinate]
other_radians = [np.radians(_) for _ in other.coordinate]
return 6371.0 * haversine_distances([self_radians, other_radians])[0][1]

0
aman/types/__init__.py Normal file
View File

BIN
external/bin/protoc.exe vendored Normal file

Binary file not shown.

32
external/licenses/ProtoBuf-3.18.1 vendored Normal file
View File

@@ -0,0 +1,32 @@
Copyright 2008 Google Inc. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the
distribution.
* Neither the name of Google Inc. nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
Code generated by the Protocol Buffer compiler is owned by the owner
of the input file used when generating it. This code is not
standalone and requires a support library to be linked with it. This
support library is itself covered by the above license.

22
icao/recat.py Normal file
View File

@@ -0,0 +1,22 @@
# 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 Normal file
View File

@@ -0,0 +1,10 @@
from tcp.TCPServer import TCPServer
def main():
server = TCPServer()
server.run()
if __name__ == "__main__":
main()

101
setup.py Normal file
View File

@@ -0,0 +1,101 @@
#!/usr/bin/env python
import os
import re
import shutil
import subprocess
import sys
from setuptools import setup
from distutils.command.clean import clean as _clean
from distutils.command.build_py import build_py_2to3 as _build_py
# @brief Creates Protobuf python files to encode and decode messages
# @param[in] source The protobuf source file
def generateProtobuf(source):
output = source.replace('.proto', '_pb2.py').replace('src/protobuf/', 'aman/com/')
if (not os.path.exists(output) or (os.path.exists(source) and os.path.getmtime(source) > os.path.getmtime(output))):
print('Generating %s...' % output)
if not os.path.exists(source):
sys.stderr.write('Cannot find %s' % source)
sys.exit(-1)
if not os.path.exists('external/bin/protoc.exe'):
sys.stderr.write('Cannot find proto-compiler')
sys.exit(-1)
command = [ 'external/bin/protoc.exe', '-Isrc/protobuf/', '-I.', '--python_out=aman/com/', source]
if 0 != subprocess.call(command):
sys.exit(-1)
# check if we need to replace some import commands
replaced = False
content = open(output, 'r').read()
for entry in re.findall('import.[A-Z].*.as.*', content):
content = content.replace(entry, 'from . ' + entry)
replaced = True
# update the content
if replaced:
with open(output, 'w') as file:
file.write(content)
# @brief Cleans up all auto-generated files and folders
# @param[in] _clean Instance of setuptools to clean up the system
class clean(_clean):
def run(self):
for (dirpath, dirnames, filenames) in os.walk('.'):
for filename in filenames:
filepath = os.path.join(dirpath, filename)
if filepath.endswith('_pb2.py') or filepath.endswith('.pyc'):
os.remove(filepath)
for dirname in dirnames:
if 'Arrival_MANager.egg-info' == dirname or 'build' == dirname or 'dist' == dirname or '__pycache__' == dirname:
shutil.rmtree(os.path.join(dirpath, dirname))
_clean.run(self)
# @brief Generates the python files and folders to set up the development/runtime environment
# @param[in] _build_py Instance of setuptools to build the system
class build_py(_build_py):
def run(self):
generateProtobuf('src/protobuf/Aircraft.proto')
generateProtobuf('src/protobuf/AircraftReport.proto')
generateProtobuf('src/protobuf/AircraftSchedule.proto')
generateProtobuf('src/protobuf/BaseTypes.proto')
_build_py.run(self)
with open('README.md', 'r') as f:
longDescription = f.read()
setup(
name = 'Arrival MANager',
version = '0.1.0',
packages = [
'aman',
'aman.com',
'aman.config',
'aman.formats',
'aman.sys',
'aman.tools',
'aman.types'
],
namespace_packages = [ 'aman' ],
description = 'AMAN optimization backend',
long_description = longDescription,
author = 'Sven Czarnian',
author_email = 'devel@svcz.de',
license = 'GPLv3',
cmdclass = { 'clean': clean, 'build_py': build_py },
install_requires=[
'argparse',
'bs4',
'configparser',
'numpy',
'protobuf',
'pyzmq',
'scikit-learn',
'scipy',
'setuptools'
]
)

1
src/protobuf Submodule

Submodule src/protobuf added at 0c5ed87078