Added support for integrated Google Maps interface via QWebView/JavaScript/JSONP. Broken due to something hairy wrt QWebView and /tmp.

This commit is contained in:
Nick Foster 2013-06-20 23:05:41 -07:00
parent fbe3c464fb
commit 55cd17de67
7 changed files with 234 additions and 17 deletions

View File

@ -19,9 +19,9 @@
# Boston, MA 02110-1301, USA. # Boston, MA 02110-1301, USA.
# #
import os, sys, time, threading, datetime, math, csv import os, sys, time, threading, datetime, math, csv, tempfile
from optparse import OptionParser from optparse import OptionParser
from PyQt4 import QtCore,QtGui from PyQt4 import QtCore,QtGui,QtWebKit
from PyQt4.Qwt5 import Qwt from PyQt4.Qwt5 import Qwt
from gnuradio import gr, gru, optfir, eng_notation, blks2 from gnuradio import gr, gru, optfir, eng_notation, blks2
from gnuradio.eng_option import eng_option from gnuradio.eng_option import eng_option
@ -73,6 +73,7 @@ class mainwindow(QtGui.QMainWindow):
#disable by default #disable by default
self.ui.check_adsbonly.setCheckState(QtCore.Qt.Unchecked) self.ui.check_adsbonly.setCheckState(QtCore.Qt.Unchecked)
#set up the radio stuff
self.queue = gr.msg_queue(10) self.queue = gr.msg_queue(10)
self.running = False self.running = False
self.kmlgen = None #necessary bc we stop its thread in shutdown self.kmlgen = None #necessary bc we stop its thread in shutdown
@ -288,12 +289,27 @@ class mainwindow(QtGui.QMainWindow):
self.az_map_output = air_modes.az_map_output(self._cpr_dec, self.az_model, self._publisher) self.az_map_output = air_modes.az_map_output(self._cpr_dec, self.az_model, self._publisher)
#self._relay.subscribe("dl_data", self.az_map_output.output) #self._relay.subscribe("dl_data", self.az_map_output.output)
#set up map
self._htmlfile = open("/home/nick/wat.html", 'wb+')#tempfile.NamedTemporaryFile()
self._jsonfile = tempfile.NamedTemporaryFile()
self.livedata = air_modes.output_print(self._cpr_dec, self._publisher) self.livedata = air_modes.output_print(self._cpr_dec, self._publisher)
#add output for live data box #add output for live data box
#self._relay.subscribe("dl_data", self.output_live_data) #self._relay.subscribe("dl_data", self.output_live_data)
#create SQL database for KML and dashboard displays #create SQL database for KML and dashboard displays
self.dbwriter = air_modes.output_sql(self._cpr_dec, self.dbname, self.lock, self._publisher) self.dbwriter = air_modes.output_sql(self._cpr_dec, self.dbname, self.lock, self._publisher)
self.jsonpgen = air_modes.output_jsonp(self._jsonfile.name, self.dbname, my_position, self.lock, timeout=1)
htmlstring = air_modes.html_template(my_position, self._jsonfile.name)
self._htmlfile.write(htmlstring)
self._htmlfile.flush()
class WebPage(QtWebKit.QWebPage):
def javaScriptConsoleMessage(self, msg, line, source):
print '%s line %d: %s' % (source, line, msg)
page = WebPage()
self.ui.mapView.setPage(page)
self.ui.mapView.load( QtCore.QUrl( QtCore.QUrl.fromLocalFile("/home/nick/wat.html") ) )
self.ui.mapView.show()
#output to update reports/sec widget #output to update reports/sec widget
self._relay.subscribe("dl_data", self.increment_reportspersec) self._relay.subscribe("dl_data", self.increment_reportspersec)

View File

