#!/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()