Skip to content

Commit

Permalink
Merge pull request #20 from cwebster2/feature/aircraft-data
Browse files Browse the repository at this point in the history
Feature/aircraft data
  • Loading branch information
cwebster2 authored Nov 21, 2018
2 parents f028064 + 5c97cda commit 76d5d01
Show file tree
Hide file tree
Showing 9 changed files with 3,234 additions and 13 deletions.
2 changes: 1 addition & 1 deletion VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
0.7
0.8
15 changes: 15 additions & 0 deletions bin/plot-acars
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()
146 changes: 146 additions & 0 deletions pymeteo/UI/acars.py
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)
4 changes: 0 additions & 4 deletions pymeteo/cm1/PlotWidget.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,22 +36,19 @@ def plot_sounding_axes(self):
self.figure.clear()
ax = self.figure.add_axes([0.05, 0.05, 0.90, 0.945])
# ax = self.figure.add_axes([0.005,0.05,0.985,0.945])
ax.hold(True)
skewt.set_fontscalefactor(4)
skewt.plot_sounding_axes(ax)
self.canvas.draw()

def plot_hodograph_axes(self):
ax = self.figure.add_axes([0.005, 0.005, 0.985, 0.985])
ax.hold(True)
skewt.plot_hodo_axes(ax)
self.canvas.draw()

def plot_sounding(self, z, th, p, qv, u, v):
self.figure.clear()
# ax = self.figure.add_axes([0.005,0.05,0.985,0.945])
ax = self.figure.add_axes([0.05, 0.05, 0.90, 0.945])
ax.hold(True)
skewt.plot_sounding_axes(ax)
skewt.plot_sounding(ax, z, th, p, qv, u, v)
self.canvas.draw()
Expand All @@ -61,7 +58,6 @@ def plot_sounding(self, z, th, p, qv, u, v):
def plot_hodograph(self, z, u, v):
self.figure.clear()
ax = self.figure.add_axes([0.005, 0.05, 0.985, 0.945])
ax.hold(True)
skewt.plot_hodo_axes(ax)
skewt.plot_hodograph(ax, z, u, v)
self.canvas.draw()
144 changes: 144 additions & 0 deletions pymeteo/data/acars.py
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
Loading

0 comments on commit 76d5d01

Please sign in to comment.