-
Notifications
You must be signed in to change notification settings - Fork 19
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #20 from cwebster2/feature/aircraft-data
Feature/aircraft data
- Loading branch information
Showing
9 changed files
with
3,234 additions
and
13 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1 @@ | ||
0.7 | ||
0.8 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
#!/usr/bin/env python | ||
|
||
import sys, os | ||
import matplotlib | ||
matplotlib.use('qt5agg') | ||
from PyQt5 import QtWidgets, QtGui, QtCore | ||
from pymeteo.UI.acars import MainWindow | ||
|
||
def main(): | ||
app = QtWidgets.QApplication(sys.argv) | ||
w = MainWindow() | ||
sys.exit(app.exec_()) | ||
|
||
if __name__ == '__main__': | ||
main() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,146 @@ | ||
from PyQt5 import QtWidgets, QtGui, QtCore | ||
from PyQt5.QtCore import pyqtSignal, pyqtSlot | ||
from pymeteo.data import acars | ||
import pymeteo.cm1.PlotWidget as PW | ||
import pymeteo.cm1.StatWidget as SW | ||
|
||
class MainWindow(QtWidgets.QWidget): | ||
|
||
def __init__(self): | ||
super(MainWindow, self).__init__() | ||
self.initUI() | ||
|
||
def initUI(self): | ||
self.splitter_2 = QtWidgets.QSplitter(QtCore.Qt.Horizontal) | ||
|
||
self.LVert_widget = QtWidgets.QWidget(self.splitter_2) | ||
self.verticalLayout = QtWidgets.QVBoxLayout(self.LVert_widget) | ||
self.horizontalLayout = QtWidgets.QHBoxLayout() | ||
self.label = QtWidgets.QLabel('Output file:',self.LVert_widget) | ||
self.horizontalLayout.addWidget(self.label) | ||
self.lineEdit = QtWidgets.QLineEdit(self.LVert_widget) | ||
self.horizontalLayout.addWidget(self.lineEdit) | ||
self.pushButton = QtWidgets.QPushButton("Export", self.LVert_widget) | ||
self.horizontalLayout.addWidget(self.pushButton) | ||
self.verticalLayout.addLayout(self.horizontalLayout) | ||
self.line = QtWidgets.QFrame(self.LVert_widget) | ||
self.line.setFrameShape(QtWidgets.QFrame.HLine) | ||
self.line.setFrameShadow(QtWidgets.QFrame.Sunken) | ||
self.verticalLayout.addWidget(self.line) | ||
|
||
self.label2 = QtWidgets.QLabel('Select ACARS data') | ||
self.verticalLayout.addWidget(self.label2) | ||
|
||
self.acarsFiles = QtWidgets.QComboBox(self) | ||
self.verticalLayout.addWidget(self.acarsFiles) | ||
|
||
self.line_2 = QtWidgets.QFrame(self.LVert_widget) | ||
self.line_2.setFrameShape(QtWidgets.QFrame.HLine) | ||
self.line_2.setFrameShadow(QtWidgets.QFrame.Sunken) | ||
self.verticalLayout.addWidget(self.line_2) | ||
|
||
self.label3 = QtWidgets.QLabel('Select Profile') | ||
self.verticalLayout.addWidget(self.label3) | ||
self.profiles = QtWidgets.QComboBox(self) | ||
self.verticalLayout.addWidget(self.profiles) | ||
|
||
self.verticalLayout.addStretch(1) | ||
|
||
self.drawButton = QtWidgets.QPushButton("Update Plot", self.LVert_widget) | ||
self.quitButton = QtWidgets.QPushButton("Quit", self.LVert_widget) | ||
self.verticalLayout.addWidget(self.drawButton) | ||
self.verticalLayout.addWidget(self.quitButton) | ||
self.splitter = QtWidgets.QSplitter(self.splitter_2) | ||
self.splitter.setOrientation(QtCore.Qt.Vertical) | ||
self.Hodograph = PW.PlotWidget(self.splitter) | ||
self.SoundingInfo = SW.StatWidget(self.splitter) | ||
self.Sounding = PW.PlotWidget(self.splitter_2) | ||
|
||
datasets = acars.getAvailableDatasets() | ||
self.acarsFiles.addItems(datasets) | ||
self.acarsFiles.activated[str].connect(self.processDataset) | ||
|
||
self.profiles.activated.connect(self.displayProfile) | ||
|
||
self.Sounding.SStats.connect(self.SoundingInfo.SStat_values) | ||
|
||
self.pushButton.clicked.connect(self.export) | ||
self.exportSounding.connect(self.SoundingInfo.output_sounding) | ||
|
||
self.drawButton.clicked.connect(self.update_plot) | ||
self.quitButton.clicked.connect(self.close) | ||
|
||
self.hbox = QtWidgets.QHBoxLayout(self) | ||
self.hbox.addWidget(self.splitter_2) | ||
self.setLayout(self.hbox) | ||
|
||
self.resize(1200,768) | ||
|
||
#TODO: size widgets initially. This doesnt work | ||
self.LVert_widget.resize(250,500) | ||
self.Hodograph.resize(250,250) | ||
self.SoundingInfo.resize(250,250) | ||
self.Sounding.resize(250,500) | ||
|
||
|
||
self.center() | ||
self.setWindowTitle('ACARS Profile Plotting Tool') | ||
|
||
self.show() | ||
self.Sounding.plot_sounding_axes() | ||
self.Hodograph.plot_hodograph_axes() | ||
self.SoundingInfo.calcStats() | ||
|
||
self.SoundingInfo.statsdone.connect(self.enableButton) | ||
|
||
def center(self): | ||
qr = self.frameGeometry() | ||
cp = QtWidgets.QDesktopWidget().availableGeometry().center() | ||
qr.moveCenter(cp) | ||
self.move(qr.topLeft()) | ||
|
||
def profileDesc(self,profile): | ||
profileDir = "UP" if profile["flag"] == 1 else "DOWN" | ||
return "{0}: {1}({2}) {3}".format(profile["i"], profile["airport"], profileDir, profile["time"]) | ||
|
||
def processDataset(self, acarsFile): | ||
print("[+] Getting Data") | ||
self.profiles.clear() | ||
self.data = acars.processDataSet(acars.getDataSet(acarsFile)) | ||
for profile in self.data: | ||
desc = self.profileDesc(profile) | ||
print("[-] Adding profile {0}".format(desc)) | ||
self.profiles.addItem(desc) | ||
|
||
def displayProfile(self, index): | ||
print("[+] Displaying profile {0}".format(index)) | ||
profile = self.data[index] | ||
desc = self.profileDesc(profile) | ||
print("[-] {0}".format(desc)) | ||
self.Sounding.plot_sounding( | ||
profile["z"], | ||
profile["th"], | ||
profile["p"], | ||
profile["qv"], | ||
profile["u"], | ||
profile["v"] | ||
) | ||
self.Hodograph.plot_hodograph( | ||
profile["z"], | ||
profile["u"], | ||
profile["v"] | ||
) | ||
|
||
|
||
exportSounding = pyqtSignal(str) | ||
|
||
def export(self): | ||
self.exportSounding.emit(self.lineEdit.text()) | ||
|
||
def update_plot(self): | ||
self.drawButton.setEnabled(False) | ||
|
||
|
||
@pyqtSlot() | ||
def enableButton(self): | ||
self.drawButton.setEnabled(True) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,144 @@ | ||
# Acars data access | ||
# Thanks to Rich Mamrosh @ NOAA for pointing the availability of this data out to me | ||
|
||
import re | ||
import netCDF4 | ||
import gzip | ||
import io | ||
import tempfile | ||
import os | ||
import numpy as np | ||
from PyQt5 import QtWidgets, QtGui, QtCore | ||
from PyQt5.QtCore import pyqtSignal, pyqtSlot | ||
from datetime import datetime | ||
from pymeteo import dynamics, thermo | ||
try: | ||
# For Python 3.0 and later | ||
from urllib import request | ||
except ImportError: | ||
# Fall back to Python 2's urllib2 | ||
import urllib2 as request | ||
|
||
data_url = "https://madis-data.ncep.noaa.gov/madisPublic1/data/point/acars/netcdf/" | ||
airport_ids = {} | ||
|
||
def getAvailableDatasets(): | ||
# crawl links at https://madis-data.ncep.noaa.gov/madisPublic1/data/point/acars/netcdf/ | ||
req = request.Request(data_url) | ||
linkMatcher = re.compile(r"\"([0-9_]+\.gz)\"") | ||
try: | ||
print("[+] Fetching list of resources available") | ||
with request.urlopen(req) as f: | ||
print("[-] Parsing list") | ||
data = str(f.read()) | ||
sets = linkMatcher.findall(data) | ||
return sets | ||
except: | ||
print("error") | ||
|
||
def getDataSet(set): | ||
req = request.Request(data_url + set) | ||
try: | ||
print("[+] Fetching dataset {0}".format(set)) | ||
with request.urlopen(req) as f: | ||
compressedData = io.BytesIO(f.read()) # gzipped data | ||
print("[-] Decompressing response data") | ||
data = gzip.GzipFile(fileobj=compressedData) | ||
|
||
return data | ||
except: | ||
print("error") | ||
|
||
def getAirportByCode(airport_id): | ||
print("[+] Looking up airport id '{0}'".format(airport_id)) | ||
datfile = os.path.join(os.path.dirname(__file__), "airport_info.dat") | ||
if not bool(airport_ids): | ||
with open(datfile, "r") as f: | ||
for _, line in enumerate(f): | ||
fields = line.strip().split() | ||
airport_ids[int(fields[0])] = fields[1] | ||
return airport_ids[airport_id] | ||
|
||
def processDataSet(data): | ||
print("[+] Writing data into temporary file") | ||
tdata = tempfile.NamedTemporaryFile() | ||
tdata.write(data.read()) | ||
print("[-] Data written to {0}".format(tdata.name)) | ||
print("[+] Opening data as NetCDF") | ||
d = data.read() | ||
with netCDF4.Dataset(tdata.name, mode='r') as nc: | ||
print("[-] Dataset open with") | ||
|
||
_z = nc["altitude"][:] | ||
_T = nc["temperature"][:] | ||
_qv = nc["waterVaporMR"][:] | ||
windSpeed = nc["windSpeed"][:] | ||
windDir = nc["windDir"][:] | ||
_lon = nc["longitude"][:] | ||
_lat = nc["latitude"][:] | ||
flag = nc["sounding_flag"][:] | ||
_airport = nc["sounding_airport_id"][:] | ||
time = nc["soundingSecs"][:] | ||
|
||
print ("[-] {0} Records".format(len(_z))) | ||
#conversions | ||
_p = thermo.p_from_pressure_altitude(_z, _T) | ||
_u, _v = dynamics.wind_deg_to_uv(windDir, windSpeed) | ||
_th = thermo.theta(_T, _p) | ||
|
||
# split the arrays when the flag changes sign | ||
splits = np.where(np.diff(time))[0]+1 | ||
|
||
_z = np.split(_z, splits) | ||
_p = np.split(_p, splits) | ||
_th = np.split(_th, splits) | ||
_qv = np.split(_qv, splits) | ||
_u = np.split(_u, splits) | ||
_v = np.split(_v, splits) | ||
_lat = np.split(_lat, splits) | ||
_lon = np.split(_lon, splits) | ||
_airport = np.split(_airport, splits) | ||
time = np.split(time, splits) | ||
flag = np.split(flag, splits) | ||
|
||
print("[-] Found {0} profiles".format(len(_z))) | ||
|
||
#re-shape data | ||
outputData = [] | ||
for i in range(len(_z)): | ||
ts = time[i].compressed() | ||
if len(ts) == 0: | ||
# profiles without timestamps invalid? | ||
continue | ||
|
||
profileDir = flag[i][0] | ||
if (profileDir == 0): | ||
continue | ||
|
||
z = _z[i].filled() | ||
p = _p[i].filled() | ||
th = _th[i].filled() | ||
qv = _qv[i].filled() | ||
u = _u[i].filled() | ||
v = _v[i].filled() | ||
lat = _lat[i].filled() | ||
lon = _lon[i].filled() | ||
airport = getAirportByCode(_airport[i][0]) | ||
profileData = { | ||
"i": i, | ||
"n": len(z), | ||
"z": z if profileDir > 0 else z[::-1], | ||
"p": p if profileDir > 0 else p[::-1], | ||
"th": th if profileDir > 0 else th[::-1], | ||
"qv": qv if profileDir > 0 else qv[::-1], | ||
"u": u if profileDir > 0 else u[::-1], | ||
"v": v if profileDir > 0 else v[::-1], | ||
"lat": lat if profileDir > 0 else lat[::-1], | ||
"lon": lon if profileDir > 0 else lon[::-1], | ||
"airport": airport, | ||
"time": datetime.utcfromtimestamp(ts.mean()).strftime("%H%MZ"), | ||
"flag": profileDir | ||
} | ||
outputData.append(profileData) | ||
|
||
return outputData |
Oops, something went wrong.