summaryrefslogtreecommitdiff
path: root/contrib/webgps.py
diff options
context:
space:
mode:
authorBeat Bolli <bbolli@ewanet.ch>2011-03-03 23:55:40 +0100
committerEric S. Raymond <esr@thyrsus.com>2011-03-06 18:04:44 -0500
commit81dec0a5a480df04f5a0355405ee6014ea07e080 (patch)
treef71ba7ae93527e1771639da14fa7c7727690feb8 /contrib/webgps.py
parent1a6f979230c7ea40026781ff5b13422ea5e4eb60 (diff)
downloadgpsd-81dec0a5a480df04f5a0355405ee6014ea07e080.tar.gz
webgps.py: use the HTML5 <canvas> instead of SVG
This also cleans up the TABs in the Python source. Signed-off-by: Eric S. Raymond <esr@thyrsus.com>
Diffstat (limited to 'contrib/webgps.py')
-rwxr-xr-xcontrib/webgps.py332
1 files changed, 192 insertions, 140 deletions
diff --git a/contrib/webgps.py b/contrib/webgps.py
index 1438f8c0..eecf8dcb 100755
--- a/contrib/webgps.py
+++ b/contrib/webgps.py
@@ -14,14 +14,13 @@ 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
+ -int(radius * math.cos(theta) + 0.5), # switch sides for a skyview!
+ int(radius * math.sin(theta) + 0.5)
)
@@ -40,12 +39,11 @@ class Track:
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.'''
+ '''gpsd client writing HTML5 and <canvas> output.'''
def __init__(self):
gps.__init__(self)
@@ -54,144 +52,187 @@ class SatTracks(gps):
self.statetimer = time.time()
self.needsupdate = 0
- def html(self, svgfile):
- self.fh.write("""<?xml version="1.0" encoding="utf-8"?>
-<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML Transitional 1.0//EN"
-\t"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+ def html(self, fh, jsfile):
+ fh.write("""<!DOCTYPE html>
-<html xmlns="http://www.w3.org/1999/xhtml">
+<html>
<head>
-\t<meta http-equiv="Refresh" content="300" />
+\t<meta http-equiv=Refresh content=300>
+\t<meta charset='utf-8'>
\t<title>GPSD Satellite Positions and Readings</title>
-\t<style type="text/css"><!--
+\t<style type='text/css'>
\t\t.num td { text-align: right; }
\t\tth { text-align: left; }
-\t--></style>
+\t</style>
+\t<script src='%s'></script>
</head>
<body>
-\t<table border="1">
+\t<table border=1>
\t\t<tr>
\t\t\t<td>
-\t\t\t\t<table border="0" class="num">
+\t\t\t\t<table border=0 class=num>
\t\t\t\t\t<tr><th>PRN:</th><th>Elev:</th><th>Azim:</th><th>SNR:</th><th>Used:</th></tr>
-""")
+""" % jsfile)
sats = self.satellites[:]
sats.sort(lambda a, b: a.PRN - b.PRN)
for s in sats:
- self.fh.write("\t\t\t\t\t<tr><td>%d</td><td>%d</td><td>%d</td><td>%d</td><td>%s</td></tr>\n" % (
+ fh.write("\t\t\t\t\t<tr><td>%d</td><td>%d</td><td>%d</td><td>%d</td><td>%s</td></tr>\n" % (
s.PRN, s.elevation, s.azimuth, s.ss, s.used and 'Y' or 'N'
))
- self.fh.write("\t\t\t\t</table>\n\t\t\t\t<table border=\"0\">\n")
-
- def row(l, v):
- self.fh.write("\t\t\t\t\t<tr><th>%s:</th><td>%s</td></tr>\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</table>\n\t\t\t</td>\n")
-
- # SVG stuff
- self.fh.write("\t\t\t<td>\n\t\t\t\t<object data=\"%s\" \
-width=\"425\" height=\"425\" type=\"image/svg+xml\" />\n\
-\t\t\t</td>\n\t\t</tr>\n" % svgfile)
-
- self.fh.write("\t</table>\n</body>\n</html>\n")
-
- def svg(self):
- self.fh.write("""<?xml version="1.0" standalone="no"?>
-<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
- "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
-<svg width="100%" height="100%" version="1.1" xmlns="http://www.w3.org/2000/svg">
-\t<g transform="translate(0,0)">
-\t\t<circle cx="210" cy="210" r="200" stroke="black" stroke-width="1" fill="white"/>
-\t\t<circle cx="210" cy="210" r="100" stroke="grey" stroke-width="1" fill="white"/>
-\t\t<circle cx="210" cy="210" r="2" stroke="grey" stroke-width="1" fill="white"/>
-\t\t<line x1="210" y1="10" x2="210" y2="410" stroke="lightgrey"/>
-\t\t<line x1="10" y1="210" x2="410" y2="210" stroke="lightgrey"/>
-\t\t<line x1="68.578644" y1="68.578644" x2="351.42136" y2="351.42136" stroke="lightgrey"/>
-\t\t<line x1="68.578644" y1="351.42136" x2="351.42136" y2="68.578644" stroke="lightgrey"/>
-\t\t<g font-size="10" stroke="black" stroke-width="0.5">
-\t\t\t<text x="206" y="8">N</text>
-\t\t\t<text x="0" y="214">W</text>
-\t\t\t<text x="412" y="214">E</text>
-\t\t\t<text x="206" y="420">S</text>
-\t\t</g>
-"""
+ fh.write("\t\t\t\t</table>\n\t\t\t\t<table border=0>\n")
+
+ def row(l, v):
+ fh.write("\t\t\t\t\t<tr><th>%s:</th><td>%s</td></tr>\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))
+
+ fh.write("""\t\t\t\t</table>
+\t\t\t</td>
+\t\t\t<td>
+\t\t\t\t<canvas id=satview width=425 height=425>
+\t\t\t\t\t<p>Your browser needs HTML5 &lt;canvas> support to display the satellite view correctly.</p>
+\t\t\t\t</canvas>
+\t\t\t\t<script type='text/javascript'>draw_satview();</script>
+\t\t\t</td>
+\t\t</tr>
+\t</table>
+</body>
+</html>
+""")
+
+ def js(self, fh):
+ fh.write("""// draw the satellite view
+
+function draw_satview() {
+ var c = document.getElementById('satview');
+ if (!c.getContext) return;
+ var ctx = c.getContext('2d');
+ if (!ctx) return;
+
+ var circle = Math.PI * 2,
+ M = function (x, y) { ctx.moveTo(x, y); },
+ L = function (x, y) { ctx.lineTo(x, y); };
+
+ ctx.save();
+ ctx.clearRect(0, 0, c.width, c.height);
+ ctx.translate(210, 210);
+
+ // grid and labels
+ ctx.strokeStyle = 'black';
+ ctx.beginPath();
+ ctx.arc(0, 0, 200, 0, circle, 0);
+ ctx.stroke();
+
+ ctx.beginPath();
+ ctx.strokeText('N', -4, -202);
+ ctx.strokeText('E', -210, 4);
+ ctx.strokeText('W', 202, 4);
+ ctx.strokeText('S', -4, 210);
+
+ ctx.strokeStyle = 'grey';
+ ctx.beginPath();
+ ctx.arc(0, 0, 100, 0, circle, 0);
+ M(2, 0);
+ ctx.arc(0, 0, 2, 0, circle, 0);
+ ctx.stroke();
+
+ ctx.strokeStyle = 'lightgrey';
+ ctx.save();
+ ctx.beginPath();
+ M(0, -200); L(0, 200); ctx.rotate(circle / 8);
+ M(0, -200); L(0, 200); ctx.rotate(circle / 8);
+ M(0, -200); L(0, 200); ctx.rotate(circle / 8);
+ M(0, -200); L(0, 200);
+ ctx.stroke();
+ ctx.restore();
+
+ // tracks
+ ctx.lineWidth = 0.6;
+ ctx.strokeStyle = 'red';
+""");
+
+ def M(p):
+ return 'M(%d,%d); ' % p
+ def L(p):
+ return 'L(%d,%d); ' % p
+
# Draw the tracks
- self.fh.write('\t\t<g stroke-width="0.6" stroke="red" fill="none">\n')
for t in self.sattrack.values():
if t.posn:
- self.fh.write('\t\t\t<polyline points="%s"%s/>\n' % (
- ' '.join(['%d,%d' % p for p in t.posn]), t.stale == 0 and ' opacity=".33"' or ''
+ fh.write(" ctx.globalAlpha = %s; ctx.beginPath(); %s%sctx.stroke();\n" % (
+ t.stale == 0 and '0.66' or '1', M(t.posn[0]),
+ ''.join([L(p) for p in t.posn[1:]])
))
- self.fh.write('\t\t</g>\n')
+
+ fh.write("""
+ // satellites
+ ctx.lineWidth = 1;
+ ctx.strokeStyle = 'black';
+""")
# Draw the satellites
- self.fh.write('\t\t<g stroke-width="1" stroke="black" fill="black" font-size="10">\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 ''
+ fill = not s.used and 'lightgrey' or \
+ s.ss < 30 and 'red' or \
+ s.ss < 35 and 'yellow' or \
+ s.ss < 40 and 'green' or 'lime'
# 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<path d="M%d %d l-8 -8 -8 8 8 8 8 -8" fill="%s"%s/>\n' %
- (x + 8, y, fill, opaque)
- )
+ fh.write(" ctx.beginPath(); ctx.fillStyle = '%s'; " % fill)
+ if s.PRN > 32: # Draw a square for SBAS satellites
+ fh.write("ctx.rect(%d, %d, 16, 16); " % (x - 8, y - 8))
else:
- self.fh.write(
- '\t\t\t<circle cx="%d" cy="%d" r="8" fill="%s"%s/>\n' %
- (x, y, fill, opaque)
- )
- self.fh.write('\t\t\t<text x="%d" y="%d">%d</text>\n' % (x - 6 + offset, y + 4, s.PRN))
+ fh.write("ctx.arc(%d, %d, 8, 0, circle, 0); " % (x, y))
+ fh.write("ctx.fill(); ctx.stroke(); ctx.strokeText('%s', %d, %d);\n" % (s.PRN, x - 6 + offset, y + 4))
- self.fh.write('\t\t</g>\n\t</g>\n</svg>\n')
+ fh.write("""
+ ctx.restore();
+}
+""")
def make_stale(self):
for t in self.sattrack.values():
@@ -219,40 +260,51 @@ width=\"425\" height=\"425\" type=\"image/svg+xml\" />\n\
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 generate_html(self, htmlfile, jsfile):
+ fh = open(htmlfile, 'w')
+ self.html(fh, jsfile)
+ fh.close()
+
+ def generate_js(self, jsfile):
+ fh = open(jsfile, 'w')
+ self.js(fh)
+ fh.close()
+
+ def run(self, suffix, period):
+ jsfile = 'gpsd' + suffix + '.js'
+ htmlfile = 'gpsd' + suffix + '.html'
+ end = time.time() + period
+ self.needsupdate = 1
+ self.stream(WATCH_ENABLE | WATCH_NEWSTYLE)
+ for report in self:
+ if report['class'] not in ('TPV', 'SKY'):
+ continue
+ self.update_tracks()
+ if self.needsupdate:
+ self.generate_js(jsfile)
+ self.needsupdate = 0
+ self.generate_html(htmlfile, jsfile)
+ if period <= 0 and self.fix.mode >= MODE_2D \
+ or 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]]
+ factors = {
+ 's': 1, 'm': 60, 'h': 60 * 60, 'd': 24 * 60 * 60
+ }
+ arg = argv and argv[0] or ''
+ if arg[-1:] in factors.keys():
+ period = int(arg[:-1]) * factors[arg[-1]]
+ elif arg == 'c':
+ period = None
+ elif arg:
+ period = int(arg)
else:
- period = int(period)
+ period = 0
+ if arg:
+ arg = '-' + arg
sat = SatTracks()
@@ -264,7 +316,7 @@ def main():
p.close()
try:
- sat.run(period)
+ sat.run(arg, period)
except KeyboardInterrupt:
# save the tracks
p = open(pfile, 'w')