3. 轨迹数据解析:GPX文件结构解析、Python解析GPX文件、提取轨迹点与时间戳、轨迹数据清洗

好,咱们进入实战环节。上一章我们把地图底图搞定了,现在该处理真正的轨迹数据了。你想想看,不管是跑步、骑行还是开车,记录下来的数据大多以GPX格式存在。这玩意儿长什么样?怎么把它读进Python里?读进来之后怎么清洗?这一章我带你一步步搞定。

3.1 GPX文件结构解析

GPX,全称GPS Exchange Format,说白了就是一种基于XML的文本格式。我最早接触它的时候,还以为是某种加密格式,打开一看——嗯,就是纯文本,结构非常清晰。

一个标准的GPX文件长这样:

<?xml version="1.0" encoding="UTF-8"?>
<gpx version="1.1" creator="Garmin">
  <trk>
    <name>下午骑行</name>
    <trkseg>
      <trkpt lat="39.9042" lon="116.4074">
        <ele>52.3</ele>
        <time>2024-01-15T14:30:00Z</time>
      </trkpt>
      <trkpt lat="39.9045" lon="116.4078">
        <ele>52.5</ele>
        <time>2024-01-15T14:30:05Z</time>
      </trkpt>
    </trkseg>
  </trk>
</gpx>

核心结构就三层:

  • gpx:根节点,包含版本号和创建者信息
  • trk:一条轨迹,可以包含名称、描述等元数据
  • trkseg:轨迹段,里面是一个个轨迹点
  • trkpt:单个轨迹点,包含经纬度、海拔、时间戳

我在项目中遇到过一个问题:有些设备导出的GPX文件,<ele>和<time>标签可能缺失。嗯,这很正常,不是所有GPS芯片都记录海拔。所以解析的时候一定要做容错处理。

关键点:GPX的核心是<trkpt>节点,它的lat和lon属性是必须的,其他都是可选。

3.2 Python解析GPX文件

Python解析GPX,我推荐两种方式。第一种是用现成的库gpxpy,省心省力。第二种是自己手写XML解析,适合定制化需求。

方式一:使用gpxpy库

我个人习惯用这个库,因为它把轨迹段、点的提取都封装好了。安装很简单:

pip install gpxpy

解析代码:

import gpxpy

with open('track.gpx', 'r', encoding='utf-8') as f:
    gpx = gpxpy.parse(f)

for track in gpx.tracks:
    print(f"轨迹名称: {track.name}")
    for segment in track.segments:
        for point in segment.points:
            print(f"纬度: {point.latitude}, 经度: {point.longitude}")
            print(f"海拔: {point.elevation}, 时间: {point.time}")

你看,几行代码就把所有轨迹点读出来了。不过要注意,gpxpy返回的时间是datetime对象,可以直接用。

方式二:手写XML解析

有时候你需要更精细的控制,比如只提取某些字段。这时候用xml.etree.ElementTree更灵活:

import xml.etree.ElementTree as ET

tree = ET.parse('track.gpx')
root = tree.getroot()

# GPX的命名空间
ns = {'gpx': 'http://www.topografix.com/GPX/1/1'}

points = []
for trkpt in root.findall('.//gpx:trkpt', ns):
    lat = float(trkpt.get('lat'))
    lon = float(trkpt.get('lon'))
    ele = trkpt.find('gpx:ele', ns)
    time = trkpt.find('gpx:time', ns)
    
    point = {
        'lat': lat,
        'lon': lon,
        'ele': float(ele.text) if ele is not None else None,
        'time': time.text if time is not None else None
    }
    points.append(point)

小技巧:手写解析时,记得处理命名空间。GPX 1.1的命名空间是http://www.topografix.com/GPX/1/1,别写错了。

3.3 提取轨迹点与时间戳

提取轨迹点不难,但时间戳的处理是个坑。我曾经遇到过一个设备,它记录的时间是本地时间,没有带时区信息。结果对齐到地图上,轨迹全偏了。

正确的做法是:

  1. 统一将时间转换为UTC
  2. 如果原始数据没有时区,默认当作UTC处理
  3. 存储时使用datetime对象,方便后续计算

示例代码:

from datetime import datetime, timezone

def parse_time(time_str):
    """解析GPX时间字符串,统一转为UTC"""
    if time_str is None:
        return None
    try:
        # 尝试解析带时区的时间
        dt = datetime.fromisoformat(time_str.replace('Z', '+00:00'))
        return dt.astimezone(timezone.utc)
    except:
        # 如果没有时区,当作UTC处理
        return datetime.strptime(time_str, '%Y-%m-%dT%H:%M:%S').replace(tzinfo=timezone.utc)

注意:时间戳是轨迹对齐的关键。如果时间不准,后面的地图匹配、速度计算全都会出错。我建议你在解析完成后,先检查一下时间序列是否单调递增。

3.4 轨迹数据清洗

数据读进来了,但别急着用。真实世界的GPS数据,脏得很。我总结了几种常见问题:

问题类型 表现 处理方法
重复点 连续两个点经纬度完全一样 删除重复点,只保留第一个
漂移点 某个点突然偏离很远 计算相邻点距离,超过阈值则剔除
时间跳跃 两个点的时间间隔异常大 检查时间差,超过阈值则分段处理
海拔异常 海拔值突然变化几百米 使用滑动窗口平滑或剔除

清洗代码示例:

import math

def clean_track(points, max_speed=50.0):
    """
    清洗轨迹点
    :param points: 轨迹点列表,每个点包含lat, lon, time
    :param max_speed: 最大允许速度(米/秒)
    """
    cleaned = []
    prev = None
    
    for point in points:
        if prev is None:
            cleaned.append(point)
            prev = point
            continue
        
        # 计算距离(简化版,实际用haversine公式)
        dist = haversine(prev['lat'], prev['lon'], point['lat'], point['lon'])
        time_diff = (point['time'] - prev['time']).total_seconds()
        
        if time_diff > 0:
            speed = dist / time_diff
            if speed > max_speed:
                # 漂移点,跳过
                continue
        
        # 检查重复点
        if dist < 0.5:  # 小于0.5米视为重复
            continue
            
        cleaned.append(point)
        prev = point
    
    return cleaned

避坑指南:我曾经处理过一份骑行数据,里面有个点经纬度完全正确,但时间戳比前后点晚了整整一天。这种点用速度过滤是查不出来的,必须检查时间序列的连续性。我的做法是:计算每个点与前后点的时间差,如果某个点的时间差同时大于前后两个时间差,就标记为异常。

3.5 本章知识体系

下面这张图帮你梳理本章的核心逻辑:

轨迹数据解析流程 GPX文件 XML格式轨迹数据 Python解析 gpxpy / ElementTree 提取轨迹点 经纬度 + 时间戳 清洗 去重 去漂移 时间连续性检查 海拔平滑 干净的轨迹点序列

嗯,这一章的内容就这些。GPX解析看起来简单,但实际项目中坑不少。我建议你拿到数据后,先打印几个点看看结构,再决定用哪种解析方式。清洗这一步千万别省,否则后面地图匹配的时候,你会被漂移点折磨到怀疑人生。

可可精品资料库