4、GPX文件解析:用Python把轨迹数据“榨”出来

说实话,我第一次接触GPX文件时,还以为它是什么高深的二进制格式。结果用记事本打开一看——嚯,这不就是XML嘛!

GPX全称是GPS Exchange Format,说白了就是GPS数据的通用交换格式。你从佳明手表、两步路、甚至手机上的运动App导出的轨迹,十有八九都是这个格式。我当年做户外轨迹分析项目时,光是解析不同设备导出的GPX就折腾了好几天——各家厂商的“方言”还真不一样。

核心要点:GPX本质是XML,解析它就是在解析一棵XML树。我们关心的数据藏在 <trkpt> 标签里。

4.1 为什么选gpxpy?

Python里解析GPX有好几种路子:

  • 自己写XML解析——用ElementTree硬啃,灵活但累
  • 用gpxpy——专门干这活的库,省心
  • 用geopandas——杀鸡用牛刀,适合批量处理

我个人习惯用gpxpy。为什么?因为它把时间解析、坐标转换、海拔提取这些脏活都包了。你想想看,要是自己处理时区、处理不同版本的GPX命名空间……嗯,那画面太美我不敢看。

安装命令:pip install gpxpy

就一行,没别的依赖。清爽。

4.2 解析流程:三步走

解析GPX其实就三步:读文件 → 解析 → 提取数据。我画了张图帮你理清思路:

GPX文件解析流程 ① 读取文件 open() 或 字符串 ② 解析GPX gpxpy.parse() ③ 提取数据 遍历 track.segments 📊 提取的信息 经纬度 · 时间戳 · 海拔 · 速度 → 可用于回放、分析、可视化

4.3 实战代码:从文件到数据

来,直接上代码。这是我项目里常用的模板,稍微简化了一下:

import gpxpy

# 第一步:读文件
with open('my_track.gpx', 'r', encoding='utf-8') as f:
    gpx_content = f.read()

# 第二步:解析
gpx = gpxpy.parse(gpx_content)

# 第三步:提取轨迹点
points = []
for track in gpx.tracks:
    for segment in track.segments:
        for point in segment.points:
            points.append({
                'lat': point.latitude,
                'lon': point.longitude,
                'time': point.time,
                'elevation': point.elevation,
                'speed': point.speed if hasattr(point, 'speed') else None
            })

print(f"共提取 {len(points)} 个轨迹点")
print(f"第一个点:{points[0]}")

⚠️ 注意:不是所有GPX文件都包含速度信息。有些设备不记录速度,需要你自己根据距离和时间计算。我曾经遇到过一批从某国产手表导出的GPX,速度字段全是None——当时排查了半天才发现是设备本身就没记录。

4.4 提取的信息详解

每个轨迹点里藏着哪些宝贝?我整理了一张表:

字段 类型 说明 常见问题
latitude float 纬度,范围 -90 ~ 90 注意WGS84坐标系
longitude float 经度,范围 -180 ~ 180 东经为正,西经为负
time datetime UTC时间戳 记得转本地时间
elevation float 海拔高度(米) 部分设备不准
speed float 速度(m/s) 可能缺失

小技巧:如果速度字段缺失,可以用前后两个点的距离除以时间差来估算。公式很简单:speed = distance / delta_time。我一般用 geopy.distance 算距离,精度够用。

4.5 避坑指南:我踩过的三个坑

做GPX解析这几年,我踩过的坑真不少。挑三个最典型的说说:

  1. 编码问题——有些国产软件导出的GPX是GBK编码,直接open会报错。我后来统一用 encoding='utf-8' 先试,不行就换 gbk。
  2. 时间格式——GPX标准要求ISO 8601格式,但有些设备会带毫秒、有些带时区偏移。gpxpy能处理大部分情况,但偶尔也会翻车。我遇到过一种奇葩格式:时间后面跟了个"Z"但实际是北京时间……
  3. 空轨迹段——有些GPX文件有track但没有segment,或者segment里没有points。遍历前一定要判空,不然 for segment in track.segments 直接报错。

我的经验:拿到一个GPX文件后,先别急着解析。用文本编辑器打开看一眼——看看有没有 <trkpt> 标签,看看时间格式长什么样。这一步花不了30秒,但能省下后面半小时的调试时间。

4.6 完整示例:解析并打印轨迹摘要

最后,给你一个可以直接跑的完整示例。我平时做数据质量检查就用这个:

import gpxpy
from datetime import datetime

def parse_gpx_summary(filepath):
    with open(filepath, 'r', encoding='utf-8') as f:
        gpx = gpxpy.parse(f.read())
    
    total_points = 0
    start_time = None
    end_time = None
    max_ele = float('-inf')
    min_ele = float('inf')
    
    for track in gpx.tracks:
        for segment in track.segments:
            for point in segment.points:
                total_points += 1
                if start_time is None or point.time < start_time:
                    start_time = point.time
                if end_time is None or point.time > end_time:
                    end_time = point.time
                if point.elevation and point.elevation > max_ele:
                    max_ele = point.elevation
                if point.elevation and point.elevation < min_ele:
                    min_ele = point.elevation
    
    duration = (end_time - start_time).total_seconds() if start_time and end_time else 0
    
    print(f"轨迹点数量:{total_points}")
    print(f"开始时间:{start_time}")
    print(f"结束时间:{end_time}")
    print(f"持续时间:{duration:.0f} 秒 ({duration/60:.1f} 分钟)")
    print(f"最高海拔:{max_ele:.1f} 米")
    print(f"最低海拔:{min_ele:.1f} 米")
    
    return {
        'points': total_points,
        'start': start_time,
        'end': end_time,
        'duration': duration,
        'max_elevation': max_ele,
        'min_elevation': min_ele
    }

# 使用示例
summary = parse_gpx_summary('my_track.gpx')

嗯,这个函数我用了两年多了,从没出过问题。你拿去改改就能用。

解析GPX其实没那么玄乎。记住核心思路:读文件 → 解析 → 遍历提取。剩下的就是根据你的业务需求,从每个轨迹点里捞出你需要的信息。经纬度、时间、海拔、速度——这四个字段够你玩出花来了。

可可精品资料库