mirror of
https://github.com/Freika/dawarich.git
synced 2026-01-10 17:21:38 -05:00
130 lines
4.1 KiB
Ruby
130 lines
4.1 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
module Tracks::TrackBuilder
|
|
extend ActiveSupport::Concern
|
|
|
|
# Create a track from an array of points
|
|
# @param points [Array<Point>] array of Point objects
|
|
# @return [Track, nil] created track or nil if creation failed
|
|
def create_track_from_points(points)
|
|
return nil if points.size < 2
|
|
|
|
track = Track.new(
|
|
user_id: user.id,
|
|
start_at: Time.zone.at(points.first.timestamp),
|
|
end_at: Time.zone.at(points.last.timestamp),
|
|
original_path: build_path(points)
|
|
)
|
|
|
|
# Calculate track statistics
|
|
track.distance = calculate_track_distance(points)
|
|
track.duration = calculate_duration(points)
|
|
track.avg_speed = calculate_average_speed(track.distance, track.duration)
|
|
|
|
# Calculate elevation statistics
|
|
elevation_stats = calculate_elevation_stats(points)
|
|
track.elevation_gain = elevation_stats[:gain]
|
|
track.elevation_loss = elevation_stats[:loss]
|
|
track.elevation_max = elevation_stats[:max]
|
|
track.elevation_min = elevation_stats[:min]
|
|
|
|
if track.save!
|
|
Point.where(id: points.map(&:id)).update_all(track_id: track.id)
|
|
track
|
|
else
|
|
Rails.logger.error "Failed to create track for user #{user.id}: #{track.errors.full_messages.join(', ')}"
|
|
nil
|
|
end
|
|
end
|
|
|
|
# Build path from points using existing BuildPath service
|
|
# @param points [Array<Point>] array of Point objects
|
|
# @return [String] LineString representation of the path
|
|
def build_path(points)
|
|
Tracks::BuildPath.new(points.map(&:lonlat)).call
|
|
end
|
|
|
|
# Calculate track distance in user's preferred unit for storage
|
|
# @param points [Array<Point>] array of Point objects
|
|
# @return [Float] distance in user's preferred unit with 2 decimal places precision
|
|
def calculate_track_distance(points)
|
|
distance_in_user_unit = Point.total_distance(points, user.safe_settings.distance_unit || 'km')
|
|
distance_in_user_unit.round(2)
|
|
end
|
|
|
|
# Calculate track duration in seconds
|
|
# @param points [Array<Point>] array of Point objects
|
|
# @return [Integer] duration in seconds
|
|
def calculate_duration(points)
|
|
points.last.timestamp - points.first.timestamp
|
|
end
|
|
|
|
# Calculate average speed in km/h
|
|
# @param distance_in_user_unit [Numeric] distance in user's preferred unit
|
|
# @param duration_seconds [Numeric] duration in seconds
|
|
# @return [Float] average speed in km/h
|
|
def calculate_average_speed(distance_in_user_unit, duration_seconds)
|
|
return 0.0 if duration_seconds <= 0 || distance_in_user_unit <= 0
|
|
|
|
# Convert distance to meters for speed calculation
|
|
distance_meters = case user.safe_settings.distance_unit
|
|
when 'miles', 'mi'
|
|
distance_in_user_unit * 1609.344 # miles to meters
|
|
else
|
|
distance_in_user_unit * 1000 # km to meters
|
|
end
|
|
|
|
# Speed in meters per second, then convert to km/h for storage
|
|
speed_mps = distance_meters.to_f / duration_seconds
|
|
(speed_mps * 3.6).round(2) # m/s to km/h
|
|
end
|
|
|
|
# Calculate elevation statistics from points
|
|
# @param points [Array<Point>] array of Point objects
|
|
# @return [Hash] elevation statistics hash
|
|
def calculate_elevation_stats(points)
|
|
altitudes = points.map(&:altitude).compact
|
|
|
|
return default_elevation_stats if altitudes.empty?
|
|
|
|
elevation_gain = 0
|
|
elevation_loss = 0
|
|
previous_altitude = altitudes.first
|
|
|
|
altitudes[1..].each do |altitude|
|
|
diff = altitude - previous_altitude
|
|
if diff > 0
|
|
elevation_gain += diff
|
|
else
|
|
elevation_loss += diff.abs
|
|
end
|
|
previous_altitude = altitude
|
|
end
|
|
|
|
{
|
|
gain: elevation_gain.round,
|
|
loss: elevation_loss.round,
|
|
max: altitudes.max,
|
|
min: altitudes.min
|
|
}
|
|
end
|
|
|
|
# Default elevation statistics when no altitude data is available
|
|
# @return [Hash] default elevation statistics
|
|
def default_elevation_stats
|
|
{
|
|
gain: 0,
|
|
loss: 0,
|
|
max: 0,
|
|
min: 0
|
|
}
|
|
end
|
|
|
|
private
|
|
|
|
# This method must be implemented by the including class
|
|
# @return [User] the user for which tracks are being created
|
|
def user
|
|
raise NotImplementedError, "Including class must implement user method"
|
|
end
|
|
end
|