mirror of
https://github.com/Freika/dawarich.git
synced 2026-01-11 09:41:40 -05:00
Calculate distance using PostGIS ST_Distance with LAG
This commit is contained in:
parent
d9eac91834
commit
4fa3c35fb8
6 changed files with 65 additions and 35 deletions
|
|
@ -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(', ')}"
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in a new issue