diff --git a/README.md b/README.md index e09aaaf9c8a34b7599ddcfd9ef2d490041f91d36..dc196be9ffb757b36e1d2364b491ea830029a690 100644 --- a/README.md +++ b/README.md @@ -58,7 +58,8 @@ python cli/run.py --help Details in [`requirements.txt`](requirements.txt) & [`requirements-dev.txt`](requirements-dev.txt) -- [`strava-cli`](https://github.com/bwilczynski/strava-cli) +- [`gpxpy`](https://github.com/tkrajina/gpxpy/#readme) : GPX file manipulation +- [`strava-cli`](https://github.com/bwilczynski/strava-cli#readme) : Use Strava's API on command line ### 🤠Contributing diff --git a/cli/gpx.py b/cli/gpx.py new file mode 100755 index 0000000000000000000000000000000000000000..7ba7e6c675998d8e2f362272f60211b085f3cd4e --- /dev/null +++ b/cli/gpx.py @@ -0,0 +1,124 @@ +#!/usr/bin/env python3 +# coding:utf-8 +"""GPX manipulation module. + +Process a GPX file to get informations. +""" + +import sys as mod_sys +import logging as mod_logging +from typing import Union, List + +import gpxpy as mod_gpxpy +import gpxpy.gpx as mod_gpx + + +def get_gpx_part_info( + gpx_part: Union[mod_gpx.GPX, mod_gpx.GPXTrack, mod_gpx.GPXTrackSegment], +) -> None: + """gpx_part may be a track or segment.""" + gpx_part_summary = {} + + gpx_part_summary.update({"length_2d": gpx_part.length_2d() or 0}) + gpx_part_summary.update({"": gpx_part.length_3d()}) + + moving_data = gpx_part.get_moving_data() + if moving_data: + gpx_part_summary.update({"moving_time": moving_data.moving_time}) + gpx_part_summary.update({"stopped_time": moving_data.stopped_time}) + gpx_part_summary.update({"max_speed": moving_data.max_speed}) + gpx_part_summary.update( + { + "avg_speed": moving_data.moving_distance / moving_data.moving_time + if moving_data.moving_time > 0 + else "?" + } + ) + + uphill, downhill = gpx_part.get_uphill_downhill() + gpx_part_summary.update({"uphill": uphill}) + gpx_part_summary.update({"downhill": downhill}) + + start_time, end_time = gpx_part.get_time_bounds() + gpx_part_summary.update({"start_time": start_time}) + gpx_part_summary.update({"end_time": end_time}) + + points_no = len(list(gpx_part.walk(only_points=True))) + gpx_part_summary.update({"point_no": points_no}) + + if points_no > 0: + distances: List[float] = [] + previous_point = None + for point in gpx_part.walk(only_points=True): + if previous_point: + distance = point.distance_2d(previous_point) + distances.append(distance) + previous_point = point + gpx_part_summary.update( + {"avg_dist_points": sum(distances) / len(list(gpx_part.walk()))} + ) + + return gpx_part_summary + + +def get_gpx_info(gpx: mod_gpx.GPX, gpx_file: str) -> None: + """Get all information of the file. + + Top level info & whole track move info are agregated. + If the file had more than one track/segment, move info are + calculated over all. + """ + gpx_summary = {} + + if gpx.name: + gpx_summary.update({"name": gpx.name}) + if gpx.description: + gpx_summary.update({"description": gpx.description}) + if gpx.author_name: + gpx_summary.update({"author_name": gpx.author_name}) + if gpx.author_email: + gpx_summary.update({"author_email": gpx.author_email}) + + gpx_summary.update({"file_name": gpx_file}) + gpx_summary.update({"waypoints": len(gpx.waypoints)}) + gpx_summary.update({"routes": len(gpx.routes)}) + gpx_summary.update({"tracks": len(gpx.tracks)}) + # gpx_summary.update({"segments": len(track.segments)}) + + gpx_summary.update(get_gpx_part_info(gpx)) + return gpx_summary + + +def run(gpx_files: List[str]) -> None: + """Manage GPX file in input.""" + if not gpx_files: + mod_logging.exception("No GPX files given") + mod_sys.exit(1) + + for gpx_file in gpx_files: + with open(gpx_file, encoding="utf-8") as open_file: + gpx = mod_gpxpy.parse(open_file) + + try: + return get_gpx_info(gpx, gpx_file) + except mod_gpx.GPXXMLSyntaxException as except_detail: + mod_logging.exception(except_detail) + mod_sys.exit(1) + except FileNotFoundError as except_detail: + mod_logging.exception(except_detail) + mod_sys.exit(1) + except Exception as except_detail: # pylint: disable=W0703 + mod_logging.exception(except_detail) + mod_sys.exit(1) + + +if __name__ == "__main__": + import argparse as mod_argparse + + parser = mod_argparse.ArgumentParser( + usage="%(prog)s [-h] path_to_gpx_file(s)", + description="Command line utility to extract basic statistics from gpx file(s)", + ) + args, local_files = parser.parse_known_args() + + print(run(gpx_files=local_files)) diff --git a/requirements.txt b/requirements.txt index 19d8a2af3fc9092fb02c98691192ba55638d15b5..ac074f1865a97d2611069edf677a363b0700c9e9 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1,3 @@ click==8.0.4 +gpxpy==1.5.0 strava-cli==0.6.1