mirror of
https://github.com/Freika/dawarich.git
synced 2026-01-10 17:21:38 -05:00
145 lines
No EOL
4.2 KiB
Ruby
145 lines
No EOL
4.2 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
# Optimization V1: LAG-based distance calculation with Ruby segmentation
|
|
# This keeps the existing Ruby segmentation logic but uses PostgreSQL LAG
|
|
# for batch distance calculations instead of individual queries
|
|
|
|
module OptimizedTracksV1
|
|
extend ActiveSupport::Concern
|
|
|
|
module ClassMethods
|
|
# V1: Use LAG to get all consecutive distances in a single query
|
|
def calculate_all_consecutive_distances(points)
|
|
return [] if points.length < 2
|
|
|
|
point_ids = points.map(&:id).join(',')
|
|
|
|
results = connection.execute(<<-SQL.squish)
|
|
WITH points_with_previous AS (
|
|
SELECT
|
|
id,
|
|
timestamp,
|
|
lonlat,
|
|
LAG(lonlat) OVER (ORDER BY timestamp) as prev_lonlat,
|
|
LAG(timestamp) OVER (ORDER BY timestamp) as prev_timestamp,
|
|
LAG(id) OVER (ORDER BY timestamp) as prev_id
|
|
FROM points
|
|
WHERE id IN (#{point_ids})
|
|
)
|
|
SELECT
|
|
id,
|
|
prev_id,
|
|
timestamp,
|
|
prev_timestamp,
|
|
ST_Distance(lonlat::geography, prev_lonlat::geography) as distance_meters,
|
|
(timestamp - prev_timestamp) as time_diff_seconds
|
|
FROM points_with_previous
|
|
WHERE prev_lonlat IS NOT NULL
|
|
ORDER BY timestamp
|
|
SQL
|
|
|
|
# Return hash mapping point_id => {distance_to_previous, time_diff}
|
|
distance_map = {}
|
|
results.each do |row|
|
|
distance_map[row['id'].to_i] = {
|
|
distance_meters: row['distance_meters'].to_f,
|
|
time_diff_seconds: row['time_diff_seconds'].to_i,
|
|
prev_id: row['prev_id'].to_i
|
|
}
|
|
end
|
|
|
|
distance_map
|
|
end
|
|
|
|
# V1: Optimized total distance using LAG (already exists in distanceable.rb)
|
|
def total_distance_lag(points, unit = :m)
|
|
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
|
|
|
|
point_ids = points.map(&:id).join(',')
|
|
|
|
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 points
|
|
WHERE id IN (#{point_ids})
|
|
)
|
|
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
|
|
end
|
|
end
|
|
|
|
# Optimized segmentation module using pre-calculated distances
|
|
module OptimizedSegmentationV1
|
|
extend ActiveSupport::Concern
|
|
|
|
private
|
|
|
|
def split_points_into_segments_v1(points)
|
|
return [] if points.empty?
|
|
|
|
# V1: Pre-calculate all distances and time diffs in one query
|
|
if points.size > 1
|
|
distance_data = Point.calculate_all_consecutive_distances(points)
|
|
else
|
|
distance_data = {}
|
|
end
|
|
|
|
segments = []
|
|
current_segment = []
|
|
|
|
points.each do |point|
|
|
if current_segment.empty?
|
|
# First point always starts a segment
|
|
current_segment = [point]
|
|
elsif should_start_new_segment_v1?(point, current_segment.last, distance_data)
|
|
# Finalize current segment if it has enough points
|
|
segments << current_segment if current_segment.size >= 2
|
|
current_segment = [point]
|
|
else
|
|
current_segment << point
|
|
end
|
|
end
|
|
|
|
# Don't forget the last segment
|
|
segments << current_segment if current_segment.size >= 2
|
|
|
|
segments
|
|
end
|
|
|
|
def should_start_new_segment_v1?(current_point, previous_point, distance_data)
|
|
return false if previous_point.nil?
|
|
|
|
# Get pre-calculated data for this point
|
|
point_data = distance_data[current_point.id]
|
|
return false unless point_data
|
|
|
|
# Check time threshold
|
|
time_threshold_seconds = time_threshold_minutes.to_i * 60
|
|
return true if point_data[:time_diff_seconds] > time_threshold_seconds
|
|
|
|
# Check distance threshold
|
|
distance_meters = point_data[:distance_meters]
|
|
return true if distance_meters > distance_threshold_meters
|
|
|
|
false
|
|
end
|
|
end
|
|
|
|
# Add methods to Point class
|
|
class Point
|
|
extend OptimizedTracksV1::ClassMethods
|
|
end |