Merge branch 'feature/setup' into 'develop'
Feature/setup See merge request nav/aman/aman-sys!1
This commit is contained in:
8
.gitignore
vendored
Normal file
8
.gitignore
vendored
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
Backend.ini
|
||||||
|
main.py
|
||||||
|
.vscode/
|
||||||
|
*_pb2.py
|
||||||
|
__pycache__
|
||||||
|
*.egg-info
|
||||||
|
build/
|
||||||
|
dist/
|
||||||
4
.gitmodules
vendored
Normal file
4
.gitmodules
vendored
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
[submodule "src/protobuf"]
|
||||||
|
path = src/protobuf
|
||||||
|
url = git@git.vatsim-germany.org:nav/aman-com.git
|
||||||
|
branch = feature/protobuf
|
||||||
31
README.md
31
README.md
@@ -1,7 +1,23 @@
|
|||||||
# aman-sys
|
# Arrival MANanager (AMAN)
|
||||||
|
|
||||||
Dieses Repository stellt den VATGER AMAN Server bereit, dessen Aufgabe es ist, einen optimalen Arrivalflow für die Flugplätze in der VACC zu errechnen.
|
## 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
|
## RHC-ACS-ASS Algorithm
|
||||||
Step 1: Initialization. Set up parameters for
|
Step 1: Initialization. Set up parameters for
|
||||||
@@ -41,4 +57,13 @@ 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
|
the best solution. Moreover, the current best solution is compared with the historically best solution
|
||||||
to determine the historically best solution.
|
to determine the historically best solution.
|
||||||
|
|
||||||
Step 3.4: Perform the global pheromone updating as (10).
|
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)
|
||||||
|
|||||||
82
aman/AMAN.py
Normal file
82
aman/AMAN.py
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
|
||||||
|
import glob
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
|
from aman.com import AircraftReport_pb2
|
||||||
|
from aman.com.Euroscope import Euroscope
|
||||||
|
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.workers = []
|
||||||
|
self.inbounds = {}
|
||||||
|
|
||||||
|
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')
|
||||||
|
|
||||||
|
# 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(icao, airportConfig)
|
||||||
|
worker.start()
|
||||||
|
self.workers.append(worker)
|
||||||
|
print('Starter worker for ' + icao)
|
||||||
|
|
||||||
|
# create the EuroScope receiver
|
||||||
|
self.receiver = Euroscope(configPath, self.systemConfig.Server, self)
|
||||||
|
|
||||||
|
def __del__(self):
|
||||||
|
if None != self.receiver:
|
||||||
|
del self.receiver
|
||||||
|
self.receiver = None
|
||||||
|
|
||||||
|
for worker in self.workers:
|
||||||
|
worker.stop()
|
||||||
|
|
||||||
|
def updateAircraftReport(self, report : AircraftReport_pb2.AircraftReport):
|
||||||
|
# find the correct worker for the inbound
|
||||||
|
for worker in self.workers:
|
||||||
|
if worker.icao == report.destination:
|
||||||
|
print('Updated ' + report.aircraft.callsign + ' for ' + worker.icao)
|
||||||
|
worker.acquire()
|
||||||
|
worker.reportQueue[report.aircraft.callsign] = report
|
||||||
|
worker.release()
|
||||||
|
break
|
||||||
2
aman/__init__.py
Normal file
2
aman/__init__.py
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
import com
|
||||||
|
import tools
|
||||||
104
aman/com/Euroscope.py
Normal file
104
aman/com/Euroscope.py
Normal file
@@ -0,0 +1,104 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
|
||||||
|
import ctypes
|
||||||
|
import glob
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import threading
|
||||||
|
import time
|
||||||
|
|
||||||
|
import zmq
|
||||||
|
import zmq.auth
|
||||||
|
|
||||||
|
from aman.com import AircraftReport_pb2
|
||||||
|
from aman.config.Server import Server
|
||||||
|
|
||||||
|
class ReceiverThread(threading.Thread):
|
||||||
|
def __init__(self, socket, aman):
|
||||||
|
threading.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 threading._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:
|
||||||
|
# @brief Initializes the ZMQ socket
|
||||||
|
# @param[in] config The server configuration
|
||||||
|
def __init__(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 at 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 at tcp://' + config.Address + ':' + str(config.PortNotification))
|
||||||
|
|
||||||
|
def __del__(self):
|
||||||
|
self.receiverThread.stopThread()
|
||||||
|
self.receiverThread.join()
|
||||||
|
self.receiverSocket.close()
|
||||||
|
self.notificationSocket.close()
|
||||||
0
aman/com/__init__.py
Normal file
0
aman/com/__init__.py
Normal file
28
aman/config/AircraftPerformance.py
Normal file
28
aman/config/AircraftPerformance.py
Normal 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
66
aman/config/Airport.py
Normal 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
29
aman/config/Server.py
Normal 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)
|
||||||
23
aman/config/System.py
Normal file
23
aman/config/System.py
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
|
||||||
|
import configparser
|
||||||
|
import sys
|
||||||
|
|
||||||
|
from aman.config.Server import Server
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
if not serverSectionAvailable:
|
||||||
|
sys.stderr.write('No server-configuration section found!')
|
||||||
|
sys.exit(-1)
|
||||||
|
|
||||||
|
self.Server = Server(config['SERVER'])
|
||||||
0
aman/config/__init__.py
Normal file
0
aman/config/__init__.py
Normal file
152
aman/formats/SctEseFormat.py
Normal file
152
aman/formats/SctEseFormat.py
Normal 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
0
aman/formats/__init__.py
Normal file
33
aman/sys/Worker.py
Normal file
33
aman/sys/Worker.py
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
|
||||||
|
from threading import Thread, Lock
|
||||||
|
import time
|
||||||
|
|
||||||
|
from aman.config.Airport import Airport
|
||||||
|
|
||||||
|
class Worker(Thread):
|
||||||
|
def __init__(self, icao : str, configuration : Airport):
|
||||||
|
Thread.__init__(self)
|
||||||
|
|
||||||
|
self.stopThread = None
|
||||||
|
self.icao = icao
|
||||||
|
self.configuration = configuration
|
||||||
|
self.arrivalRoutes = configuration.gngData.arrivalRoutes
|
||||||
|
self.updateLock = Lock()
|
||||||
|
self.reportQueue = {}
|
||||||
|
|
||||||
|
def stop(self):
|
||||||
|
self.stopThread = True
|
||||||
|
|
||||||
|
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
0
aman/sys/__init__.py
Normal file
44
aman/tools/KeyPairCreator.py
Normal file
44
aman/tools/KeyPairCreator.py
Normal 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)
|
||||||
151
aman/tools/SkybraryAircraftCrawler.py
Normal file
151
aman/tools/SkybraryAircraftCrawler.py
Normal 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
0
aman/tools/__init__.py
Normal file
11
aman/types/ArrivalRoute.py
Normal file
11
aman/types/ArrivalRoute.py
Normal 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
7
aman/types/Inbound.py
Normal 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
|
||||||
18
aman/types/PerformanceData.py
Normal file
18
aman/types/PerformanceData.py
Normal 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
33
aman/types/Waypoint.py
Normal 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
0
aman/types/__init__.py
Normal file
BIN
external/bin/protoc.exe
vendored
Normal file
BIN
external/bin/protoc.exe
vendored
Normal file
Binary file not shown.
32
external/licenses/ProtoBuf-3.17.3
vendored
Normal file
32
external/licenses/ProtoBuf-3.17.3
vendored
Normal 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.
|
||||||
101
setup.py
Normal file
101
setup.py
Normal 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
1
src/protobuf
Submodule
Submodule src/protobuf added at 3c74c96cfd
Reference in New Issue
Block a user