Module: TrackCalculator

Included in:
Calcpace
Defined in:
lib/calcpace/track_calculator.rb

Overview

Module for GPS track calculations

This module provides pure mathematical methods for computing distances, elevation changes, and pace splits from arrays of GPS coordinate points. It does not perform any file I/O or GPX parsing — callers are responsible for supplying arrays of hashes with the required keys.

Examples:

Calculate total distance of a track

calc = Calcpace.new
points = [
  { lat: -23.5505, lon: -46.6333 },
  { lat: -23.5510, lon: -46.6340 },
  { lat: -23.5520, lon: -46.6350 }
]
calc.track_distance(points) #=> 0.17 (km)

Calculate elevation gain and loss

points = [
  { lat: -23.5505, lon: -46.6333, ele: 760.0 },
  { lat: -23.5510, lon: -46.6340, ele: 763.5 },
  { lat: -23.5515, lon: -46.6347, ele: 758.0 }
]
calc.elevation_gain(points) #=> { gain: 3.5, loss: 5.5 }

Constant Summary collapse

EARTH_RADIUS_KM =

Mean radius of the Earth in kilometers (IAU standard)

6371.0

Instance Method Summary collapse

Instance Method Details

#elevation_gain(points) ⇒ Hash

Calculates cumulative elevation gain and loss along a GPS track.

Only consecutive pairs where both points have an :ele value are considered. Points missing :ele are silently skipped.

Examples:

points = [
  { lat: 0, lon: 0, ele: 100.0 },
  { lat: 0, lon: 0, ele: 105.0 },
  { lat: 0, lon: 0, ele: 102.0 }
]
elevation_gain(points) #=> { gain: 5.0, loss: 3.0 }

Parameters:

  • points (Array<Hash>)

    array of points with optional :ele key (meters)

Returns:

  • (Hash)

    hash with :gain and :loss keys, both Floats rounded to 1 decimal



99
100
101
102
103
104
105
106
107
108
109
# File 'lib/calcpace/track_calculator.rb', line 99

def elevation_gain(points)
  gain = 0.0
  loss = 0.0
  return { gain: gain, loss: loss } if points.nil? || points.size < 2

  points.each_cons(2) do |a, b|
    gain, loss = accumulate_elevation(gain, loss, fetch_ele(a), fetch_ele(b))
  end

  { gain: gain.round(1), loss: loss.round(1) }
end

#haversine_distance(lat1, lon1, lat2, lon2) ⇒ Float

Computes the great-circle distance between two GPS coordinates using the Haversine formula.

The Haversine formula calculates the shortest distance over the Earth’s surface between two points defined by latitude and longitude. It assumes a spherical Earth (error < 0.3% vs. WGS84 ellipsoid), which is accurate enough for running and cycling purposes.

Formula:

a = sin²(Δlat/2) + cos(lat1) × cos(lat2) × sin²(Δlon/2)
c = 2 × atan2(√a, √(1−a))
d = R × c

Examples:

Distance between two points in São Paulo

haversine_distance(-23.5505, -46.6333, -23.5510, -46.6340)
#=> 0.089 (km)

Parameters:

  • lat1 (Numeric)

    latitude of first point in decimal degrees

  • lon1 (Numeric)

    longitude of first point in decimal degrees

  • lat2 (Numeric)

    latitude of second point in decimal degrees

  • lon2 (Numeric)

    longitude of second point in decimal degrees

Returns:

  • (Float)

    distance in kilometers

Raises:

  • (ArgumentError)

    if any coordinate is outside valid range (lat ±90, lon ±180)



53
54
55
56
57
# File 'lib/calcpace/track_calculator.rb', line 53

def haversine_distance(lat1, lon1, lat2, lon2)
  validate_coordinates(lat1, lon1)
  validate_coordinates(lat2, lon2)
  haversine_km(lat1, lon1, lat2, lon2)
end

#track_distance(points) ⇒ Float

Calculates the total distance of a GPS track by summing Haversine distances between consecutive points.

Examples:

points = [
  { lat: -23.5505, lon: -46.6333 },
  { lat: -23.5510, lon: -46.6340 },
  { lat: -23.5520, lon: -46.6350 }
]
track_distance(points) #=> 0.17

Parameters:

  • points (Array<Hash>)

    array of points with :lat and :lon keys (String or Symbol)

Returns:

  • (Float)

    total distance in kilometers, rounded to 2 decimal places

Raises:

  • (ArgumentError)

    if any point has coordinates outside valid range



73
74
75
76
77
78
79
80
81
82
# File 'lib/calcpace/track_calculator.rb', line 73

def track_distance(points)
  return 0.0 if points.nil? || points.size < 2

  total = points.each_cons(2).sum do |a, b|
    haversine_distance(fetch_coord(a, :lat), fetch_coord(a, :lon),
                       fetch_coord(b, :lat), fetch_coord(b, :lon))
  end

  total.round(2)
end

#track_splits(points, split_km = 1.0) ⇒ Array<Hash>

Calculates pace splits at regular distance intervals along a GPS track.

Accumulates Haversine distance between consecutive points until the target split distance is reached, then records elapsed time and pace for that split. Any remaining distance at the end is included as a partial split.

Examples:

5 km track with 1 km splits

calc.track_splits(points, 1.0)
#=> [
      { km: 1.0, elapsed: 312, pace: "05:12" },
      { km: 2.0, elapsed: 624, pace: "05:12" },
      ...
    ]

Parameters:

  • points (Array<Hash>)

    array of points with :lat, :lon, and :time keys. :time must respond to #to_f (Unix timestamp) or be a Time object.

  • split_km (Numeric) (defaults to: 1.0)

    split interval in kilometers (default: 1.0)

Returns:

  • (Array<Hash>)

    array of split hashes, each with:

    • :km [Float] cumulative distance at split end

    • :elapsed [Integer] elapsed seconds from start of track to end of split

    • :pace [String] pace for this split in MM:SS format

Raises:

  • (ArgumentError)

    if split_km is not positive

  • (ArgumentError)

    if any point is missing a :time key



134
135
136
137
138
139
140
# File 'lib/calcpace/track_calculator.rb', line 134

def track_splits(points, split_km = 1.0)
  raise ArgumentError, 'split_km must be positive' unless split_km.is_a?(Numeric) && split_km.positive?
  return [] if points.nil? || points.size < 2

  validate_points_have_time(points)
  collect_splits(points, split_km)
end