Przejdź do głównej treści
Grafika przedstawia ukryty obrazek

Jak połączyć i oczyścić pliki GPX w Pythonie — kompletny przewodnik

Grafika SVG Trasa

Format GPX (GPS Exchange Format) to popularny standard wymiany danych GPS, wykorzystywany w urządzeniach Garmin, Suunto, Wahoo, w aplikacjach sportowych, trackerach outdoorowych i mapach online. Często zachodzi potrzeba:

  • połączenia kilku śladów GPX w jedną trasę
  • posortowania punktów według czasu
  • usunięcia duplikatów
  • usunięcia błędnych punktów o nienaturalnej prędkości
  • wykrycia przerw i stworzenia kilku segmentów

W tym artykule pokazuję krok po kroku, jak to zrobić w Pythonie — od najprostszej wersji, aż po w pełni wyposażony skrypt do czyszczenia i scalania danych GPS.

Instalacja biblioteki GPX

Wszystkie przykłady korzystają z gpxpy:

pip install gpxpy

1. Najprostsze łączenie kilku plików GPX w jeden

Pierwsza wersja scala GPX-y tak, jak są — bez sortowania ani filtrowania. Jest to najprostszy wariant, idealny gdy chcesz po prostu połączyć pliki.

merge_gpx.py — proste łączenie GPX

#!/usr/bin/env python3
import argparse
import gpxpy
import gpxpy.gpx

def merge_gpx_files(input_files, output_file):
    merged_gpx = gpxpy.gpx.GPX()

    for file_path in input_files:
        print(f"Ładowanie: {file_path}")
        with open(file_path, "r", encoding="utf-8") as f:
            gpx = gpxpy.parse(f)

            # Dodaj trasy
            for track in gpx.tracks:
                merged_gpx.tracks.append(track)

            # Dodaj ścieżki (routes)
            for route in gpx.routes:
                merged_gpx.routes.append(route)

            # Dodaj punkty (waypoints)
            for waypoint in gpx.waypoints:
                merged_gpx.waypoints.append(waypoint)

    with open(output_file, "w", encoding="utf-8") as f:
        f.write(merged_gpx.to_xml())

    print(f"Zapisano połączony plik: {output_file}")

def main():
    parser = argparse.ArgumentParser(description="Łączenie wielu plików GPX w jeden.")
    parser.add_argument("input_files", nargs="+", help="Lista plików GPX do połączenia.")
    parser.add_argument("-o", "--output", default="merged.gpx",
                        help="Nazwa pliku wynikowego (domyślnie merged.gpx).")

    args = parser.parse_args()
    merge_gpx_files(args.input_files, args.output)


if __name__ == "__main__":
    main()

2. Łączenie GPX według czasu — tworzenie jednego chronologicznego śladu

Ten wariant:

  • zbiera wszystkie punkty (trackpointy)
  • sortuje je według timestamp
  • zapisuje je jako jedną trasę GPX

Idealny, gdy masz kilka fragmentów trasy z różnych urządzeń lub przerw w nagrywaniu.

merge_gpx_time.py — łączenie według czasu

#!/usr/bin/env python3
import argparse
import gpxpy
import gpxpy.gpx
from datetime import datetime

def merge_gpx_by_time(input_files, output_file):
    all_points = []

    # Wczytaj wszystkie pliki i zbierz trackpointy
    for file_path in input_files:
        print(f"Ładowanie: {file_path}")
        with open(file_path, "r", encoding="utf-8") as f:
            gpx = gpxpy.parse(f)

            for track in gpx.tracks:
                for segment in track.segments:
                    for point in segment.points:
                        if point.time is None:
                            print(f"⚠️ Punkt w {file_path} nie ma czasu – pomijam")
                            continue
                        all_points.append(point)

    if not all_points:
        print("Brak trackpointów z czasem. Nie można utworzyć połączonej trasy.")
        return

    # Sortuj po czasie
    all_points.sort(key=lambda p: p.time)

    # Utwórz nowy plik GPX
    new_gpx = gpxpy.gpx.GPX()
    new_track = gpxpy.gpx.GPXTrack()
    new_segment = gpxpy.gpx.GPXTrackSegment()

    new_gpx.tracks.append(new_track)
    new_track.segments.append(new_segment)

    for p in all_points:
        new_segment.points.append(p)

    # Zapisz wynik
    with open(output_file, "w", encoding="utf-8") as f:
        f.write(new_gpx.to_xml())

    print(f"Zapisano połączony plik: {output_file}")


