diff --git a/my/core/__main__.py b/my/core/__main__.py index 05f5a2c..dce646a 100644 --- a/my/core/__main__.py +++ b/my/core/__main__.py @@ -542,6 +542,14 @@ def query_hpi_functions( pprint(item) else: pprint(list(res)) + elif output == 'gpx': + from my.location.common import locations_to_gpx + + # can ignore the mypy warning here, locations_to_gpx yields any errors + # if you didnt pass it something that matches the LocationProtocol + for exc in locations_to_gpx(res, sys.stdout): # type: ignore[arg-type] + click.echo(str(exc), err=True) + sys.stdout.flush() else: res = list(res) # type: ignore[assignment] # output == 'repl' @@ -681,7 +689,7 @@ def module_install_cmd(user: bool, parallel: bool, modules: Sequence[str]) -> No @click.option('-o', '--output', default='json', - type=click.Choice(['json', 'pprint', 'repl']), + type=click.Choice(['json', 'pprint', 'repl', 'gpx']), help='what to do with the result [default: json]') @click.option('-s', '--stream', diff --git a/my/location/common.py b/my/location/common.py index fa8bdad..5c03d5e 100644 --- a/my/location/common.py +++ b/my/location/common.py @@ -1,5 +1,5 @@ from datetime import date, datetime -from typing import Union, Tuple, Optional +from typing import Union, Tuple, Optional, Iterable, TextIO, Iterator from dataclasses import dataclass from my.core import __NOT_HPI_MODULE__ @@ -32,3 +32,48 @@ class Location(LocationProtocol): accuracy: Optional[float] elevation: Optional[float] datasource: Optional[str] = None # which module provided this, useful for debugging + + +def locations_to_gpx(locations: Iterable[LocationProtocol], buffer: TextIO) -> Iterator[Exception]: + """ + Convert locations to a GPX file, printing to a buffer (an open file, io.StringIO, sys.stdout, etc) + """ + + try: + import gpxpy.gpx + except ImportError as ie: + from my.core.warnings import warn + + warn("gpxpy not installed, cannot write to gpx. 'pip install gpxpy'") + raise ie + + gpx = gpxpy.gpx.GPX() + + # hmm -- would it be useful to allow the user to split this into tracks?, perhaps by date? + + # Create first track in our GPX: + gpx_track = gpxpy.gpx.GPXTrack() + gpx.tracks.append(gpx_track) + + # Create first segment in our GPX track: + gpx_segment = gpxpy.gpx.GPXTrackSegment() + gpx_track.segments.append(gpx_segment) + + + for location in locations: + try: + point = gpxpy.gpx.GPXTrackPoint( + latitude=location.lat, + longitude=location.lon, + elevation=location.elevation, + time=location.dt, + comment=location.datasource, + ) + except AttributeError: + yield TypeError( + f"Expected a Location or Location-like object, got {type(location)} {repr(location)}" + ) + continue + gpx_segment.points.append(point) + + buffer.write(gpx.to_xml()) diff --git a/tox.ini b/tox.ini index e3ff8f1..2809e3c 100644 --- a/tox.ini +++ b/tox.ini @@ -85,6 +85,7 @@ allowlist_externals = cat commands = pip install -e .[testing,optional] pip install orgparse # used it core.orgmode? + pip install gpxpy # for hpi query --output gpx {envpython} -m mypy --install-types --non-interactive \ -p my.core \