diff --git a/app/models/concerns/distanceable.rb b/app/models/concerns/distanceable.rb index 7e316e4a..9a220aad 100644 --- a/app/models/concerns/distanceable.rb +++ b/app/models/concerns/distanceable.rb @@ -11,6 +11,66 @@ module Distanceable yd: 0.9144 # to meters }.freeze + module ClassMethods + def total_distance(points = nil, unit = :km) + # Handle method being called directly on relation vs with array + if points.nil? + calculate_distance_for_relation(unit) + else + calculate_distance_for_array(points, unit) + end + end + + private + + def calculate_distance_for_relation(unit) + unless DISTANCE_UNITS.key?(unit.to_sym) + raise ArgumentError, "Invalid unit. Supported units are: #{DISTANCE_UNITS.keys.join(', ')}" + end + + distance_in_meters = connection.select_value(<<-SQL.squish) + WITH points_with_previous AS ( + SELECT + lonlat, + LAG(lonlat) OVER (ORDER BY timestamp) as prev_lonlat + FROM (#{to_sql}) AS points + ) + SELECT COALESCE( + SUM( + ST_Distance( + lonlat::geography, + prev_lonlat::geography + ) + ), + 0 + ) + FROM points_with_previous + WHERE prev_lonlat IS NOT NULL + SQL + + distance_in_meters.to_f / DISTANCE_UNITS[unit.to_sym] + end + + def calculate_distance_for_array(points, unit = :km) + unless DISTANCE_UNITS.key?(unit.to_sym) + raise ArgumentError, "Invalid unit. Supported units are: #{DISTANCE_UNITS.keys.join(', ')}" + end + + return 0 if points.length < 2 + + total_meters = points.each_cons(2).sum do |point1, point2| + connection.select_value(<<-SQL.squish) + SELECT ST_Distance( + ST_GeomFromEWKT('#{point1.lonlat}')::geography, + ST_GeomFromEWKT('#{point2.lonlat}')::geography + ) + SQL + end + + total_meters.to_f / DISTANCE_UNITS[unit.to_sym] + end + end + def distance_to(other_point, unit = :km) unless DISTANCE_UNITS.key?(unit.to_sym) raise ArgumentError, "Invalid unit. Supported units are: #{DISTANCE_UNITS.keys.join(', ')}" diff --git a/app/models/stat.rb b/app/models/stat.rb index 6b2d56dd..36bb0be3 100644 --- a/app/models/stat.rb +++ b/app/models/stat.rb @@ -37,7 +37,7 @@ class Stat < ApplicationRecord def calculate_daily_distances(monthly_points) timespan.to_a.map.with_index(1) do |day, index| daily_points = filter_points_for_day(monthly_points, day) - distance = calculate_distance(daily_points) + distance = Point.total_distance(daily_points, DISTANCE_UNIT) [index, distance.round(2)] end end @@ -48,10 +48,4 @@ class Stat < ApplicationRecord points.select { |p| p.timestamp.between?(beginning_of_day, end_of_day) } end - - def calculate_distance(points) - points.each_cons(2).sum do |point1, point2| - DistanceCalculator.new(point1, point2).call - end - end end diff --git a/app/models/trip.rb b/app/models/trip.rb index 5e094078..c216105d 100644 --- a/app/models/trip.rb +++ b/app/models/trip.rb @@ -14,7 +14,6 @@ class Trip < ApplicationRecord calculate_distance end - def points user.tracked_points.where(timestamp: started_at.to_i..ended_at.to_i).order(:timestamp) end @@ -52,13 +51,8 @@ class Trip < ApplicationRecord self.path = trip_path end - def calculate_distance - distance = 0 - - points.each_cons(2) do |point1, point2| - distance += DistanceCalculator.new(point1, point2).call - end + distance = Point.total_distance(points, DISTANCE_UNIT) self.distance = distance.round end diff --git a/app/services/distance_calculator.rb b/app/services/distance_calculator.rb deleted file mode 100644 index d00d070b..00000000 --- a/app/services/distance_calculator.rb +++ /dev/null @@ -1,18 +0,0 @@ -# frozen_string_literal: true - -class DistanceCalculator - def initialize(point1, point2) - @point1 = point1 - @point2 = point2 - end - - def call - Geocoder::Calculations.distance_between( - point1.to_coordinates, point2.to_coordinates, units: ::DISTANCE_UNIT - ) - end - - private - - attr_reader :point1, :point2 -end diff --git a/app/services/stats/calculate_month.rb b/app/services/stats/calculate_month.rb index 324cc3a7..b303d39f 100644 --- a/app/services/stats/calculate_month.rb +++ b/app/services/stats/calculate_month.rb @@ -46,7 +46,7 @@ class Stats::CalculateMonth .tracked_points .without_raw_data .where(timestamp: start_timestamp..end_timestamp) - .select(:latitude, :longitude, :timestamp, :city, :country) + .select(:lonlat, :timestamp, :city, :country) .order(timestamp: :asc) end diff --git a/spec/services/stats/calculate_month_spec.rb b/spec/services/stats/calculate_month_spec.rb index b41c4d2d..fb2b26b1 100644 --- a/spec/services/stats/calculate_month_spec.rb +++ b/spec/services/stats/calculate_month_spec.rb @@ -53,7 +53,7 @@ RSpec.describe Stats::CalculateMonth do it 'calculates distance' do calculate_stats - expect(user.stats.last.distance).to eq(338) + expect(user.stats.last.distance).to eq(339) end context 'when there is an error' do @@ -81,7 +81,7 @@ RSpec.describe Stats::CalculateMonth do it 'calculates distance' do calculate_stats - expect(user.stats.last.distance).to eq(210) + expect(user.stats.last.distance).to eq(211) end context 'when there is an error' do