diff options
Diffstat (limited to 'fietsboek/convert.py')
| -rw-r--r-- | fietsboek/convert.py | 86 |
1 files changed, 72 insertions, 14 deletions
diff --git a/fietsboek/convert.py b/fietsboek/convert.py index d3bfb22..2e8b5db 100644 --- a/fietsboek/convert.py +++ b/fietsboek/convert.py @@ -1,7 +1,12 @@ """Conversion functions to convert between various recording formats.""" +import datetime +from typing import Optional import fitparse -from gpxpy.gpx import GPX, GPXTrack, GPXTrackPoint, GPXTrackSegment +import gpxpy + +from . import geo, util +from .models import Track FIT_RECORD_FIELDS = ["position_lat", "position_long", "altitude", "timestamp"] @@ -15,8 +20,8 @@ def semicircles_to_deg(circles: int) -> float: return circles * (180 / 2**31) -def from_fit(data: bytes) -> GPX: - """Reads a .fit as GPX data. +def from_fit(data: bytes) -> Track: + """Reads a .fit as track data. This uses the fitparse_ library under the hood. @@ -26,30 +31,81 @@ def from_fit(data: bytes) -> GPX: :return: The converted structure. """ fitfile = fitparse.FitFile(data) + start_time = None points = [] for record in fitfile.get_messages("record"): values = record.get_values() try: if any(values[field] is None for field in FIT_RECORD_FIELDS): continue - point = GPXTrackPoint( + time = values["timestamp"] + if start_time is None: + start_time = time + point = geo.Point( latitude=semicircles_to_deg(values["position_lat"]), longitude=semicircles_to_deg(values["position_long"]), elevation=values["altitude"], - time=values["timestamp"], + time_offset=time - start_time, ) except KeyError: pass else: points.append(point) - track = GPXTrack() - track.segments = [GPXTrackSegment(points)] - gpx = GPX() - gpx.tracks = [track] - return gpx + path = geo.Path(points) + track = Track() + track.set_path(path) + return track + + +def from_gpx(data: bytes) -> Track: + """Reads a .gpx as track data. + + This uses the gpxpy_ library under the hood. + .. _gpxpy: https://github.com/tkrajina/gpxpy -def smart_convert(data: bytes) -> bytes: + :param data: The input bytes. + :return: The converted structure. + """ + gpx = gpxpy.parse(data) + points = [] + start_time = None + + for track in gpx.tracks: + for segment in track.segments: + for point in segment.points: + if start_time is None: + start_time = point.time + + time_offset = (point.time - start_time).total_seconds() + points.append(geo.Point( + longitude=point.longitude, + latitude=point.latitude, + elevation=point.elevation, + time_offset=time_offset, + )) + + timezone = util.guess_gpx_timezone(gpx) + date = gpx.time or gpx.get_time_bounds().start_time or datetime.datetime.now() + date = date.astimezone(timezone) + track_name = gpx.name + track_desc = gpx.description + for track in gpx.tracks: + if not track_name and track.name: + track_name = track.name + if not track_desc and track.description: + track_desc = track.description + + path = geo.Path(points) + track = Track() + track.set_path(path) + track.title = track_name + track.description = track_desc + track.date = date + return track + + +def smart_convert(data: bytes) -> Optional[Track]: """Tries to be smart in converting the input bytes. This function automatically applies the correct conversion if possible. @@ -61,8 +117,10 @@ def smart_convert(data: bytes) -> bytes: :return: The converted content. """ if len(data) > 11 and data[9:12] == b"FIT": - return from_fit(data).to_xml().encode("utf-8") - return data + return from_fit(data) + if data.startswith(b"<?xml") and b"<gpx" in data[:200]: + return from_gpx(data) + return None -__all__ = ["from_fit", "smart_convert"] +__all__ = ["from_fit", "from_gpx", "smart_convert"] |