def main():
    parser = argparse.ArgumentParser(description="Łączenie wielu plików GPX w jeden według czasu.")
    parser.add_argument("input_files", nargs="+", help="Lista plików GPX do połączenia.")
    parser.add_argument("-o", "--output", default="merged_time.gpx",
                        help="Nazwa pliku wynikowego (domyślnie merged_time.gpx).")

    args = parser.parse_args()
    merge_gpx_by_time(args.input_files, args.output)


if __name__ == "__main__":
    main()

3. Łączenie z filtrowaniem duplikatów i nienaturalnej prędkości

Ten wariant czyści trasę poprzez:

usuwanie duplikatów

  • ten sam timestamp
  • zbyt blisko (np. < 3 m od poprzedniego)

usuwanie punktów o nielogicznej prędkości

(np. > 200 km/h)

Idealne do czyszczenia tras nagranych smartfonem lub urządzeniem sportowym.

merge_gpx_clean.py — łączenie + filtrowanie jakości

#!/usr/bin/env python3
import argparse
import gpxpy
import gpxpy.gpx
import math
from datetime import datetime

def haversine(lat1, lon1, lat2, lon2):
    R = 6371000
    phi1 = math.radians(lat1)
    phi2 = math.radians(lat2)
    dphi = math.radians(lat2 - lat1)
    dlambda = math.radians(lon2 - lon1)
    a = (math.sin(dphi/2)**2 +
         math.cos(phi1)*math.cos(phi2)*math.sin(dlambda/2)**2)
    return 2 * R * math.atan2(math.sqrt(a), math.sqrt(1-a))

def speed_kmh(p1, p2):
    dist = haversine(p1.latitude, p1.longitude, p2.latitude, p2.longitude)
    dt = (p2.time - p1.time).total_seconds()
    if dt <= 0:
        return 0
    return (dist / dt) * 3.6

def merge_gpx_clean(input_files, output_file, max_speed_kmh, duplicate_distance):
    all_points = []

    for file_path in input_files:
        print(f"Ładowanie: {file_path}")
        with open(file_path, "r", encoding="utf-8") as f:
            gpx = gpxpy.parse(f)
            for track in gpx.tracks:
                for segment in track.segments:
                    for point in segment.points:
                        if point.time:
                            all_points.append(point)

    if not all_points:
        print("Brak punktów.")
        return

    all_points.sort(key=lambda p: p.time)

    cleaned = []
    last = None

    for p in all_points:
        if last:
            if p.time == last.time:
                continue
            dist = haversine(last.latitude, last.longitude, p.latitude, p.longitude)
            if dist < duplicate_distance:
                continue
            if speed_kmh(last, p) > max_speed_kmh:
                continue

        cleaned.append(p)
        last = p

    new_gpx = gpxpy.gpx.GPX()
    new_track = gpxpy.gpx.GPXTrack()
    new_segment = gpxpy.gpx.GPXTrackSegment()

    new_gpx.tracks.append(new_track)
    new_track.segments.append(new_segment)

    for p in cleaned:
        new_segment.points.append(p)

    with open(output_file, "w", encoding="utf-8") as f:
        f.write(new_gpx.to_xml())

    print(f"Zapisano: {output_file}")

4. Wykrywanie przerw czasowych i tworzenie segmentów

To najważniejsze dla aktywności sportowych:

jeśli między punktami jest przerwa > X minut → twórz nowy segment.

Segmenty pozwalają np. rozróżnić etapy wycieczki lub wyciszyć przestoje urządzenia.

merge_gpx_clean_segments.py — finalna, kompletna wersja

Poniżej znajduje się pełny, scalony skrypt, który:

  • łączy wiele plików
  • sortuje według czasu
  • usuwa duplikaty
  • filtruje nienaturalne prędkości
  • wykrywa przerwy i tworzy wiele segmentów

To jest najbardziej kompletna wersja.

#!/usr/bin/env python3
import argparse
import gpxpy
import gpxpy.gpx
import math
from datetime import datetime, timedelta

