Store track distance in user's preferred unit

This commit is contained in:
Eugene Burmakin 2025-07-07 22:23:37 +02:00
parent 0d657b9d6e
commit f33dcdfe21
10 changed files with 56 additions and 51 deletions

View file

@ -26,7 +26,6 @@ class MapController < ApplicationController
end
def extract_track_ids
# Extract track IDs from coordinates (index 8: [lat, lng, battery, altitude, timestamp, velocity, id, country, track_id])
@coordinates.map { |coord| coord[8]&.to_i }.compact.uniq.reject(&:zero?)
end
@ -44,15 +43,14 @@ class MapController < ApplicationController
)
end
# Convert distance to meters for consistent storage
distance_in_meters = case current_user.safe_settings.distance_unit.to_s
when 'miles', 'mi'
when 'mi'
distance * 1609.344 # miles to meters
else
distance * 1000 # km to meters
end
distance_in_meters.round # Return as integer meters
distance_in_meters.round
end
def parsed_start_at

View file

@ -30,7 +30,7 @@ export function createTrackPopupContent(track, distanceUnit) {
<strong>🕐 Start:</strong> ${startTime}<br>
<strong>🏁 End:</strong> ${endTime}<br>
<strong> Duration:</strong> ${durationFormatted}<br>
<strong>📏 Distance:</strong> ${formatDistance(track.distance / 1000, distanceUnit)}<br>
<strong>📏 Distance:</strong> ${formatDistance(track.distance, distanceUnit)}<br>
<strong> Avg Speed:</strong> ${formatSpeed(track.avg_speed, distanceUnit)}<br>
<strong> Elevation:</strong> +${track.elevation_gain || 0}m / -${track.elevation_loss || 0}m<br>
<strong>📊 Max Alt:</strong> ${track.elevation_max || 0}m<br>
@ -356,8 +356,8 @@ export function toggleTracksVisibility(tracksLayer, map, isVisible) {
// Helper function to filter tracks by criteria
export function filterTracks(tracks, criteria) {
return tracks.filter(track => {
if (criteria.minDistance && track.distance < criteria.minDistance * 1000) return false;
if (criteria.maxDistance && track.distance > criteria.maxDistance * 1000) return false;
if (criteria.minDistance && track.distance < criteria.minDistance) return false;
if (criteria.maxDistance && track.distance > criteria.maxDistance) return false;
if (criteria.minDuration && track.duration < criteria.minDuration * 60) return false;
if (criteria.maxDuration && track.duration > criteria.maxDuration * 60) return false;
if (criteria.startDate && new Date(track.start_at) < new Date(criteria.startDate)) return false;

View file

@ -1,6 +1,6 @@
# frozen_string_literal: true
class IncrementalTrackGeneratorJob < ApplicationJob
class Tracks::IncrementalGeneratorJob < ApplicationJob
queue_as :default
sidekiq_options retry: 3

View file

@ -53,28 +53,14 @@ module Calculateable
end
def convert_distance_for_storage(calculated_distance)
if track_model?
convert_distance_to_meters(calculated_distance)
else
# For Trip model - store rounded distance in user's preferred unit
calculated_distance.round
end
# Store distance in user's preferred unit with 2 decimal places precision
calculated_distance.round(2)
end
def track_model?
self.class.name == 'Track'
end
def convert_distance_to_meters(calculated_distance)
# For Track model - convert to meters for storage (Track expects distance in meters)
case user_distance_unit.to_s
when 'mi'
(calculated_distance * 1609.344).round # miles to meters
else
(calculated_distance * 1000).round # km to meters
end
end
def save_if_changed!
save! if changed?
end

View file

@ -44,19 +44,12 @@ module Tracks::TrackBuilder
Tracks::BuildPath.new(points.map(&:lonlat)).call
end
# Calculate track distance in meters for storage
# Calculate track distance in user's preferred unit for storage
# @param points [Array<Point>] array of Point objects
# @return [Integer] distance in meters
# @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')
# Convert to meters for storage (Track model expects distance in meters)
case user.safe_settings.distance_unit
when 'miles', 'mi'
(distance_in_user_unit * 1609.344).round # miles to meters
else
(distance_in_user_unit * 1000).round # km to meters
end
distance_in_user_unit.round(2)
end
# Calculate track duration in seconds
@ -67,11 +60,19 @@ module Tracks::TrackBuilder
end
# Calculate average speed in km/h
# @param distance_meters [Numeric] distance in meters
# @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_meters, duration_seconds)
return 0.0 if duration_seconds <= 0 || distance_meters <= 0
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

View file

@ -5,7 +5,7 @@ class CreateTracks < ActiveRecord::Migration[8.0]
t.datetime :end_at, null: false
t.references :user, null: false, foreign_key: true
t.line_string :original_path, null: false
t.integer :distance
t.decimal :distance, precision: 8, scale: 2
t.float :avg_speed
t.integer :duration
t.integer :elevation_gain

View file

@ -120,5 +120,25 @@ RSpec.describe Point, type: :model do
expect(point.lat).to eq(2)
end
end
describe '#recalculate_track' do
let(:point) { create(:point, track: track) }
let(:track) { create(:track) }
it 'recalculates the track' do
expect(track).to receive(:recalculate_path_and_distance!)
point.update(lonlat: 'POINT(-79.85581250721961 15.854775993302411)')
end
end
describe '#trigger_incremental_track_generation' do
let(:point) { create(:point, track: track) }
let(:track) { create(:track) }
it 'enqueues Tracks::IncrementalGeneratorJob' do
expect { point.trigger_incremental_track_generation }.to have_enqueued_job(Tracks::IncrementalGeneratorJob)
end
end
end
end

View file

@ -143,16 +143,16 @@ RSpec.describe Track, type: :model do
track.calculate_distance
expect(track.distance).to be > 0
expect(track.distance).to be_a(Integer)
expect(track.distance).to be_a(Numeric)
end
it 'stores distance in meters for Track model' do
it 'stores distance in user preferred unit for Track model' do
allow(user).to receive(:safe_settings).and_return(double(distance_unit: 'km'))
allow(Point).to receive(:total_distance).and_return(1.5) # 1.5 km
track.calculate_distance
expect(track.distance).to eq(1500) # Should be in meters as integer
expect(track.distance).to eq(1.5) # Should be 1.5 km with 2 decimal places precision
end
end

View file

@ -274,9 +274,9 @@ RSpec.describe Tracks::CreateFromPoints do
allow(Point).to receive(:total_distance).and_return(1.5) # 1.5 km
end
it 'converts km to meters by default' do
it 'stores distance in km by default' do
distance = service.send(:calculate_track_distance, points)
expect(distance).to eq(1500) # 1.5 km = 1500 meters
expect(distance).to eq(1.5) # 1.5 km with 2 decimal places precision
end
context 'with miles unit' do
@ -284,9 +284,9 @@ RSpec.describe Tracks::CreateFromPoints do
user.update!(settings: user.settings.merge({'maps' => {'distance_unit' => 'miles'}}))
end
it 'converts miles to meters' do
it 'stores distance in miles' do
distance = service.send(:calculate_track_distance, points)
expect(distance).to eq(2414) # 1.5 miles ≈ 2414 meters (rounded)
expect(distance).to eq(1.5) # 1.5 miles with 2 decimal places precision
end
end
end

View file

@ -138,9 +138,9 @@ RSpec.describe Tracks::TrackBuilder do
allow(Point).to receive(:total_distance).and_return(1.5) # 1.5 km
end
it 'converts km to meters' do
it 'stores distance in km' do
result = builder.calculate_track_distance(points)
expect(result).to eq(1500) # 1.5 km = 1500 meters
expect(result).to eq(1.5) # 1.5 km with 2 decimal places precision
end
end
@ -150,9 +150,9 @@ RSpec.describe Tracks::TrackBuilder do
allow(Point).to receive(:total_distance).and_return(1.0) # 1 mile
end
it 'converts miles to meters' do
it 'stores distance in miles' do
result = builder.calculate_track_distance(points)
expect(result).to eq(1609) # 1 mile ≈ 1609 meters
expect(result).to eq(1) # 1 mile
end
end
@ -162,9 +162,9 @@ RSpec.describe Tracks::TrackBuilder do
allow(Point).to receive(:total_distance).and_return(2.0)
end
it 'defaults to km and converts to meters' do
it 'defaults to km and stores distance in km' do
result = builder.calculate_track_distance(points)
expect(result).to eq(2000)
expect(result).to eq(2.0) # 2.0 km with 2 decimal places precision
end
end
end