Merge branch 'feature/weather' into 'develop'
Feature/weather See merge request nav/aman/aman-sys!2
This commit is contained in:
@@ -1,18 +0,0 @@
|
|||||||
# RHC-ACS-ASS Algorithm
|
|
||||||
# This class is written to contain an implementation
|
|
||||||
# of the Ant Colony System based upon the Receding Horizon
|
|
||||||
# for Aircraft Arrival Sequencing
|
|
||||||
|
|
||||||
class RhcAcsAss:
|
|
||||||
k = 1 # Receding horizon counter
|
|
||||||
N_rhc = 4 # The number of receding horizons
|
|
||||||
T_ti = 1 # The scheduling window
|
|
||||||
|
|
||||||
def __init__(self, n, t):
|
|
||||||
self.N_rhc = n
|
|
||||||
self.T_ti = t
|
|
||||||
|
|
||||||
def find_aircraft_for_horizon(self, ki):
|
|
||||||
# Omega(k) = [(k-1) * T_ti, (k+N_rhc-1) * T_ti]
|
|
||||||
pass
|
|
||||||
|
|
||||||
35
aman/AMAN.py
35
aman/AMAN.py
@@ -6,6 +6,7 @@ import sys
|
|||||||
|
|
||||||
from aman.com import AircraftReport_pb2
|
from aman.com import AircraftReport_pb2
|
||||||
from aman.com.Euroscope import Euroscope
|
from aman.com.Euroscope import Euroscope
|
||||||
|
from aman.com.Weather import Weather
|
||||||
from aman.config.AircraftPerformance import AircraftPerformance
|
from aman.config.AircraftPerformance import AircraftPerformance
|
||||||
from aman.config.Airport import Airport
|
from aman.config.Airport import Airport
|
||||||
from aman.config.System import System
|
from aman.config.System import System
|
||||||
@@ -29,9 +30,14 @@ class AMAN:
|
|||||||
self.systemConfig = None
|
self.systemConfig = None
|
||||||
self.aircraftPerformance = None
|
self.aircraftPerformance = None
|
||||||
self.receiver = None
|
self.receiver = None
|
||||||
|
self.weather = None
|
||||||
self.workers = []
|
self.workers = []
|
||||||
self.inbounds = {}
|
self.inbounds = {}
|
||||||
|
|
||||||
|
def __del__(self):
|
||||||
|
self.release()
|
||||||
|
|
||||||
|
def aquire(self):
|
||||||
configPath = AMAN.findConfigPath()
|
configPath = AMAN.findConfigPath()
|
||||||
|
|
||||||
# read all system relevant configuration files
|
# read all system relevant configuration files
|
||||||
@@ -46,6 +52,9 @@ class AMAN:
|
|||||||
else:
|
else:
|
||||||
print('Parsed PerformanceData.ini. Extracted ' + str(len(self.aircraftPerformance.aircrafts)) + ' aircrafts')
|
print('Parsed PerformanceData.ini. Extracted ' + str(len(self.aircraftPerformance.aircrafts)) + ' aircrafts')
|
||||||
|
|
||||||
|
self.weather = Weather()
|
||||||
|
self.weather.acquire(self.systemConfig.Weather)
|
||||||
|
|
||||||
# find the airport configurations and create the workers
|
# find the airport configurations and create the workers
|
||||||
airportsPath = os.path.join(os.path.join(configPath, 'airports'), '*.ini')
|
airportsPath = os.path.join(os.path.join(configPath, 'airports'), '*.ini')
|
||||||
for file in glob.glob(airportsPath):
|
for file in glob.glob(airportsPath):
|
||||||
@@ -55,28 +64,34 @@ class AMAN:
|
|||||||
airportConfig = Airport(file, icao)
|
airportConfig = Airport(file, icao)
|
||||||
|
|
||||||
# initialize the worker thread
|
# initialize the worker thread
|
||||||
worker = Worker(icao, airportConfig)
|
worker = Worker()
|
||||||
worker.start()
|
worker.acquire(icao, airportConfig)
|
||||||
self.workers.append(worker)
|
self.workers.append(worker)
|
||||||
print('Starter worker for ' + icao)
|
print('Started worker for ' + icao)
|
||||||
|
|
||||||
# create the EuroScope receiver
|
# create the EuroScope receiver
|
||||||
self.receiver = Euroscope(configPath, self.systemConfig.Server, self)
|
self.receiver = Euroscope()
|
||||||
|
self.receiver.acquire(configPath, self.systemConfig.Server, self)
|
||||||
|
|
||||||
def __del__(self):
|
def release(self):
|
||||||
if None != self.receiver:
|
if None != self.receiver:
|
||||||
del self.receiver
|
self.receiver.release()
|
||||||
self.receiver = None
|
self.receiver = None
|
||||||
|
|
||||||
|
if None != self.weather:
|
||||||
|
self.weather.release()
|
||||||
|
self.weather = None
|
||||||
|
|
||||||
|
if None != self.workers:
|
||||||
for worker in self.workers:
|
for worker in self.workers:
|
||||||
worker.stop()
|
worker.release()
|
||||||
|
self.workers = None
|
||||||
|
|
||||||
def updateAircraftReport(self, report : AircraftReport_pb2.AircraftReport):
|
def updateAircraftReport(self, report : AircraftReport_pb2.AircraftReport):
|
||||||
# find the correct worker for the inbound
|
# find the correct worker for the inbound
|
||||||
for worker in self.workers:
|
for worker in self.workers:
|
||||||
if worker.icao == report.destination:
|
if worker.icao == report.destination:
|
||||||
print('Updated ' + report.aircraft.callsign + ' for ' + worker.icao)
|
worker.acquireLock()
|
||||||
worker.acquire()
|
|
||||||
worker.reportQueue[report.aircraft.callsign] = report
|
worker.reportQueue[report.aircraft.callsign] = report
|
||||||
worker.release()
|
worker.releaseLock()
|
||||||
break
|
break
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
import com
|
|
||||||
import tools
|
|
||||||
157
aman/com/DwdCrawler.py
Normal file
157
aman/com/DwdCrawler.py
Normal 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
|
||||||
@@ -4,7 +4,6 @@ import ctypes
|
|||||||
import glob
|
import glob
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
import threading
|
|
||||||
import time
|
import time
|
||||||
|
|
||||||
import zmq
|
import zmq
|
||||||
@@ -12,10 +11,11 @@ import zmq.auth
|
|||||||
|
|
||||||
from aman.com import AircraftReport_pb2
|
from aman.com import AircraftReport_pb2
|
||||||
from aman.config.Server import Server
|
from aman.config.Server import Server
|
||||||
|
from threading import Thread, _active
|
||||||
|
|
||||||
class ReceiverThread(threading.Thread):
|
class ReceiverThread(Thread):
|
||||||
def __init__(self, socket, aman):
|
def __init__(self, socket, aman):
|
||||||
threading.Thread.__init__(self)
|
Thread.__init__(self)
|
||||||
self.socket = socket
|
self.socket = socket
|
||||||
self.aman = aman
|
self.aman = aman
|
||||||
|
|
||||||
@@ -41,7 +41,7 @@ class ReceiverThread(threading.Thread):
|
|||||||
def threadId(self):
|
def threadId(self):
|
||||||
if hasattr(self, '_thread_id'):
|
if hasattr(self, '_thread_id'):
|
||||||
return self._thread_id
|
return self._thread_id
|
||||||
for id, thread in threading._active.items():
|
for id, thread in _active.items():
|
||||||
if thread is self:
|
if thread is self:
|
||||||
return id
|
return id
|
||||||
|
|
||||||
@@ -53,9 +53,18 @@ class ReceiverThread(threading.Thread):
|
|||||||
|
|
||||||
# @brief Receives and sends messages to EuroScope plugins
|
# @brief Receives and sends messages to EuroScope plugins
|
||||||
class Euroscope:
|
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
|
# @brief Initializes the ZMQ socket
|
||||||
# @param[in] config The server configuration
|
# @param[in] config The server configuration
|
||||||
def __init__(self, configPath : str, config : Server, aman):
|
def acquire(self, configPath : str, config : Server, aman):
|
||||||
self.context = zmq.Context()
|
self.context = zmq.Context()
|
||||||
|
|
||||||
# find the key directories
|
# find the key directories
|
||||||
@@ -87,7 +96,7 @@ class Euroscope:
|
|||||||
self.receiverSocket.setsockopt(zmq.SUBSCRIBE, b'')
|
self.receiverSocket.setsockopt(zmq.SUBSCRIBE, b'')
|
||||||
self.receiverThread = ReceiverThread(self.receiverSocket, aman)
|
self.receiverThread = ReceiverThread(self.receiverSocket, aman)
|
||||||
self.receiverThread.start()
|
self.receiverThread.start()
|
||||||
print('Listening at tcp://' + config.Address + ':' + str(config.PortReceiver))
|
print('Listening to tcp://' + config.Address + ':' + str(config.PortReceiver))
|
||||||
|
|
||||||
# initialize the notification
|
# initialize the notification
|
||||||
self.notificationSocket = zmq.Socket(self.context, zmq.PUB)
|
self.notificationSocket = zmq.Socket(self.context, zmq.PUB)
|
||||||
@@ -95,10 +104,18 @@ class Euroscope:
|
|||||||
self.notificationSocket.setsockopt(zmq.CURVE_SECRETKEY, keyPair[1])
|
self.notificationSocket.setsockopt(zmq.CURVE_SECRETKEY, keyPair[1])
|
||||||
self.notificationSocket.setsockopt(zmq.CURVE_SERVER, True)
|
self.notificationSocket.setsockopt(zmq.CURVE_SERVER, True)
|
||||||
self.notificationSocket.bind('tcp://' + config.Address + ':' + str(config.PortNotification))
|
self.notificationSocket.bind('tcp://' + config.Address + ':' + str(config.PortNotification))
|
||||||
print('Publishing at tcp://' + config.Address + ':' + str(config.PortNotification))
|
print('Publishing to tcp://' + config.Address + ':' + str(config.PortNotification))
|
||||||
|
|
||||||
def __del__(self):
|
def release(self):
|
||||||
|
if None != self.receiverThread:
|
||||||
self.receiverThread.stopThread()
|
self.receiverThread.stopThread()
|
||||||
self.receiverThread.join()
|
self.receiverThread.join()
|
||||||
|
self.receiverThread = None
|
||||||
|
|
||||||
|
if None != self.receiverSocket:
|
||||||
self.receiverSocket.close()
|
self.receiverSocket.close()
|
||||||
|
self.receiverSocket = None
|
||||||
|
|
||||||
|
if None != self.notificationSocket:
|
||||||
self.notificationSocket.close()
|
self.notificationSocket.close()
|
||||||
|
self.notificationSocket = None
|
||||||
|
|||||||
56
aman/com/Weather.py
Normal file
56
aman/com/Weather.py
Normal 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')
|
||||||
@@ -4,6 +4,7 @@ import configparser
|
|||||||
import sys
|
import sys
|
||||||
|
|
||||||
from aman.config.Server import Server
|
from aman.config.Server import Server
|
||||||
|
from aman.config.Weather import Weather
|
||||||
|
|
||||||
class System:
|
class System:
|
||||||
def __init__(self, filepath : str):
|
def __init__(self, filepath : str):
|
||||||
@@ -15,9 +16,15 @@ class System:
|
|||||||
for key in config:
|
for key in config:
|
||||||
if 'SERVER' == key:
|
if 'SERVER' == key:
|
||||||
serverSectionAvailable = True
|
serverSectionAvailable = True
|
||||||
|
elif 'WEATHER' == key:
|
||||||
|
weatherSectionAvailable = True
|
||||||
|
|
||||||
if not serverSectionAvailable:
|
if not serverSectionAvailable:
|
||||||
sys.stderr.write('No server-configuration section found!')
|
sys.stderr.write('No server-configuration section found!')
|
||||||
sys.exit(-1)
|
sys.exit(-1)
|
||||||
|
if not weatherSectionAvailable:
|
||||||
|
sys.stderr.write('No weather-configuration section found!')
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
self.Server = Server(config['SERVER'])
|
self.Server = Server(config['SERVER'])
|
||||||
|
self.Weather = Weather(config['WEATHER'])
|
||||||
|
|||||||
19
aman/config/Weather.py
Normal file
19
aman/config/Weather.py
Normal 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)
|
||||||
148
aman/sys/WeatherModel.py
Normal file
148
aman/sys/WeatherModel.py
Normal 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))
|
||||||
@@ -6,18 +6,38 @@ import time
|
|||||||
from aman.config.Airport import Airport
|
from aman.config.Airport import Airport
|
||||||
|
|
||||||
class Worker(Thread):
|
class Worker(Thread):
|
||||||
def __init__(self, icao : str, configuration : Airport):
|
def __init__(self):
|
||||||
Thread.__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.stopThread = None
|
||||||
self.icao = icao
|
self.icao = icao
|
||||||
self.configuration = configuration
|
self.configuration = configuration
|
||||||
self.arrivalRoutes = configuration.gngData.arrivalRoutes
|
self.arrivalRoutes = configuration.gngData.arrivalRoutes
|
||||||
self.updateLock = Lock()
|
self.updateLock = Lock()
|
||||||
self.reportQueue = {}
|
self.reportQueue = {}
|
||||||
|
self.start()
|
||||||
|
|
||||||
def stop(self):
|
def acquireLock(self):
|
||||||
|
if None != self.updateLock:
|
||||||
|
self.updateLock.acquire()
|
||||||
|
|
||||||
|
def release(self):
|
||||||
self.stopThread = True
|
self.stopThread = True
|
||||||
|
self.join()
|
||||||
|
|
||||||
|
def releaseLock(self):
|
||||||
|
if None != self.updateLock:
|
||||||
|
self.updateLock.release()
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
counter = 0
|
counter = 0
|
||||||
|
|||||||
BIN
external/bin/protoc.exe
vendored
BIN
external/bin/protoc.exe
vendored
Binary file not shown.
Submodule src/protobuf updated: 3c74c96cfd...0c5ed87078
@@ -1,51 +0,0 @@
|
|||||||
import socket
|
|
||||||
import _thread
|
|
||||||
|
|
||||||
|
|
||||||
class TCPServer(socket.socket):
|
|
||||||
clients = []
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
socket.socket.__init__(self)
|
|
||||||
self.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
|
||||||
self.bind(('0.0.0.0', 8765))
|
|
||||||
self.listen(5)
|
|
||||||
|
|
||||||
def run(self):
|
|
||||||
print("Starting TCP Server")
|
|
||||||
try:
|
|
||||||
self.accept_clients()
|
|
||||||
except Exception as ex:
|
|
||||||
print(ex)
|
|
||||||
finally:
|
|
||||||
print("Server shutdown")
|
|
||||||
for client in self.clients:
|
|
||||||
client.close()
|
|
||||||
self.close()
|
|
||||||
|
|
||||||
def accept_clients(self):
|
|
||||||
while True:
|
|
||||||
(client_socket, address) = self.accept()
|
|
||||||
self.clients.append(client_socket)
|
|
||||||
self.on_open(client_socket)
|
|
||||||
_thread.start_new_thread(self.receive, (client_socket,))
|
|
||||||
|
|
||||||
def receive(self, client):
|
|
||||||
while True:
|
|
||||||
data = client.recv(1024)
|
|
||||||
if data == '':
|
|
||||||
break
|
|
||||||
self.on_message(client, data)
|
|
||||||
self.clients.remove(client)
|
|
||||||
self.on_close(client)
|
|
||||||
client.close()
|
|
||||||
_thread.exit()
|
|
||||||
|
|
||||||
def on_open(self, client):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def on_message(self, client, message):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def on_close(self, client):
|
|
||||||
pass
|
|
||||||
Reference in New Issue
Block a user