123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408 |
- #!@PYSHEBANG@
- # -*- coding: utf-8 -*-
- # @GENERATED@
- # This file is Copyright 2020 by the GPSD project
- # SPDX-License-Identifier: BSD-2-clause
- # This code runs compatibly under Python 2 and 3.x for x >= 2.
- # Preserve this property!
- #
- # This code has been profiled and speed tested. Some code choices
- # that appear suboptimal are done for speed. Check all changes for
- # speed.
- #
- # Codacy D203 and D211 conflict, I choose D203
- # Codacy D212 and D213 conflict, I choose D212
- """gpsplot -- plot gpsd data in real time with matplotlib."""
- from __future__ import print_function
- import argparse
- import datetime
- import os
- import socket
- import sys
- import time # for time.time()
- if 'DISPLAY' not in os.environ or not os.environ['DISPLAY']:
- have_display = False
- else:
- have_display = True
- try:
- # Don't fail until after check for -h or -V.
- import matplotlib
- import matplotlib.pyplot
- have_matplotlib = True
- except (ImportError, RuntimeError):
- have_matplotlib = False
- # pylint wants local modules last
- try:
- import gps
- import gps.clienthelpers
- except ImportError as e:
- sys.stderr.write(
- "%s: can't load Python gps libraries -- check PYTHONPATH.\n" %
- (sys.argv[0]))
- sys.stderr.write("%s\n" % e)
- sys.exit(1)
- gps_version = '@VERSION@'
- if gps.__version__ != gps_version:
- sys.stderr.write("%s: ERROR: need gps module version %s, got %s\n" %
- (sys.argv[0], gps_version, gps.__version__))
- sys.exit(1)
- def _do_one_line(data):
- """Dump one report line."""
- # multicolor dots looked cool, but are 3x to 6x slower..
- if options.stripchart:
- if 'time' not in data:
- # pause so we do not block
- matplotlib.pyplot.pause(0.001)
- return
- timestr = datetime.datetime.strptime(data['time'],
- '%Y-%m-%dT%H:%M:%S.%fZ')
- if fields[0] in data and fields[2] in data:
- axs[0].plot(timestr, data[fields[0]], 'r.')
- axs[1].plot(timestr, data[fields[2]], 'g.')
- if fields[4] in data:
- axs[2].plot(timestr, data[fields[4]] * conversion.altfactor, 'b.')
- else:
- # must be scatterplot
- if fields[0] in data and fields[2] in data:
- axs[0].plot(data[fields[2]], data[fields[0]], 'r.', alpha=0.5)
- if fields[4] in data:
- axs[1].plot(0.5, data[fields[4]] * conversion.altfactor,
- 'g.', alpha=0.5)
- # pause so we do not block, pause() also draws
- matplotlib.pyplot.pause(0.001)
- if options.stripchart:
- try:
- # try to tighten up the plot. let it fail silently
- matplotlib.pyplot.tight_layout(h_pad=.2)
- except ValueError:
- pass
- # get default units from the environment
- # GPSD_UNITS, LC_MEASUREMENT and LANG
- default_units = gps.clienthelpers.unit_adjustments()
- description = 'Create dynamic plots from gpsd with matplotlib.'
- usage = '%(prog)s [OPTIONS] [host[:port[:device]]]'
- epilog = ('''
- The desired Matplotlib backend can be placed in the MPLBACKEND environment
- variable.
- BSD terms apply: see the file COPYING in the distribution root for details.
- '''
- )
- parser = argparse.ArgumentParser(
- description=description,
- epilog=epilog,
- formatter_class=argparse.RawDescriptionHelpFormatter,
- usage=usage)
- parser.add_argument(
- '-?',
- action="help",
- help='show this help message and exit'
- )
- parser.add_argument(
- '-b', '--backend',
- default='',
- dest='backend',
- metavar='BACKEND',
- help='Set the Matplotlib interactive backend to BACKEND.',
- )
- parser.add_argument(
- '-B', '--backends',
- action="store_true",
- dest='backends',
- help='Print available Matplotlib interactive backends, then exit',
- )
- parser.add_argument(
- '-D',
- '--debug',
- default=0,
- dest='debug',
- help='Set level of debug. Must be integer. [Default %(default)s]',
- type=int,
- )
- parser.add_argument(
- '--device',
- default='',
- dest='device',
- help='The device to connect. [Default %(default)s]',
- )
- parser.add_argument(
- '--exit',
- dest='exit',
- default=False,
- action="store_true",
- help='Exit after --count, --file, or --file completes'
- )
- parser.add_argument(
- '--fields',
- choices=['llh', 'llm'],
- default='llh',
- dest='fields',
- help='Fields to plot. [Default %(default)s]',
- )
- parser.add_argument(
- '-f', '--file',
- dest='input_file_name',
- default=None,
- metavar='FILE',
- help='Read gpsd JSON from FILE instead of a gpsd instance.',
- )
- parser.add_argument(
- '--host',
- default='localhost',
- dest='host',
- help='The host to connect. [Default %(default)s]',
- )
- parser.add_argument(
- '--image',
- dest='image_name',
- default=None,
- help=('Save plot as IMAGE.EXT. EXT determines image type '
- '(.jpg, .png, etc.)'),
- metavar='IMAGE.EXT',
- )
- parser.add_argument(
- '-n',
- '--count',
- default=0,
- dest='count',
- help=('Stop after COUNT messages to parse. 0 to disable. '
- ' [Default %(default)s]'),
- metavar='COUNT',
- type=int,
- )
- parser.add_argument(
- '--plottype',
- choices=['scatterplot', 'stripchart'],
- default='scatterplot',
- dest='plottype',
- help='Plot type. [Default %(default)s]',
- )
- parser.add_argument(
- '--port',
- default=gps.GPSD_PORT,
- dest='port',
- help='The port to connect. [Default %(default)s]',
- )
- parser.add_argument(
- '-u', '--units',
- choices=['i', 'imperial', 'n', 'nautical', 'm', 'metric'],
- default=default_units.name,
- dest='units',
- help='Units [Default %(default)s]',
- )
- parser.add_argument(
- '-V', '--version',
- action='version',
- help='Output version to stderr, then exit',
- version="%(prog)s: Version " + gps_version + "\n",
- )
- parser.add_argument(
- '-x',
- '--seconds',
- default=0,
- dest='seconds',
- help='Stop after SECONDS. 0 to disable. [Default %(default)s]',
- metavar='SECONDS',
- type=int,
- )
- parser.add_argument(
- 'target',
- help='[host[:port[:device]]]',
- nargs='?',
- )
- options = parser.parse_args()
- # allow -V and -h, above, before exiting on matplotlib, or DISPLAY, not found
- if not have_display:
- # matplotlib will not import w/o DISPLAY
- sys.stderr.write("gpsplot: ERROR: $DISPLAY not set\n")
- sys.exit(1)
- if not have_matplotlib:
- sys.stderr.write("gpsplot: ERROR: required Python module "
- "matplotlib not found\n")
- sys.exit(1)
- if options.backends:
- print("Available Matplotlib interactive backends:")
- for b in matplotlib.rcsetup.interactive_bk:
- # matplotlib.rcsetup.interactive_bk is all possible backends.
- # not guaranteed to work. Validate it works.
- try:
- matplotlib.pyplot.switch_backend(b)
- print(" ", b)
- except (ImportError, RuntimeError):
- # ImportError to shut up codacy
- continue
- sys.exit(0)
- # get conversion factors
- conversion = gps.clienthelpers.unit_adjustments(units=options.units)
- flds = {'llh': ('lat', 'Latitude', 'lon', 'Longitude', 'altHAE', 'altHAE'),
- 'llm': ('lat', 'Latitude', 'lon', 'Longitude', 'altMSL', 'altMSL'),
- }
- if options.fields not in flds:
- sys.stderr.write("gpsplot: Invalid --fields argument %s\n" %
- options.fields)
- sys.exit(1)
- fields = flds[options.fields]
- # the options host, port, device are set by the defaults
- if options.target:
- # override host, port and device with target
- arg = options.target.split(':')
- len_arg = len(arg)
- if len_arg == 1:
- (options.host,) = arg
- elif len_arg == 2:
- (options.host, options.port) = arg
- elif len_arg == 3:
- (options.host, options.port, options.device) = arg
- else:
- parser.print_help()
- sys.exit(0)
- if not options.port:
- options.port = gps.GPSD_PORT
- options.scatterplot = False
- options.stripchart = False
- if 'scatterplot' == options.plottype.lower():
- # scatterplot
- options.scatterplot = True
- else:
- # stripchart
- options.stripchart = True
- if ((options.exit and
- not options.count and
- not options.input_file_name and
- not options.seconds)):
- sys.stderr.write("gpsplot: --exit requires one of: "
- " --count, --file or --seconds")
- sys.exit(1)
- options.mclass = 'TPV'
- # Fields to parse
- # autodetect, read one message, use those fields
- options.json_fields = None
- options.frames = None
- options.subclass = None
- try:
- session = gps.gps(host=options.host, port=options.port,
- input_file_name=options.input_file_name,
- verbose=options.debug)
- except socket.error:
- sys.stderr.write("gpsplot: Could not connect to gpsd daemon\n")
- sys.exit(1)
- session.stream(gps.WATCH_ENABLE | gps.WATCH_SCALED, devpath=options.device)
- if options.backend:
- matplotlib.use(options.backend)
- # matplotlib.pyplot.ion() and matplotlib.pyplot.ioff()
- # seem to have no speed effect
- matplotlib.pyplot.ion()
- x = []
- y = []
- if options.scatterplot:
- fig = matplotlib.pyplot.figure(figsize=(7, 7))
- # x/y
- ax = fig.add_axes([0.18, 0.17, 0.65, 0.65])
- matplotlib.pyplot.xticks(rotation=30, ha='right')
- # z
- ax1 = fig.add_axes([0.85, 0.17, 0.02, 0.65])
- axs = [ax, ax1]
- for ax in axs:
- ax.ticklabel_format(useOffset=False)
- axs[0].tick_params(direction='in', top=True, right=True)
- axs[0].set_xlabel(fields[3])
- axs[0].set_ylabel(fields[1])
- axs[1].set_title("%s (%s)" % (fields[5], conversion.altunits))
- axs[1].tick_params(bottom=False, left=False, right=True,
- labeltop=True, labelbottom=False)
- axs[1].xaxis.set_visible(False)
- axs[1].yaxis.tick_right()
- elif options.stripchart:
- fig, axs = matplotlib.pyplot.subplots(3, sharex=True, figsize=(7, 7))
- for ax in axs:
- ax.ticklabel_format(useOffset=False)
- ax.tick_params(direction='in', top=True, right=True)
- axs[0].set_title(fields[1])
- axs[1].set_title(fields[3])
- axs[2].set_title("%s (%s)" % (fields[5], conversion.altunits))
- # matplotlib.pyplot.xticks(rotation=30, ha='right')
- fig.autofmt_xdate(rotation=30, ha='right')
- matplotlib.pyplot.subplots_adjust(left=0.16, bottom=0.10)
- else:
- sys.stderr.write("Error: Unknown plot type\n")
- sys.exit(1)
- count = 0
- if 0 < options.seconds:
- end_seconds = time.time() + options.seconds
- else:
- end_seconds = 0
- try:
- while True:
- try:
- report = session.next()
- except StopIteration:
- # end of data
- break
- if report['class'] != options.mclass:
- continue
- _do_one_line(report)
- if 0 < options.count:
- count += 1
- if count >= options.count:
- break
- if 0 < options.seconds:
- if time.time() > end_seconds:
- break
- except KeyboardInterrupt:
- # caught control-C
- # FIXME: plot is in different process, does not come here...
- print()
- sys.exit(1)
- if options.image_name:
- fig.savefig(options.image_name, dpi=200)
- if options.exit:
- sys.exit(0)
- # make the plot persist until window closed.
- matplotlib.pyplot.show(block=True)
|