Получение статистики с gpx файла

Сбор статистики с файла gpx (порядка 35 МБ)

Недавно проехал сокращенный маршрут Уральской байкпакинговой гонки 2026. Вышло 538 км и 2,5 суток в седле. Трек записывал велокомпьютером Coros Dura. После заезда хотел посмотреть такой параметр, как “время в движении”, но его не оказалось в приложении COROS.

Первая мысль — скачаю трек и загружу в любой сторонний сервис для анализа тренировок. Но, как оказалось, что не каждый ресурс переваривает такие объемы. А точнее я не нашел ресурса, который переварил бы мои метрики с гонки. Например, Strava принимает файлы до 25 МБ, а мой gpx файл весил целых 35 МБ.

Что хотелось узнать?

  • Время в движении
  • Время остановок:
    • Длинные на ночлег: более 2 часов
    • Короткие на отдых/перекусы/магазины: от 5 минут до 2 часов

Накидал простенький скрипт на python:

import xml.etree.ElementTree as ET
from datetime import datetime
import math
import os

# --- FILTER SETTINGS ---
# Minimum speed to consider as moving (in km/h).
MIN_MOVING_SPEED_KMH = 0.5 

# Minimum distance between points to consider as moving (in meters).
MIN_MOVING_DISTANCE_M = 1.5

# Maximum pause between points (in seconds). 
MAX_TIME_GAP_SEC = 30 

# Micro-stop threshold (in seconds). Stops shorter than this are considered as part of riding.
# 5 minutes = 300 seconds
MICRO_STOP_THRESHOLD_SEC = 5 * 60 

# Sleep threshold (in seconds). Stops longer than this are considered as sleep.
# 2 hours = 7200 seconds
SLEEP_THRESHOLD_SEC = 2 * 60 * 60 

def haversine(lat1, lon1, lat2, lon2):
    """Calculates distance between two coordinates in meters."""
    R = 6371000.0 
    phi1 = math.radians(lat1)
    phi2 = math.radians(lat2)
    dphi = math.radians(lat2 - lat1)
    dlambda = math.radians(lon2 - lon1)
    
    a = math.sin(dphi/2.0)**2 + math.cos(phi1) * math.cos(phi2) * math.sin(dlambda/2.0)**2
    c = 2 * math.atan2(math.sqrt(a), math.sqrt(1-a))
    return R * c

def parse_gpx(file_path):
    print(f"Читаем файл: {file_path} ({os.path.getsize(file_path) / (1024*1024):.2f} МБ)...")
    
    tree = ET.parse(file_path)
    root = tree.getroot()
    
    points = []
    
    for trkpt in root.iter('{http://www.topografix.com/GPX/1/1}trkpt'):
        lat = float(trkpt.get('lat'))
        lon = float(trkpt.get('lon'))
        time_str = trkpt.find('{http://www.topografix.com/GPX/1/1}time').text
        
        time_str = time_str.replace('Z', '+00:00')
        dt = datetime.fromisoformat(time_str)
        
        points.append({'lat': lat, 'lon': lon, 'time': dt})
        
    print(f"Найдено точек: {len(points)}")
    return points

def calculate_stats(points):
    total_distance = 0.0
    total_time_sec = 0.0
    moving_time_sec = 0.0
    short_stops_sec = 0.0  # Stops from 5 minutes to 2 hours
    sleep_time_sec = 0.0   # Stops 2 hours or more
    
    current_stop_duration = 0.0 
    
    for i in range(1, len(points)):
        p1 = points[i-1]
        p2 = points[i]
        
        dist = haversine(p1['lat'], p1['lon'], p2['lat'], p2['lon'])
        time_delta = (p2['time'] - p1['time']).total_seconds()
        
        if time_delta <= 0:
            continue
            
        total_distance += dist
        total_time_sec += time_delta
        
        speed_ms = dist / time_delta
        speed_kmh = speed_ms * 3.6
        
        is_moving = (dist >= MIN_MOVING_DISTANCE_M) and (speed_kmh >= MIN_MOVING_SPEED_KMH) and (time_delta <= MAX_TIME_GAP_SEC)
        
        if is_moving:
            moving_time_sec += time_delta
            
            # If we are moving, the previous stop has ended. Sorting it:
            if current_stop_duration > 0:
                if current_stop_duration >= SLEEP_THRESHOLD_SEC:
                    sleep_time_sec += current_stop_duration
                elif current_stop_duration > MICRO_STOP_THRESHOLD_SEC:
                    short_stops_sec += current_stop_duration
                else:
                    # Stops less than 5 minutes are merged into moving time
                    moving_time_sec += current_stop_duration 
                    
                current_stop_duration = 0.0
        else:
            current_stop_duration += time_delta
            
    # Check the final stop at the end of the track
    if current_stop_duration > 0:
        if current_stop_duration >= SLEEP_THRESHOLD_SEC:
            sleep_time_sec += current_stop_duration
        elif current_stop_duration > MICRO_STOP_THRESHOLD_SEC:
            short_stops_sec += current_stop_duration
        else:
            moving_time_sec += current_stop_duration
            
    return total_distance, total_time_sec, moving_time_sec, short_stops_sec, sleep_time_sec

def format_time(seconds):
    """Converts seconds to HH:MM:SS format"""
    hours = int(seconds // 3600)
    minutes = int((seconds % 3600) // 60)
    secs = int(seconds % 60)
    return f"{hours:02d}:{minutes:02d}:{secs:02d}"

if __name__ == "__main__":
    # Specify your file name here:
    GPX_FILE = "my_ride.gpx" 
    
    if not os.path.exists(GPX_FILE):
        print(f"ОШИБКА: Файл '{GPX_FILE}' не найден!")
    else:
        pts = parse_gpx(GPX_FILE)
        dist, total_t, mov_t, short_t, sleep_t = calculate_stats(pts)
        
        print("\n" + "="*50)
        print(f"Общая дистанция:                        {dist / 1000:.2f} км")
        print(f"Общее время:                            {format_time(total_t)} (от старта до финиша)")
        print("-" * 50)
        print(f"ВРЕМЯ В ДВИЖЕНИИ:                       {format_time(mov_t)}")
        print(f"Остановки:                              {format_time(short_t)} (от 5 мин до 2 часов)")
        print(f"Время остановки на ночлег:              {format_time(sleep_t)} (более 2 часов)")
        print("="*50)
        if mov_t > 0:
            print(f"Средняя скорость (в движении):   {(dist/1000) / (mov_t/3600):.2f} км/ч")
        if total_t > 0:
            print(f"Средняя скорость (общая):        {(dist/1000) / (total_t/3600):.2f} км/ч")

Вывод работы скрипта:

python3 gpx_calc.py
Читаем файл: my_ride.gpx (34.30 МБ)...
Найдено точек: 134273

==================================================
Общая дистанция:                        538.94 км
Общее время:                            61:52:21 (от старта до финиша)
--------------------------------------------------
ВРЕМЯ В ДВИЖЕНИИ:                       39:26:49
Остановки:                              07:20:54 (от 5 мин до 2 часов)
Время остановки на ночлег:              15:04:38 (более 2 часов)
==================================================
Средняя скорость (в движении):   13.66 км/ч
Средняя скорость (общая):        8.71 км/ч
Создано при помощи Hugo
Тема Stack, дизайн Jimmy