Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/otGpxTrack/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
from .Base import *
from .main import GpxTrack
77 changes: 77 additions & 0 deletions src/otGpxTrack/main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
"""
Main module for GpxTrack with additional utility methods.
"""

import gpxpy


class GpxTrack:
"""
A class to represent and analyze a GPX track.
"""

def __init__(self, gpx_file_path):
"""
Initialize the GpxTrack with a GPX file.

Args:
gpx_file_path (str): Path to the GPX file.
"""
self.gpx_file_path = gpx_file_path
self.gpx = None
self.points = []
self._load_gpx()
self._extract_points()

def _load_gpx(self):
"""Load and parse the GPX file."""
with open(self.gpx_file_path, "r") as f:
self.gpx = gpxpy.parse(f)

def _extract_points(self):
"""Extract track points from the GPX object."""
for track in self.gpx.tracks:
for segment in track.segments:
self.points.extend(segment.points)

def get_distance(self):
"""
Calculate the total distance of the track.

Returns:
float: Total distance in meters.
"""
total_distance = 0.0
for i in range(1, len(self.points)):
total_distance += self.points[i].distance_3d(self.points[i - 1])
return total_distance

def get_duration(self):
"""
Calculate the total duration of the track.

Returns:
float: Total duration in seconds.
"""
if len(self.points) < 2:
return 0.0
start_time = self.points[0].time
end_time = self.points[-1].time
return (end_time - start_time).total_seconds()

def calculate_avg_speed(self):
"""
Calculate the average speed of the track in km/h using timestamps and coordinates.

Returns:
float: Average speed in kilometers per hour.
"""
distance_meters = self.get_distance()
duration_seconds = self.get_duration()

if duration_seconds == 0:
return 0.0

# Convert meters per second to kilometers per hour
avg_speed_kmh = (distance_meters / duration_seconds) * 3.6
return avg_speed_kmh
43 changes: 43 additions & 0 deletions test/test_gpxtrack.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import openturns as ot
import matplotlib.pyplot as plt
from otGpxTrack.Base import GpxTrack
from otGpxTrack.main import GpxTrack as GpxTrackMain


def get_test_file_path(filename):
Expand Down Expand Up @@ -47,6 +48,48 @@ def test_gpxtrack_average_speed():
assert avg_speed >= 0


def test_gpxtrack_main_calculate_avg_speed():
"""Test the calculate_avg_speed method of GpxTrack in main.py."""
gpx_file = get_test_file_path("activity_19218242997.gpx")
track = GpxTrackMain(gpx_file)
avg_speed_kmh = track.calculate_avg_speed()

# Verify the result is non-negative
assert avg_speed_kmh >= 0

# Verify the result is in km/h (should be a reasonable value for a track)
# Typical running/cycling speeds are between 0 and 100 km/h
assert avg_speed_kmh < 1000


def test_gpxtrack_main_calculate_avg_speed_conversion():
"""Test that calculate_avg_speed correctly converts from m/s to km/h."""
gpx_file = get_test_file_path("activity_19218242997.gpx")
track_base = GpxTrack(gpx_file)
track_main = GpxTrackMain(gpx_file)

# Get speeds in both units
avg_speed_mps = track_base.get_average_speed()
avg_speed_kmh = track_main.calculate_avg_speed()

# Verify the conversion: km/h = m/s * 3.6
expected_kmh = avg_speed_mps * 3.6
assert abs(avg_speed_kmh - expected_kmh) < 0.001


def test_gpxtrack_main_calculate_avg_speed_zero_duration():
"""Test calculate_avg_speed with zero duration (edge case)."""
# For this test, we'll use the existing file but verify the method handles edge cases
gpx_file = get_test_file_path("activity_19218242997.gpx")
track = GpxTrackMain(gpx_file)

# If duration is zero, the method should return 0.0
# We can't easily create a zero-duration track, but we can verify the logic
# by checking that the method doesn't crash and returns a valid value
avg_speed_kmh = track.calculate_avg_speed()
assert isinstance(avg_speed_kmh, float)


def test_gpxtrack_openturns_sample():
"""Test the OpenTURNS sample creation of GpxTrack."""
gpx_file = get_test_file_path("activity_19218242997.gpx")
Expand Down
99 changes: 99 additions & 0 deletions test/test_main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
"""
Test module for GpxTrack class in main.py.
"""
import pytest
import os
import sys

# Add src to path
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', 'src'))

from otGpxTrack.main import GpxTrack


def get_test_file_path(filename):
"""Get the absolute path to a test file."""
return os.path.join(os.path.dirname(__file__), '..', 'firstexample', filename)


def test_gpxtrack_main_initialization():
"""Test the initialization of GpxTrack with a valid GPX file."""
gpx_file = get_test_file_path("activity_19218242997.gpx")
track = GpxTrack(gpx_file)
assert track.gpx is not None
assert len(track.points) > 0


def test_gpxtrack_main_distance():
"""Test the distance calculation of GpxTrack."""
gpx_file = get_test_file_path("activity_19218242997.gpx")
track = GpxTrack(gpx_file)
distance = track.get_distance()
assert distance > 0


def test_gpxtrack_main_duration():
"""Test the duration calculation of GpxTrack."""
gpx_file = get_test_file_path("activity_19218242997.gpx")
track = GpxTrack(gpx_file)
duration = track.get_duration()
assert duration > 0


def test_gpxtrack_main_calculate_avg_speed():
"""Test the calculate_avg_speed method of GpxTrack in main.py."""
gpx_file = get_test_file_path("activity_19218242997.gpx")
track = GpxTrack(gpx_file)
avg_speed_kmh = track.calculate_avg_speed()

# Verify the result is non-negative
assert avg_speed_kmh >= 0

# Verify the result is in km/h (should be a reasonable value for a track)
# Typical running/cycling speeds are between 0 and 100 km/h
assert avg_speed_kmh < 1000


def test_gpxtrack_main_calculate_avg_speed_conversion():
"""Test that calculate_avg_speed correctly converts from m/s to km/h."""
gpx_file = get_test_file_path("activity_19218242997.gpx")
track = GpxTrack(gpx_file)

# Get distance and duration
distance = track.get_distance()
duration = track.get_duration()

# Calculate expected speed in km/h
expected_kmh = (distance / duration) * 3.6
avg_speed_kmh = track.calculate_avg_speed()

# Verify the conversion: km/h = m/s * 3.6
assert abs(avg_speed_kmh - expected_kmh) < 0.001


def test_gpxtrack_main_calculate_avg_speed_zero_duration():
"""Test calculate_avg_speed with zero duration (edge case)."""
# Create a minimal GPX file with a single point
import tempfile
import gpxpy.gpx

# Create a GPX with one point
gpx = gpxpy.gpx.GPX()
track = gpxpy.gpx.GPXTrack()
gpx.tracks.append(track)
segment = gpxpy.gpx.GPXTrackSegment()
track.segments.append(segment)
segment.points.append(gpxpy.gpx.GPXTrackPoint(0.0, 0.0))

# Write to temp file
with tempfile.NamedTemporaryFile(mode='w', suffix='.gpx', delete=False) as f:
f.write(gpx.to_xml())
temp_path = f.name

try:
# Test with single point (zero duration)
track = GpxTrack(temp_path)
avg_speed_kmh = track.calculate_avg_speed()
assert avg_speed_kmh == 0.0
finally:
os.unlink(temp_path)
Loading