From 2111203ac9fcd82cd78306a058a48369f97a6ce7 Mon Sep 17 00:00:00 2001 From: Beat Bolli Date: Wed, 5 Jan 2011 16:46:53 +0100 Subject: Add a Python port of Jason Hecker's webgps.py This client draws a sky view of the satellites and their tracks using HTML and SVG. It needs to run continuously in order to build a history and generate the satellite tracks. Your browser must be able to handle SVG to get the skymap display working properly. --- contrib/webgps.py | 275 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 275 insertions(+) create mode 100755 contrib/webgps.py (limited to 'contrib/webgps.py') diff --git a/contrib/webgps.py b/contrib/webgps.py new file mode 100755 index 00000000..1438f8c0 --- /dev/null +++ b/contrib/webgps.py @@ -0,0 +1,275 @@ +#!/usr/bin/env python +# encoding: utf-8 + +# webgps.py +# +# This is a Python port of webgps.c from http://www.wireless.org.au/~jhecker/gpsd/ +# by Beat Bolli +# + +import time, calendar, math, socket, sys, os, select, pickle +from gps import * + +TRACKMAX = 1024 +STALECOUNT = 10 + +DIAMETER = 200 +XYOFFSET = 10 + +def polartocart(el, az): + radius = DIAMETER * (1 - el / 90.0) # * math.cos(Deg2Rad(float(el))) + theta = Deg2Rad(float(az - 90)) + return ( + int(radius * math.cos(theta) + 0.5) + DIAMETER + XYOFFSET, + int(radius * math.sin(theta) + 0.5) + DIAMETER + XYOFFSET + ) + + +class Track: + '''Store the track of one satellite.''' + + def __init__(self, prn): + self.prn = prn + self.stale = 0 + self.posn = [] # list of (x, y) tuples + + def add(self, x, y): + pos = (x, y) + self.stale = STALECOUNT + if not self.posn or self.posn[-1] != pos: + self.posn.append(pos) + if len(self.posn) > TRACKMAX: + self.posn = self.posn[-TRACKMAX:] + #print self.prn, self.posn + return 1 + return 0 + +class SatTracks(gps): + '''gpsd client writing HTML and SVG output.''' + + def __init__(self): + gps.__init__(self) + self.sattrack = {} # maps PRNs to Tracks + self.state = None + self.statetimer = time.time() + self.needsupdate = 0 + + def html(self, svgfile): + self.fh.write(""" + + + + +\t +\tGPSD Satellite Positions and Readings +\t + + +\t +\t\t +\t\t\t\n") + + # SVG stuff + self.fh.write("\t\t\t\n\t\t\n" % svgfile) + + self.fh.write("\t
+\t\t\t\t +\t\t\t\t\t +""") + + sats = self.satellites[:] + sats.sort(lambda a, b: a.PRN - b.PRN) + for s in sats: + self.fh.write("\t\t\t\t\t\n" % ( + s.PRN, s.elevation, s.azimuth, s.ss, s.used and 'Y' or 'N' + )) + + self.fh.write("\t\t\t\t
PRN:Elev:Azim:SNR:Used:
%d%d%d%d%s
\n\t\t\t\t\n") + + def row(l, v): + self.fh.write("\t\t\t\t\t\n" % (l, v)) + + def deg_to_str(a, hemi): + return '%.6f %c' % (abs(a), hemi[a < 0]) + + row('Time', self.utc or 'N/A') + + if self.fix.mode >= MODE_2D: + row('Latitude', deg_to_str(self.fix.latitude, 'SN')) + row('Longitude', deg_to_str(self.fix.longitude, 'WE')) + row('Altitude', self.fix.mode == MODE_3D and "%f m" % self.fix.altitude or 'N/A') + row('Speed', not isnan(self.fix.speed) and "%f m/s" % self.fix.speed or 'N/A') + row('Course', not isnan(self.fix.track) and "%f°" % self.fix.track or 'N/A') + else: + row('Latitude', 'N/A') + row('Longitude', 'N/A') + row('Altitude', 'N/A') + row('Speed', 'N/A') + row('Course', 'N/A') + + row('EPX', not isnan(self.fix.epx) and "%f m" % self.fix.epx or 'N/A') + row('EPY', not isnan(self.fix.epy) and "%f m" % self.fix.epy or 'N/A') + row('EPV', not isnan(self.fix.epv) and "%f m" % self.fix.epv or 'N/A') + row('Climb', self.fix.mode == MODE_3D and not isnan(self.fix.climb) and + "%f m/s" % self.fix.climb or 'N/A' + ) + + if not (self.valid & ONLINE_SET): + newstate = 0 + state = "OFFLINE" + else: + newstate = self.fix.mode + if newstate == MODE_2D: + state = self.status == STATUS_DGPS_FIX and "2D DIFF FIX" or "2D FIX" + elif newstate == MODE_3D: + state = self.status == STATUS_DGPS_FIX and "3D DIFF FIX" or "3D FIX" + else: + state = "NO FIX" + if newstate != self.state: + self.statetimer = time.time() + self.state = newstate + row('State', state + " (%d secs)" % (time.time() - self.statetimer)) + + self.fh.write("\t\t\t\t
%s:%s
\n\t\t\t
\n\t\t\t\t\n\ +\t\t\t
\n\n\n") + + def svg(self): + self.fh.write(""" + + +\t +\t\t +\t\t +\t\t +\t\t +\t\t +\t\t +\t\t +\t\t +\t\t\tN +\t\t\tW +\t\t\tE +\t\t\tS +\t\t +""" + ) + + # Draw the tracks + self.fh.write('\t\t\n') + for t in self.sattrack.values(): + if t.posn: + self.fh.write('\t\t\t\n' % ( + ' '.join(['%d,%d' % p for p in t.posn]), t.stale == 0 and ' opacity=".33"' or '' + )) + self.fh.write('\t\t\n') + + # Draw the satellites + self.fh.write('\t\t\n') + for s in self.satellites: + x, y = polartocart(s.elevation, s.azimuth) + fill = s.ss < 30 and 'red' or s.ss < 35 and 'yellow' or s.ss < 40 and 'green' or 'lime' + opaque = not s.used and ' opacity=".33"' or '' + + # Center PRNs in the marker + offset = s.PRN < 10 and 3 or s.PRN >= 100 and -3 or 0 + + if s.PRN > 32: # draw a diamond for SBAS satellites + self.fh.write( + '\t\t\t\n' % + (x + 8, y, fill, opaque) + ) + else: + self.fh.write( + '\t\t\t\n' % + (x, y, fill, opaque) + ) + self.fh.write('\t\t\t%d\n' % (x - 6 + offset, y + 4, s.PRN)) + + self.fh.write('\t\t\n\t\n\n') + + def make_stale(self): + for t in self.sattrack.values(): + if t.stale: + t.stale -= 1 + + def delete_stale(self): + for prn in self.sattrack.keys(): + if self.sattrack[prn].stale == 0: + del self.sattrack[prn] + self.needsupdate = 1 + + def insert_sat(self, prn, x, y): + try: + t = self.sattrack[prn] + except KeyError: + self.sattrack[prn] = t = Track(prn) + self.needsupdate += t.add(x, y) + + def update_tracks(self): + self.make_stale() + for s in self.satellites: + x, y = polartocart(s.elevation, s.azimuth) + if self.insert_sat(s.PRN, x, y): + self.needsupdate = 1 + self.delete_stale() + + def generate_html(self, htmlfile, svgfile): + self.fh = open(htmlfile, 'w') + self.html(svgfile) + self.fh.close() + + def generate_svg(self, svgfile): + self.fh = open(svgfile, 'w') + self.svg() + self.fh.close() + + def run(self, period): + end = time.time() + period + self.stream(WATCH_ENABLE | WATCH_NEWSTYLE) + for report in self: + if report['class'] not in ('TPV', 'SKY'): + continue + self.needsupdate = 0 + self.update_tracks() + self.generate_html('gpsd.html', 'gpsd.svg') + if self.needsupdate: + self.generate_svg('gpsd.svg') + if period <= 0 and not isnan(self.fix.time): + break + if period > 0 and time.time() > end: + break + +def main(): + argv = sys.argv[1:] + + period = argv and argv[0] or '0' + if period[-1:] in 'smhd': + period = int(period[:-1]) * {'s': 1, 'm': 60, 'h': 60*60, 'd': 24*60*60}[period[-1]] + else: + period = int(period) + + sat = SatTracks() + + # restore the tracks + pfile = 'tracks.p' + if os.path.isfile(pfile): + p = open(pfile) + sat.sattrack = pickle.load(p) + p.close() + + try: + sat.run(period) + except KeyboardInterrupt: + # save the tracks + p = open(pfile, 'w') + pickle.dump(sat.sattrack, p) + p.close() + +if __name__ == '__main__': + main() -- cgit v1.2.1