diff --git a/apps/modes_gui b/apps/modes_gui index 86b4632..48368ed 100755 --- a/apps/modes_gui +++ b/apps/modes_gui @@ -85,6 +85,9 @@ class mainwindow(QtGui.QMainWindow): self.ui.list_aircraft.setModel(self.datamodel) self.ui.list_aircraft.setModelColumn(0) + self.az_model = air_modes.az_map_model(None) + self.ui.azimuth_map.setModel(self.az_model) + #set up dashboard views self.icaodelegate = ICAOViewDelegate() self.ui.list_aircraft.setItemDelegate(self.icaodelegate) @@ -276,9 +279,13 @@ class mainwindow(QtGui.QMainWindow): self.outputs.append(rawport.output) self.updates.append(rawport.add_pending_conns) + #add azimuth map output and hook it up + if my_position is not None: + self.az_map_output = air_modes.az_map_output(my_position, self.az_model) + self.outputs.append(self.az_map_output.output) + self.livedata = air_modes.output_print(my_position) #add output for live data box - #TODO: this doesn't handle large volumes of data well; i get segfaults. self.outputs.append(self.output_live_data) #create SQL database for KML and dashboard displays @@ -291,7 +298,7 @@ class mainwindow(QtGui.QMainWindow): #create output handler thread self.output_handler = output_handler(self.outputs, self.updates, self.queue) - + self.ui.button_start.setText("Stop") def on_quit(self): @@ -317,6 +324,13 @@ class mainwindow(QtGui.QMainWindow): #slot to catch signal emitted by output_live_data (necessary for #thread safety since output_live_data is called by another thread) def on_append_live_data(self, msgstr): + #limit scrollback buffer size -- is there a faster way? + if(self.ui.text_livedata.document().lineCount() > 500): + cursor = self.ui.text_livedata.textCursor() + cursor.movePosition(QtGui.QTextCursor.Start) + cursor.select(QtGui.QTextCursor.LineUnderCursor) + cursor.removeSelectedText() + self.ui.text_livedata.append(msgstr) self.ui.text_livedata.verticalScrollBar().setSliderPosition(self.ui.text_livedata.verticalScrollBar().maximum()) diff --git a/python/CMakeLists.txt b/python/CMakeLists.txt index d620f56..a5ec50d 100644 --- a/python/CMakeLists.txt +++ b/python/CMakeLists.txt @@ -32,6 +32,7 @@ GR_PYTHON_INSTALL( FILES __init__.py altitude.py + az_map.py cpr.py mlat.py exceptions.py diff --git a/python/__init__.py b/python/__init__.py index 9fc740f..dcdccc4 100644 --- a/python/__init__.py +++ b/python/__init__.py @@ -59,6 +59,7 @@ from sbs1 import output_sbs1 from kml import output_kml from raw_server import raw_server from exceptions import * +from az_map import * #this is try/excepted in case the user doesn't have numpy installed try: from flightgear import output_flightgear diff --git a/python/az_map.py b/python/az_map.py new file mode 100755 index 0000000..d19370e --- /dev/null +++ b/python/az_map.py @@ -0,0 +1,237 @@ +#!/usr/bin/env python +# +# Copyright 2012 Nick Foster +# +# This file is part of gr-air-modes +# +# gr-air-modes is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3, or (at your option) +# any later version. +# +# gr-air-modes is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with gr-air-modes; see the file COPYING. If not, write to +# the Free Software Foundation, Inc., 51 Franklin Street, +# Boston, MA 02110-1301, USA. +# + +# azimuthal projection widget to plot reception range vs. azimuth + +from PyQt4 import QtCore, QtGui +import threading +import math +import air_modes + + +# model has max range vs. azimuth in n-degree increments +# contains separate max range for a variety of altitudes so +# you can determine your altitude dropouts by bearing +# assumes that if you can hear ac at 1000', you can hear at 5000'+. +class az_map_model(QtCore.QObject): + dataChanged = QtCore.pyqtSignal(name='dataChanged') + npoints = 360/5 + def __init__(self, parent=None): + super(az_map_model, self).__init__(parent) + self._data = [] + self.lock = threading.Lock() + self._altitudes = [0, 1000, 2000, 5000, 10000, 15000, 20000, 25000, 30000] + #initialize everything to 0 + for i in range(0,az_map_model.npoints): + self._data.append([0] * len(self._altitudes)) + + def rowCount(self): + return len(self._data) + + def columnCount(self): + return len(self._altitudes) + + def data(self, row, col): + return self._data[row][col] + + def addRecord(self, bearing, altitude, distance): + with self.lock: + #round up to nearest altitude in altitudes list + #there's probably another way to do it + if altitude >= max(self._altitudes): + col = self.columnCount()-1 + else: + col = self._altitudes.index(min([alt for alt in self._altitudes if alt >= altitude])) + + #find which bearing row we sit in + row = int(int(bearing+(180./az_map_model.npoints)) / (360./az_map_model.npoints)) % az_map_model.npoints + #set max range for all alts >= the ac alt + #this expresses the assumption that higher ac can be heard further + update = False + for i in range(col, len(self._altitudes)): + if distance > self._data[row][i]: + self._data[row][i] = distance + update = True + if update: + self.dataChanged.emit() + + def reset(self): + with self.lock: + self._data = [] + for i in range(0,az_map_model.npoints): + self._data.append([0] * len(self._altitudes)) + self.dataChanged.emit() + + +# the azimuth map widget +class az_map(QtGui.QWidget): + maxrange = 450 + ringsize = 100 + bgcolor = QtCore.Qt.black + ringpen = QtGui.QPen(QtGui.QColor(0, 96, 127, 255), 1.3) + #rangepen = QtGui.QPen(QtGui.QColor(255, 255, 0, 255), 1.0) + + def __init__(self, parent=None): + super(az_map, self).__init__(parent) + self._model = None + self._paths = [] + self.maxrange = az_map.maxrange + self.ringsize = az_map.ringsize + + def minimumSizeHint(self): + return QtCore.QSize(50, 50) + + def sizeHint(self): + return QtCore.QSize(300, 300) + + def setModel(self, model): + self._model = model + self._model.dataChanged.connect(self.repaint) + + def paintEvent(self, event): + painter = QtGui.QPainter(self) + painter.setRenderHint(QtGui.QPainter.Antialiasing) + #TODO: make it not have to redraw paths EVERY repaint + #drawing paths is VERY SLOW + #maybe use a QTimer to limit repaints + self.drawPaths() + + #set background + painter.fillRect(event.rect(), QtGui.QBrush(az_map.bgcolor)) + + #draw the range rings + self.drawRangeRings(painter) + for i in range(len(self._paths)): + alpha = 230 * (i+1) / (len(self._paths)) + 25 + painter.setPen(QtGui.QPen(QtGui.QColor(alpha,alpha,0,255), 1.0)) + painter.drawPath(self._paths[i]) + + def drawPaths(self): + self._paths = [] + if(self._model): + for alt in range(0, self._model.columnCount()): + path = QtGui.QPainterPath() + for i in range(az_map_model.npoints-1,-1,-1): + #bearing is to start point of arc (clockwise) + bearing = (i+0.5) * 360./az_map_model.npoints + distance = self._model._data[i][alt] + radius = min(self.width(), self.height()) / 2.0 + scale = radius * distance / self.maxrange + #convert bearing,distance to x,y + xpts = scale * math.sin(bearing * math.pi / 180) + ypts = scale * math.cos(bearing * math.pi / 180) + #get the bounding rectangle of the arc + + arcrect = QtCore.QRectF(QtCore.QPointF(0-scale, 0-scale), + QtCore.QPointF(scale, scale)) + + if path.isEmpty(): + path.moveTo(xpts, 0-ypts) #so we don't get a line from 0,0 to the first point + else: + path.lineTo(xpts, 0-ypts) + path.arcTo(arcrect, 90-bearing, 360./az_map_model.npoints) + + self._paths.append(path) + + def drawRangeRings(self, painter): + painter.translate(self.width()/2, self.height()/2) + painter.setPen(az_map.ringpen) + for i in range(0, self.maxrange, self.ringsize): + diameter = (float(i) / az_map.maxrange) * min(self.width(), self.height()) + painter.drawEllipse(QtCore.QRectF(-diameter / 2.0, + -diameter / 2.0, diameter, diameter)) + + def setMaxRange(self, maxrange): + self.maxrange = maxrange + self.drawPath() + + def setRingSize(self, ringsize): + self.ringsize = ringsize + self.drawPath() + +class az_map_output(air_modes.parse): + def __init__(self, mypos, model): + air_modes.parse.__init__(self, mypos) + self.model = model + + def output(self, msg): + [data, ecc, reference, timestamp] = msg.split() + data = air_modes.modes_reply(long(data, 16)) + ecc = long(ecc, 16) + rssi = 10.*math.log10(float(reference)) + msgtype = data["df"] + now = time.time() + + if msgtype == 17: + icao = data["aa"] + subtype = data["ftc"] + distance, altitude, bearing = [0,0,0] + if 5 <= subtype <= 8: + (ground_track, decoded_lat, decoded_lon, distance, bearing) = self.parseBDS06(data) + altitude = 0 + elif 9 <= subtype <= 18: + (altitude, decoded_lat, decoded_lon, distance, bearing) = self.parseBDS05(data) + + self.model.addRecord(bearing, altitude, distance) + + +############################## +# Test stuff +############################## +import random, time + +class model_updater(threading.Thread): + def __init__(self, model): + super(model_updater, self).__init__() + self.model = model + self.setDaemon(1) + self.done = False + self.start() + + def run(self): + for i in range(az_map_model.npoints): + time.sleep(0.005) + if(self.model): + for alt in self.model._altitudes: + self.model.addRecord(i*360./az_map_model.npoints, alt, random.randint(0,az_map.maxrange)*alt / max(self.model._altitudes)) + self.done = True + +class Window(QtGui.QWidget): + def __init__(self): + super(Window, self).__init__() + layout = QtGui.QGridLayout() + self.model = az_map_model() + mymap = az_map(None) + mymap.setModel(self.model) + self.updater = model_updater(self.model) + layout.addWidget(mymap, 0, 1) + self.setLayout(layout) + +if __name__ == '__main__': + + import sys + + app = QtGui.QApplication(sys.argv) + window = Window() + window.show() + window.update() + sys.exit(app.exec_()) diff --git a/res/modes_rx.ui b/res/modes_rx.ui index b54b2d5..3fce593 100644 --- a/res/modes_rx.ui +++ b/res/modes_rx.ui @@ -6,954 +6,996 @@ 0 0 - 686 - 418 + 687 + 422 + + + 0 + 0 + + MainWindow - - - - 130 - 30 - 551 - 301 - - - - 1 - - - - Setup - - - - - 10 - 20 - 236 - 193 - - - - Input - - - - - 11 - 66 - 66 - 17 - - - - Rate - - - - - - 11 - 36 - 66 - 17 - - - - Source - - - - - - 90 - 30 - 121 - 27 - - - - - - - 10 - 95 - 66 - 17 - - - - Threshold - - - - - - 90 - 60 - 91 - 27 - - - - - - - 90 - 90 - 71 - 27 - - - - - - - 164 - 96 - 31 - 17 - - - - dB - - - - - - 184 - 66 - 41 - 17 - - - - Msps - - - - - - -1 - 117 - 221 - 71 - - - - 1 - - - - - - 90 - 40 - 121 - 27 - - - - - - - 90 - 10 - 71 - 27 - - - - - - - 10 - 10 - 66 - 17 - - - - Gain - - - - - - 10 - 40 - 66 - 17 - - - - Antenna - - - - - - 160 - 10 - 31 - 17 - - - - dB - - - - - - - - 91 - 2 - 113 - 27 - - - - - - - 11 - 7 - 66 - 17 - - - - Filename - - - line_inputfile - label_13 - combo_rate - label_27 - - - - - - - 270 - 20 - 281 - 151 - - - - Output - - - - - 19 - 124 - 61 - 22 - - - - KML - - - - - - 19 - 34 - 71 - 22 - - - - SBS-1 - - - - - - 19 - 64 - 61 - 22 - - - - Raw - - - - - - 160 - 120 - 111 - 27 - - - - - - - 160 - 30 - 71 - 27 - - - - - - - 160 - 60 - 71 - 27 - - - - - - - 160 - 90 - 71 - 27 - - - - - - - 19 - 94 - 101 - 22 - - - - FlightGear - - - - - - 128 - 66 - 31 - 17 - - - - Port - - - - - - 128 - 96 - 31 - 17 - - - - Port - - - - - - 128 - 36 - 31 - 17 - - - - Port - - - - - - 98 - 126 - 66 - 17 - - - - Filename - - - - - - - 280 - 170 - 211 - 111 - - - - RX position - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter - - - - - 16 - 64 - 71 - 20 - - - - Longitude - - - - - - 90 - 60 - 113 - 27 - - - - - - - 90 - 30 - 113 - 27 - - - - - - - 28 - 34 - 61 - 20 - - - - Latitude - - - - - - - Dashboard - - - - - 193 - 35 - 21 - 18 - - - - nm - - - - - - 483 - 66 - 21 - 17 - - - - kts - - - - - - 483 - 96 - 21 - 17 - - - - ft - - - - - - 250 - 10 - 61 - 20 - - - - Heading - - - - - - 30 - 10 - 61 - 20 - - - - Bearing - - - - - - 120 - 240 - 118 - 23 - - - - 24 - - - false - - - - - - 17 - 243 - 101 - 17 - - - - Signal strength - - - - - - 120 - 10 - 61 - 20 - - - - Range - - - - - - 365 - 64 - 51 - 20 - - - - Speed - - - - - - 352 - 93 - 61 - 20 - - - - Altitude - - - - - - 370 - 154 - 51 - 20 - - - - Ident - - - - - - 120 - 30 - 71 - 27 - - - - true - - - - - - 410 - 60 - 71 - 27 - - - - true - - - - - - 410 - 90 - 71 - 27 - - - - true - - - - - - 410 - 150 - 71 - 27 - - - - true - - - - - - 410 - 180 - 121 - 27 - - - - true - - - - - - 373 - 184 - 51 - 20 - - - - Type - - - - - - 120 - 180 - 121 - 27 - - - - true - - - - - - 120 - 210 - 121 - 27 - - - - true - - - - - - 62 - 183 - 61 - 21 - - - - Latitude - - - - - - 50 - 213 - 71 - 21 - - - - Longitude - - - - - - 410 - 120 - 71 - 27 - - - - true - - - - - - 483 - 125 - 31 - 17 - - - - ft/s - - - - - - 368 - 123 - 41 - 20 - - - - Climb - - - - - - 200 - 30 - 161 - 91 - - - - true - - - 4 - - - - - - 372 - 34 - 51 - 20 - - - - ICAO - - - - - - 410 - 30 - 71 - 27 - - - - - - - true - - - - - - -20 - 30 - 161 - 91 - - - - true - - - 4 - - - - - - Data browser - - - - - Live data - - - - - 5 - 11 - 531 - 251 - - - - - - - - - 19 - 36 - 101 - 17 - - - - Visible aircraft - - - - - - 580 - 340 - 98 - 27 - - - - Start - - - - - false - - - - 4 - 344 - 271 - 22 - - - - Show ADS-B-equipped aircraft only - - - - - - 18 - 59 - 101 - 272 - - - - QAbstractItemView::NoEditTriggers - - - QListView::SinglePass - - - true - - - - - - 520 - 341 - 41 - 27 - - - - - - - 407 - 345 - 111 - 20 - - - - Reports/second - - + + + 0 + 0 + + + + + + + QLayout::SetDefaultConstraint + + + + + + + + + Visible aircraft + + + + + + + + 0 + 0 + + + + + 100 + 100 + + + + + 100 + 16777215 + + + + QAbstractItemView::NoEditTriggers + + + QListView::SinglePass + + + true + + + + + + + + + + 1 + 1 + + + + 3 + + + + + 0 + 0 + + + + Setup + + + + + 10 + 20 + 236 + 193 + + + + Input + + + + + 11 + 66 + 66 + 17 + + + + Rate + + + + + + 11 + 36 + 66 + 17 + + + + Source + + + + + + 90 + 30 + 121 + 27 + + + + + + + 10 + 95 + 66 + 17 + + + + Threshold + + + + + + 90 + 60 + 91 + 27 + + + + + + + 90 + 90 + 71 + 27 + + + + + + + 164 + 96 + 31 + 17 + + + + dB + + + + + + 184 + 66 + 41 + 17 + + + + Msps + + + + + + -1 + 117 + 221 + 71 + + + + 1 + + + + + + 90 + 40 + 121 + 27 + + + + + + + 90 + 10 + 71 + 27 + + + + + + + 10 + 10 + 66 + 17 + + + + Gain + + + + + + 10 + 40 + 66 + 17 + + + + Antenna + + + + + + 160 + 10 + 31 + 17 + + + + dB + + + + + + + + 91 + 2 + 113 + 27 + + + + + + + 11 + 7 + 66 + 17 + + + + Filename + + + + + + + + + 270 + 20 + 281 + 151 + + + + Output + + + + + 19 + 124 + 61 + 22 + + + + KML + + + + + + 19 + 34 + 71 + 22 + + + + SBS-1 + + + + + + 19 + 64 + 61 + 22 + + + + Raw + + + + + + 160 + 120 + 111 + 27 + + + + + + + 160 + 30 + 71 + 27 + + + + + + + 160 + 60 + 71 + 27 + + + + + + + 160 + 90 + 71 + 27 + + + + + + + 19 + 94 + 101 + 22 + + + + FlightGear + + + + + + 128 + 66 + 31 + 17 + + + + Port + + + + + + 128 + 96 + 31 + 17 + + + + Port + + + + + + 128 + 36 + 31 + 17 + + + + Port + + + + + + 98 + 126 + 66 + 17 + + + + Filename + + + + + + + 280 + 170 + 211 + 111 + + + + RX position + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + + + 16 + 64 + 71 + 20 + + + + Longitude + + + + + + 90 + 60 + 113 + 27 + + + + + + + 90 + 30 + 113 + 27 + + + + + + + 28 + 34 + 61 + 20 + + + + Latitude + + + + + + + Dashboard + + + + + 193 + 35 + 21 + 18 + + + + nm + + + + + + 483 + 66 + 21 + 17 + + + + kts + + + + + + 483 + 96 + 21 + 17 + + + + ft + + + + + + 250 + 10 + 61 + 20 + + + + Heading + + + + + + 30 + 10 + 61 + 20 + + + + Bearing + + + + + + 120 + 240 + 118 + 23 + + + + 24 + + + false + + + + + + 17 + 243 + 101 + 17 + + + + Signal strength + + + + + + 120 + 10 + 61 + 20 + + + + Range + + + + + + 365 + 64 + 51 + 20 + + + + Speed + + + + + + 352 + 93 + 61 + 20 + + + + Altitude + + + + + + 370 + 154 + 51 + 20 + + + + Ident + + + + + + 120 + 30 + 71 + 27 + + + + true + + + + + + 410 + 60 + 71 + 27 + + + + true + + + + + + 410 + 90 + 71 + 27 + + + + true + + + + + + 410 + 150 + 71 + 27 + + + + true + + + + + + 410 + 180 + 121 + 27 + + + + true + + + + + + 373 + 184 + 51 + 20 + + + + Type + + + + + + 120 + 180 + 121 + 27 + + + + true + + + + + + 120 + 210 + 121 + 27 + + + + true + + + + + + 62 + 183 + 61 + 21 + + + + Latitude + + + + + + 50 + 213 + 71 + 21 + + + + Longitude + + + + + + 410 + 120 + 71 + 27 + + + + true + + + + + + 483 + 125 + 31 + 17 + + + + ft/s + + + + + + 368 + 123 + 41 + 20 + + + + Climb + + + + + + 200 + 30 + 161 + 91 + + + + true + + + 4 + + + + + + 372 + 34 + 51 + 20 + + + + ICAO + + + + + + 410 + 30 + 71 + 27 + + + + + + + true + + + + + + -20 + 30 + 161 + 91 + + + + true + + + 4 + + + + + + Azimuth map + + + + + + + + + + + 0 + 0 + + + + Live data + + + + + + + + + + + + + + + + + false + + + Show ADS-B-equipped aircraft only + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Reports/second + + + + + + + + 50 + 16777215 + + + + + + + + Start + + + + + + + + 0 0 - 686 + 687 25 @@ -971,6 +1013,12 @@ QWidget
qwt_dial.h
+ + az_map + QWidget +
air_modes/az_map
+ 1 +