Source code for gpmf.gps

from collections import namedtuple
from datetime import datetime, timedelta
from xml.etree import ElementTree as ET

import gpxpy
from . import parse


GPSData = namedtuple("GPSData",
                     [
                         "description",
                         "timestamp",
                         "precision",
                         "fix",
                         "latitude",
                         "longitude",
                         "altitude",
                         "speed_2d",
                         "speed_3d",
                         "units",
                         "npoints"
                     ])


[docs] def extract_gps_blocks(stream): """ Extract GPS data blocks from binary stream This is a generator on lists `KVLItem` objects. In the GPMF stream, GPS data comes into blocks of several different data items. For each of these blocks we return a list. Parameters ---------- stream: bytes The raw GPMF binary stream Returns ------- gps_items_generator: generator Generator of lists of `KVLItem` objects """ for s in parse.filter_klv(stream, "STRM"): content = [] is_gps = False # GPS5 (Hero 5-11) or GPS9 (Hero 11-13) for elt in s.value: content.append(elt) if elt.key in ["GPS5", "GPS9"]: # Support both GPS5 and GPS9 is_gps = True if is_gps: yield content
[docs] def parse_gps_block(gps_block): """Turn GPS data blocks into `GPSData` objects Supports both GPS5 (Hero 5-10) and GPS9 (Hero 11+) streams Parameters ---------- gps_block: list of KVLItem A list of KVLItem corresponding to a GPS data block. Returns ------- gps_data: GPSData A GPSData object holding the GPS information of a block. """ block_dict = { s.key: s for s in gps_block } # Hero 11-13 support: use GPS9 if available (10Hz), fall back to GPS5 gps_key = "GPS9" if "GPS9" in block_dict else "GPS5" if gps_key == "GPS9": # GPS9: complex structure with 9 fields (Hero 11+) # Extract first 5 fields (lat, lon, alt, speed_2d, speed_3d) for compatibility gps_values = block_dict["GPS9"].value # Handle both array and single-value cases if hasattr(gps_values, 'shape') and len(gps_values.shape) > 1: # Multi-sample case: use first 5 columns gps_data = gps_values[:, :5] * 1.0 / block_dict["SCAL"].value[:5] else: # Single sample case gps_data = gps_values[:5] * 1.0 / block_dict["SCAL"].value[:5] else: # GPS5: traditional 5-field structure (Hero 5-10) gps_data = block_dict["GPS5"].value * 1.0 / block_dict["SCAL"].value latitude, longitude, altitude, speed_2d, speed_3d = gps_data.T return GPSData( description=block_dict["STNM"].value, timestamp=block_dict["GPSU"].value, precision=block_dict["GPSP"].value / 100., fix=block_dict["GPSF"].value, latitude=latitude, longitude=longitude, altitude=altitude, speed_2d=speed_2d, speed_3d=speed_3d, units=block_dict["UNIT"].value, npoints=len(gps_data) )
FIX_TYPE = { 0: "none", 2: "2d", 3: "3d" } def _make_speed_extensions(gps_data, i): speed_2d = ET.Element("speed_2d") value = ET.SubElement(speed_2d, "value") value.text = "%g" % gps_data.speed_2d[i] unit = ET.SubElement(speed_2d, "unit") unit.text = "m/s" speed_3d = ET.Element("speed_3d") value = ET.SubElement(speed_3d, "value") value.text = "%g" % gps_data.speed_3d[i] unit = ET.SubElement(speed_3d, "unit") unit.text = "m/s" return [speed_2d, speed_3d]
[docs] def make_pgx_segment(gps_blocks, first_only=False, speeds_as_extensions=True): """Convert a list of GPSData objects into a GPX track segment. Parameters ---------- gps_blocks: list of GPSData A list of GPSData objects first_only: bool, optional (default=False) If True use only the first GPS entry of each data block. speeds_as_extensions: bool, optional (default=True) If True, include 2d and 3d speed values as exentensions of the GPX trackpoints. This is especially useful when saving to GPX 1.1 format. Returns ------- gpx_segment: gpxpy.gpx.GPXTrackSegment A gpx track segment. """ track_segment = gpxpy.gpx.GPXTrackSegment() dt = timedelta(seconds=1.0 / 18.) for gps_data in gps_blocks: time = datetime.strptime(gps_data.timestamp, "%Y-%m-%d %H:%M:%S.%f") # Reference says the frequency is about 18 Hz and other GPS data about 1Hz stop = 1 if first_only else gps_data.npoints for i in range(stop): tp = gpxpy.gpx.GPXTrackPoint( latitude=gps_data.latitude[i], longitude=gps_data.longitude[i], elevation=gps_data.altitude[i], speed=gps_data.speed_3d[i], position_dilution=gps_data.precision, time=time + i * dt, symbol="Square", ) tp.type_of_gpx_fix = FIX_TYPE[gps_data.fix] if speeds_as_extensions: for e in _make_speed_extensions(gps_data, 0): tp.extensions.append(e) track_segment.points.append(tp) return track_segment