From ec958ce6c875ce9ff1bc447727a364710de7d078 Mon Sep 17 00:00:00 2001 From: Freezed <2160318-free_zed@users.noreply.gitlab.com> Date: Sun, 27 Mar 2022 18:47:38 +0200 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20Get=20GPX=20data=20summary=20from?= =?UTF-8?q?=20file(s)=20#2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Relies on `gpxpy` library - Can be used as a standalone module TODO: remove print() by a logger (see #11) --- README.md | 3 +- cli/gpx.py | 124 +++++++++++++++++++++++++++++++++++++++++++++++ requirements.txt | 1 + 3 files changed, 127 insertions(+), 1 deletion(-) create mode 100755 cli/gpx.py diff --git a/README.md b/README.md index e09aaaf..dc196be 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 0000000..7ba7e6c --- /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 19d8a2a..ac074f1 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1,3 @@ click==8.0.4 +gpxpy==1.5.0 strava-cli==0.6.1 -- GitLab