@ -34,6 +34,7 @@ GR_PYTHON_INSTALL(
altitude.py altitude.py
az_map.py az_map.py
cpr.py cpr.py
html_template.py
mlat.py mlat.py
exceptions.py exceptions.py
flightgear.py flightgear.py

View File

@ -57,7 +57,7 @@ from parse import *
from msprint import output_print from msprint import output_print
from sql import output_sql from sql import output_sql
from sbs1 import output_sbs1 from sbs1 import output_sbs1
from kml import output_kml from kml import output_kml, output_jsonp
from raw_server import raw_server from raw_server import raw_server
from radio import modes_radio from radio import modes_radio
from exceptions import * from exceptions import *
@ -65,6 +65,7 @@ from az_map import *
from types import * from types import *
from altitude import * from altitude import *
from cpr import cpr_decoder from cpr import cpr_decoder
from html_template import html_template
#this is try/excepted in case the user doesn't have numpy installed #this is try/excepted in case the user doesn't have numpy installed
try: try:
from flightgear import output_flightgear from flightgear import output_flightgear

92
python/html_template.py Normal file
View File

@ -0,0 +1,92 @@
#!/usr/bin/env python
#HTML template for Mode S map display
#Nick Foster, 2013
def html_template(my_position, json_file):
if my_position is None:
my_position = [37, -122]
return """
<html>
<head>
<title>ADS-B Aircraft Map</title>
<meta name="viewport" content="initial-scale=1.0, user-scalable=no" />
<script type="text/javascript" src="http://maps.google.com/maps/api/js?sensor=false">
</script>
<script type="text/javascript">
var map;
var markers = [];
var defaultLocation = new google.maps.LatLng(%f, %f);
var defaultZoomLevel = 9;
function requestJSONP() {
var script = document.createElement("script");
script.src = "%s?" + Math.random();
script.params = Math.random();
document.getElementsByTagName('head')[0].appendChild(script);
};
var planeMarker;
var planes = [];
function clearMarkers() {
for (var i = 0; i < planes.length; i++) {
planes[i].setMap(null);
}
planes = [];
};
function jsonp_callback(results) { // from JSONP
clearMarkers();
airplanes = {};
for (var i = 0; i < results.length; i++) {
airplanes[results[i].icao] = {
center: new google.maps.LatLng(results[i].lat, results[i].lon),
heading: results[i].hdg,
altitude: 0
};
}
refreshIcons();
}
function refreshIcons() {
for (var airplane in airplanes) {
var plane_icon = {
url: "http://www.nerdnetworks.org/~bistromath/airplane_sprite.png",
size: new google.maps.Size(128,128),
origin: new google.maps.Point(parseInt(airplanes[airplane].heading/10)*128,0),
anchor: new google.maps.Point(64,64),
//scaledSize: new google.maps.Size(4608,126)
};
var planeOptions = {
map: map,
position: airplanes[airplane].center,
icon: plane_icon
};
planeMarker = new google.maps.Marker(planeOptions);
planes.push(planeMarker);
};
};
function initialize()
{
var myOptions =
{
zoom: defaultZoomLevel,
center: defaultLocation,
disableDefaultUI: true,
mapTypeId: google.maps.MapTypeId.TERRAIN
};
map = new google.maps.Map(document.getElementById("map_canvas"), myOptions);
requestJSONP();
setInterval("requestJSONP()", 1000);
};
</script>
</head>
<body onload="initialize()">
<div id="map_canvas" style="width:100%%; height:100%%">
</div>
</body>
</html>""" % (my_position[0], my_position[1], json_file)

View File

@ -170,3 +170,92 @@ class output_kml(threading.Thread):
retstr+= '\n\t</Folder>\n</Document>\n</kml>' retstr+= '\n\t</Folder>\n</Document>\n</kml>'
return retstr return retstr
#we just inherit from output_kml because we're doing the same thing, only in a different format.
class output_jsonp(output_kml):
def genkml(self):
retstr="""jsonp_callback(["""
# if self.my_coords is not None:
# retstr += """\n\t<Folder>\n\t\t<name>Range rings</name>\n\t\t<open>0</open>"""
# for rng in [100, 200, 300]:
# retstr += """\n\t\t<Placemark>\n\t\t\t<name>%inm</name>\n\t\t\t<styleUrl>#rangering</styleUrl>\n\t\t\t<LinearRing>\n\t\t\t\t<coordinates>%s</coordinates>\n\t\t\t</LinearRing>\n\t\t</Placemark>""" % (rng, self.draw_circle(self.my_coords, rng),)
# retstr += """\t</Folder>\n"""
# retstr += """\t<Folder>\n\t\t<name>Aircraft locations</name>\n\t\t<open>0</open>"""
#read the database and add KML
q = "select distinct icao from positions where seen > datetime('now', '-5 minute')"
c = self._db.cursor()
self.locked_execute(c, q)
icaolist = c.fetchall()
#now we have a list icaolist of all ICAOs seen in the last 5 minutes
for icao in icaolist:
icao = icao[0]
#print "ICAO: %x" % icao
# q = "select * from positions where icao=%i and seen > datetime('now', '-2 hour') ORDER BY seen DESC limit 1" % icao
# self.locked_execute(c, q)
# pos = c.fetchall()
#print "Track length: %i" % len(track)
# if len(track) != 0:
# lat = track[0][3]
# if lat is None: lat = 0
# lon = track[0][4]
# if lon is None: lon = 0
# alt = track[0][2]
# if alt is None: alt = 0
# metric_alt = alt * 0.3048 #google earth takes meters, the commie bastards
# trackstr = ""
# for pos in track:
# trackstr += " %f,%f,%f" % (pos[4], pos[3], pos[2]*0.3048)
# trackstr = string.lstrip(trackstr)
# else:
# alt = 0
# metric_alt = 0
# lat = 0
# lon = 0
# trackstr = str("")
#now get metadata
# q = "select ident from ident where icao=%i" % icao
# self.locked_execute(c, q)
# r = c.fetchall()
# if len(r) != 0:
# ident = r[0][0]
# else: ident=""
#if ident is None: ident = ""
#get most recent speed/heading/vertical
q = "select seen, speed, heading, vertical from vectors where icao=%i order by seen desc limit 1" % icao
self.locked_execute(c, q)
r = c.fetchall()
if len(r) != 0:
seen = r[0][0]
speed = r[0][1]
heading = r[0][2]
vertical = r[0][3]
else:
seen = 0
speed = 0
heading = 0
vertical = 0
q = "select lat, lon from positions where icao=%i order by seen desc limit 1" % icao
self.locked_execute(c, q)
r = c.fetchall()
if len(r) != 0:
lat = r[0][0]
lon = r[0][1]
else:
lat = 0
lon = 0
#now generate some KML
retstr+= """{"icao": "%.6x", "lat": %f, "lon": %f, "hdg": %i, "speed": %i, "vertical": %i},""" % (icao, lat, lon, heading, speed, vertical)
retstr+= """]);"""
return retstr

View File

@ -80,7 +80,7 @@ class output_sql:
c = self._db.cursor() c = self._db.cursor()
c.execute(query) c.execute(query)
c.close() c.close()
# self._db.commit() self._db.commit()
except ADSBError: except ADSBError:
pass pass

View File

@ -85,7 +85,7 @@
</sizepolicy> </sizepolicy>
</property> </property>
<property name="currentIndex"> <property name="currentIndex">
<number>0</number> <number>3</number>
</property> </property>
<widget class="QWidget" name="setup"> <widget class="QWidget" name="setup">
<property name="sizePolicy"> <property name="sizePolicy">
@ -856,7 +856,7 @@
<string>Climb</string> <string>Climb</string>
</property> </property>
</widget> </widget>
<widget class="QwtCompass" name="compass_heading"> <widget class="QwtCompass" name="compass_heading" native="true">
<property name="geometry"> <property name="geometry">
<rect> <rect>
<x>200</x> <x>200</x>
@ -865,10 +865,10 @@
<height>91</height> <height>91</height>
</rect> </rect>
</property> </property>
<property name="readOnly"> <property name="readOnly" stdset="0">
<bool>true</bool> <bool>true</bool>
</property> </property>
<property name="lineWidth"> <property name="lineWidth" stdset="0">
<number>4</number> <number>4</number>
</property> </property>
</widget> </widget>
@ -901,7 +901,7 @@
<bool>true</bool> <bool>true</bool>
</property> </property>
</widget> </widget>
<widget class="QwtCompass" name="compass_bearing"> <widget class="QwtCompass" name="compass_bearing" native="true">
<property name="geometry"> <property name="geometry">
<rect> <rect>
<x>-20</x> <x>-20</x>
@ -910,10 +910,10 @@
<height>91</height> <height>91</height>
</rect> </rect>
</property> </property>
<property name="readOnly"> <property name="readOnly" stdset="0">
<bool>true</bool> <bool>true</bool>
</property> </property>
<property name="lineWidth"> <property name="lineWidth" stdset="0">
<number>4</number> <number>4</number>
</property> </property>
</widget> </widget>
@ -928,6 +928,18 @@
</item> </item>
</layout> </layout>
</widget> </widget>
<widget class="QWidget" name="map_tab">
<attribute name="title">
<string>Map</string>
</attribute>
<layout class="QGridLayout" name="gridLayout_8">
<item row="0" column="0">
<widget class="QWebView" name="mapView" native="true">
<zorder>gridLayoutWidget</zorder>
</widget>
</item>
</layout>
</widget>
<widget class="QWidget" name="livedatatab"> <widget class="QWidget" name="livedatatab">
<property name="sizePolicy"> <property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding"> <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
@ -1016,11 +1028,6 @@
<widget class="QStatusBar" name="statusbar"/> <widget class="QStatusBar" name="statusbar"/>
</widget> </widget>
<customwidgets> <customwidgets>
<customwidget>
<class>QwtCompass</class>
<extends>QwtDial</extends>
<header>qwt_compass.h</header>
</customwidget>
<customwidget> <customwidget>
<class>QwtDial</class> <class>QwtDial</class>
<extends>QWidget</extends> <extends>QWidget</extends>
@ -1032,6 +1039,17 @@
<header location="global">air_modes/az_map</header> <header location="global">air_modes/az_map</header>
<container>1</container> <container>1</container>
</customwidget> </customwidget>
<customwidget>
<class>QwtCompass</class>
<extends>QwtDial</extends>
<header>qwt_compass.h</header>
</customwidget>
<customwidget>
<class>QWebView</class>
<extends>QWidget</extends>
<header location="global">QtWebKit/qwebview.h</header>
<container>1</container>
</customwidget>
</customwidgets> </customwidgets>
<resources/> <resources/>
<connections/> <connections/>