def haversine(lat1, lon1, lat2, lon2):
    R = 6371000
    phi1 = math.radians(lat1)
    phi2 = math.radians(lat2)
    dphi = math.radians(lat2 - lat1)
    dlambda = math.radians(lon2 - lon1)
    a = (math.sin(dphi/2)**2 +
         math.cos(phi1)*math.cos(phi2)*math.sin(dlambda/2)**2)
    return 2 * R * math.atan2(math.sqrt(a), math.sqrt(1-a))

def speed_kmh(p1, p2):
    dist = haversine(p1.latitude, p1.longitude, p2.latitude, p2.longitude)
    dt = (p2.time - p1.time).total_seconds()
    if dt <= 0:
        return 0
    return (dist / dt) * 3.6

def merge_gpx_clean_segments(input_files, output_file, max_speed_kmh,
                             duplicate_distance, gap_minutes):

    all_points = []
    for file_path in input_files:
        print(f"Ładowanie: {file_path}")
        with open(file_path, "r", encoding="utf-8") as f:
            gpx = gpxpy.parse(f)
            for track in gpx.tracks:
                for segment in track.segments:
                    for point in segment.points:
                        if point.time:
                            all_points.append(point)

    if not all_points:
        print("Brak punktów.")
        return

    all_points.sort(key=lambda p: p.time)

    cleaned = []
    last = None

    for p in all_points:
        if last:
            if p.time == last.time:
                continue

            dist = haversine(last.latitude, last.longitude, p.latitude, p.longitude)
            if dist < duplicate_distance:
                continue

            if speed_kmh(last, p) > max_speed_kmh:
                continue

        cleaned.append(p)
        last = p

    new_gpx = gpxpy.gpx.GPX()
    new_track = gpxpy.gpx.GPXTrack()
    new_gpx.tracks.append(new_track)

    current_segment = gpxpy.gpx.GPXTrackSegment()
    new_track.segments.append(current_segment)

    gap = timedelta(minutes=gap_minutes)
    last = None

    for p in cleaned:
        if last and p.time - last.time > gap:
            print(f"⏸️ Przerwa {p.time - last.time}, nowy segment")
            current_segment = gpxpy.gpx.GPXTrackSegment()
            new_track.segments.append(current_segment)

        current_segment.points.append(p)
        last = p

    with open(output_file, "w", encoding="utf-8") as f:
        f.write(new_gpx.to_xml())

    print(f"Zapisano wynik: {output_file}")
    print(f"Liczba segmentów: {len(new_track.segments)}")

def main():
    parser = argparse.ArgumentParser(
        description="Zaawansowane łączenie GPX z filtrami i wykrywaniem przerw."
    )
    parser.add_argument("input_files", nargs="+", help="Pliki GPX.")
    parser.add_argument("-o", "--output", default="merged_clean_segments.gpx",
                        help="Plik wynikowy.")
    parser.add_argument("--max-speed", type=float, default=200,
                        help="Maksymalna prędkość km/h.")
    parser.add_argument("--dup-dist", type=float, default=3,
                        help="Odległość uznawana za duplikat (m).")
    parser.add_argument("--gap-minutes", type=float, default=5,
                        help="Przerwa czasowa dla nowego segmentu (min).")

    args = parser.parse_args()

    merge_gpx_clean_segments(
        args.input_files,
        args.output,
        args.max_speed,
        args.dup_dist,
        args.gap_minutes
    )

if __name__ == "__main__":
    main()

5. Podsumowanie

W artykule przedstawiłem cztery poziomy scalania GPX:

Zadanie Skrypt
Proste łączenie merge_gpx.py
Łączenie według czasu merge_gpx_time.py
Łączenie + filtrowanie merge_gpx_clean.py
Łączenie + filtrowanie + segmenty merge_gpx_clean_segments.py

Ostatni z nich to kompletne narzędzie, które:

15 listopada 2025 4

Kategorie

programowanie

Tagi

gpx python

Dziękujemy!
()

Powiązane wpisy


Informacja o cookies

Moja strona internetowa wykorzystuje wyłącznie niezbędne pliki cookies, które są wymagane do jej prawidłowego działania. Nie używam ciasteczek w celach marketingowych ani analitycznych. Korzystając z mojej strony, wyrażasz zgodę na stosowanie tych plików. Możesz dowiedzieć się więcej w mojej polityce prywatności.