From f33dcdfe21a586ce07e8ffa92b6659ea2a55e0d1 Mon Sep 17 00:00:00 2001 From: Eugene Burmakin Date: Mon, 7 Jul 2025 22:23:37 +0200 Subject: [PATCH] Store track distance in user's preferred unit --- app/controllers/map_controller.rb | 6 ++--- app/javascript/maps/tracks.js | 6 ++--- .../incremental_generator_job.rb} | 2 +- app/models/concerns/calculateable.rb | 18 ++----------- app/services/tracks/track_builder.rb | 27 ++++++++++--------- db/migrate/20250703193656_create_tracks.rb | 2 +- spec/models/point_spec.rb | 20 ++++++++++++++ spec/models/track_spec.rb | 6 ++--- .../tracks/create_from_points_spec.rb | 8 +++--- spec/services/tracks/track_builder_spec.rb | 12 ++++----- 10 files changed, 56 insertions(+), 51 deletions(-) rename app/jobs/{incremental_track_generator_job.rb => tracks/incremental_generator_job.rb} (94%) diff --git a/app/controllers/map_controller.rb b/app/controllers/map_controller.rb index 4bb8994d..99cb98e8 100644 --- a/app/controllers/map_controller.rb +++ b/app/controllers/map_controller.rb @@ -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 diff --git a/app/javascript/maps/tracks.js b/app/javascript/maps/tracks.js index 53355c1c..ffda6a35 100644 --- a/app/javascript/maps/tracks.js +++ b/app/javascript/maps/tracks.js @@ -30,7 +30,7 @@ export function createTrackPopupContent(track, distanceUnit) { 🕐 Start: ${startTime}
🏁 End: ${endTime}
⏱️ Duration: ${durationFormatted}
- 📏 Distance: ${formatDistance(track.distance / 1000, distanceUnit)}
+ 📏 Distance: ${formatDistance(track.distance, distanceUnit)}
⚡ Avg Speed: ${formatSpeed(track.avg_speed, distanceUnit)}
⛰️ Elevation: +${track.elevation_gain || 0}m / -${track.elevation_loss || 0}m
📊 Max Alt: ${track.elevation_max || 0}m
@@ -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; diff --git a/app/jobs/incremental_track_generator_job.rb b/app/jobs/tracks/incremental_generator_job.rb similarity index 94% rename from app/jobs/incremental_track_generator_job.rb rename to app/jobs/tracks/incremental_generator_job.rb index 37dfea6f..837f6a7f 100644 --- a/app/jobs/incremental_track_generator_job.rb +++ b/app/jobs/tracks/incremental_generator_job.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -class IncrementalTrackGeneratorJob < ApplicationJob +class Tracks::IncrementalGeneratorJob < ApplicationJob queue_as :default sidekiq_options retry: 3 diff --git a/app/models/concerns/calculateable.rb b/app/models/concerns/calculateable.rb index c612ef6c..45450e82 100644 --- a/app/models/concerns/calculateable.rb +++ b/app/models/concerns/calculateable.rb @@ -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 diff --git a/app/services/tracks/track_builder.rb b/app/services/tracks/track_builder.rb index 25262456..f62f7603 100644 --- a/app/services/tracks/track_builder.rb +++ b/app/services/tracks/track_builder.rb @@ -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] 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 diff --git a/db/migrate/20250703193656_create_tracks.rb b/db/migrate/20250703193656_create_tracks.rb index b89b42dc..e595b827 100644 --- a/db/migrate/20250703193656_create_tracks.rb +++ b/db/migrate/20250703193656_create_tracks.rb @@ -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 diff --git a/spec/models/point_spec.rb b/spec/models/point_spec.rb index 076dd218..4fa59cfb 100644 --- a/spec/models/point_spec.rb +++ b/spec/models/point_spec.rb @@ -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 diff --git a/spec/models/track_spec.rb b/spec/models/track_spec.rb index 91e821f5..474a51a4 100644 --- a/spec/models/track_spec.rb +++ b/spec/models/track_spec.rb @@ -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 diff --git a/spec/services/tracks/create_from_points_spec.rb b/spec/services/tracks/create_from_points_spec.rb index df9a3352..b3a66afc 100644 --- a/spec/services/tracks/create_from_points_spec.rb +++ b/spec/services/tracks/create_from_points_spec.rb @@ -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 diff --git a/spec/services/tracks/track_builder_spec.rb b/spec/services/tracks/track_builder_spec.rb index b97b5c48..ca1c47b9 100644 --- a/spec/services/tracks/track_builder_spec.rb +++ b/spec/services/tracks/track_builder_spec.rb @@ -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