mirror of
https://github.com/Freika/dawarich.git
synced 2026-01-11 09:41:40 -05:00
Clean up code a bit
This commit is contained in:
parent
7ea149bd4e
commit
2e46069fcc
2 changed files with 25 additions and 44 deletions
|
|
@ -5,7 +5,6 @@ module Distanceable
|
||||||
|
|
||||||
module ClassMethods
|
module ClassMethods
|
||||||
def total_distance(points = nil, unit = :km)
|
def total_distance(points = nil, unit = :km)
|
||||||
# Handle method being called directly on relation vs with array
|
|
||||||
if points.nil?
|
if points.nil?
|
||||||
calculate_distance_for_relation(unit)
|
calculate_distance_for_relation(unit)
|
||||||
else
|
else
|
||||||
|
|
@ -50,20 +49,17 @@ module Distanceable
|
||||||
|
|
||||||
return 0 if points.length < 2
|
return 0 if points.length < 2
|
||||||
|
|
||||||
# OPTIMIZED: Single SQL query instead of N individual queries
|
|
||||||
total_meters = calculate_batch_distances(points).sum
|
total_meters = calculate_batch_distances(points).sum
|
||||||
|
|
||||||
total_meters.to_f / ::DISTANCE_UNITS[unit.to_sym]
|
total_meters.to_f / ::DISTANCE_UNITS[unit.to_sym]
|
||||||
end
|
end
|
||||||
|
|
||||||
# Optimized batch distance calculation using single SQL query
|
|
||||||
def calculate_batch_distances(points)
|
def calculate_batch_distances(points)
|
||||||
return [] if points.length < 2
|
return [] if points.length < 2
|
||||||
|
|
||||||
point_pairs = points.each_cons(2).to_a
|
point_pairs = points.each_cons(2).to_a
|
||||||
return [] if point_pairs.empty?
|
return [] if point_pairs.empty?
|
||||||
|
|
||||||
# Create a VALUES clause with all point pairs
|
|
||||||
values_clause = point_pairs.map.with_index do |(p1, p2), index|
|
values_clause = point_pairs.map.with_index do |(p1, p2), index|
|
||||||
"(#{index}, ST_GeomFromEWKT('#{p1.lonlat}')::geography, ST_GeomFromEWKT('#{p2.lonlat}')::geography)"
|
"(#{index}, ST_GeomFromEWKT('#{p1.lonlat}')::geography, ST_GeomFromEWKT('#{p2.lonlat}')::geography)"
|
||||||
end.join(', ')
|
end.join(', ')
|
||||||
|
|
@ -71,13 +67,13 @@ module Distanceable
|
||||||
# Single query to calculate all distances
|
# Single query to calculate all distances
|
||||||
results = connection.execute(<<-SQL.squish)
|
results = connection.execute(<<-SQL.squish)
|
||||||
WITH point_pairs AS (
|
WITH point_pairs AS (
|
||||||
SELECT
|
SELECT
|
||||||
pair_id,
|
pair_id,
|
||||||
point1,
|
point1,
|
||||||
point2
|
point2
|
||||||
FROM (VALUES #{values_clause}) AS t(pair_id, point1, point2)
|
FROM (VALUES #{values_clause}) AS t(pair_id, point1, point2)
|
||||||
)
|
)
|
||||||
SELECT
|
SELECT
|
||||||
pair_id,
|
pair_id,
|
||||||
ST_Distance(point1, point2) as distance_meters
|
ST_Distance(point1, point2) as distance_meters
|
||||||
FROM point_pairs
|
FROM point_pairs
|
||||||
|
|
@ -85,7 +81,7 @@ module Distanceable
|
||||||
SQL
|
SQL
|
||||||
|
|
||||||
# Return array of distances in meters
|
# Return array of distances in meters
|
||||||
results.map { |row| row['distance_meters'].to_f }
|
results.map { |row| row['distance_meters'].to_i }
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -94,7 +90,6 @@ module Distanceable
|
||||||
raise ArgumentError, "Invalid unit. Supported units are: #{::DISTANCE_UNITS.keys.join(', ')}"
|
raise ArgumentError, "Invalid unit. Supported units are: #{::DISTANCE_UNITS.keys.join(', ')}"
|
||||||
end
|
end
|
||||||
|
|
||||||
# Extract coordinates based on what type other_point is
|
|
||||||
other_lonlat = extract_point(other_point)
|
other_lonlat = extract_point(other_point)
|
||||||
return nil if other_lonlat.nil?
|
return nil if other_lonlat.nil?
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -25,16 +25,15 @@ class Track < ApplicationRecord
|
||||||
.first
|
.first
|
||||||
end
|
end
|
||||||
|
|
||||||
# Optimized SQL segmentation using PostgreSQL window functions
|
|
||||||
def self.segment_points_in_sql(user_id, start_timestamp, end_timestamp, time_threshold_minutes, distance_threshold_meters, untracked_only: false)
|
def self.segment_points_in_sql(user_id, start_timestamp, end_timestamp, time_threshold_minutes, distance_threshold_meters, untracked_only: false)
|
||||||
time_threshold_seconds = time_threshold_minutes * 60
|
time_threshold_seconds = time_threshold_minutes * 60
|
||||||
|
|
||||||
where_clause = if untracked_only
|
where_clause = if untracked_only
|
||||||
"WHERE user_id = $1 AND timestamp BETWEEN $2 AND $3 AND track_id IS NULL"
|
"WHERE user_id = $1 AND timestamp BETWEEN $2 AND $3 AND track_id IS NULL"
|
||||||
else
|
else
|
||||||
"WHERE user_id = $1 AND timestamp BETWEEN $2 AND $3"
|
"WHERE user_id = $1 AND timestamp BETWEEN $2 AND $3"
|
||||||
end
|
end
|
||||||
|
|
||||||
sql = <<~SQL
|
sql = <<~SQL
|
||||||
WITH points_with_gaps AS (
|
WITH points_with_gaps AS (
|
||||||
SELECT
|
SELECT
|
||||||
|
|
@ -44,21 +43,21 @@ class Track < ApplicationRecord
|
||||||
LAG(lonlat) OVER (ORDER BY timestamp) as prev_lonlat,
|
LAG(lonlat) OVER (ORDER BY timestamp) as prev_lonlat,
|
||||||
LAG(timestamp) OVER (ORDER BY timestamp) as prev_timestamp,
|
LAG(timestamp) OVER (ORDER BY timestamp) as prev_timestamp,
|
||||||
ST_Distance(
|
ST_Distance(
|
||||||
lonlat::geography,
|
lonlat::geography,
|
||||||
LAG(lonlat) OVER (ORDER BY timestamp)::geography
|
LAG(lonlat) OVER (ORDER BY timestamp)::geography
|
||||||
) as distance_meters,
|
) as distance_meters,
|
||||||
(timestamp - LAG(timestamp) OVER (ORDER BY timestamp)) as time_diff_seconds
|
(timestamp - LAG(timestamp) OVER (ORDER BY timestamp)) as time_diff_seconds
|
||||||
FROM points
|
FROM points
|
||||||
#{where_clause}
|
#{where_clause}
|
||||||
ORDER BY timestamp
|
ORDER BY timestamp
|
||||||
),
|
),
|
||||||
segment_breaks AS (
|
segment_breaks AS (
|
||||||
SELECT *,
|
SELECT *,
|
||||||
CASE
|
CASE
|
||||||
WHEN prev_lonlat IS NULL THEN 1
|
WHEN prev_lonlat IS NULL THEN 1
|
||||||
WHEN time_diff_seconds > $4 THEN 1
|
WHEN time_diff_seconds > $4 THEN 1
|
||||||
WHEN distance_meters > $5 THEN 1
|
WHEN distance_meters > $5 THEN 1
|
||||||
ELSE 0
|
ELSE 0
|
||||||
END as is_break
|
END as is_break
|
||||||
FROM points_with_gaps
|
FROM points_with_gaps
|
||||||
),
|
),
|
||||||
|
|
@ -67,7 +66,7 @@ class Track < ApplicationRecord
|
||||||
SUM(is_break) OVER (ORDER BY timestamp ROWS UNBOUNDED PRECEDING) as segment_id
|
SUM(is_break) OVER (ORDER BY timestamp ROWS UNBOUNDED PRECEDING) as segment_id
|
||||||
FROM segment_breaks
|
FROM segment_breaks
|
||||||
)
|
)
|
||||||
SELECT
|
SELECT
|
||||||
segment_id,
|
segment_id,
|
||||||
array_agg(id ORDER BY timestamp) as point_ids,
|
array_agg(id ORDER BY timestamp) as point_ids,
|
||||||
count(*) as point_count,
|
count(*) as point_count,
|
||||||
|
|
@ -79,7 +78,7 @@ class Track < ApplicationRecord
|
||||||
HAVING count(*) >= 2
|
HAVING count(*) >= 2
|
||||||
ORDER BY segment_id
|
ORDER BY segment_id
|
||||||
SQL
|
SQL
|
||||||
|
|
||||||
results = Point.connection.exec_query(
|
results = Point.connection.exec_query(
|
||||||
sql,
|
sql,
|
||||||
'segment_points_in_sql',
|
'segment_points_in_sql',
|
||||||
|
|
@ -104,15 +103,18 @@ class Track < ApplicationRecord
|
||||||
|
|
||||||
# Get actual Point objects for each segment with pre-calculated distances
|
# Get actual Point objects for each segment with pre-calculated distances
|
||||||
def self.get_segments_with_points(user_id, start_timestamp, end_timestamp, time_threshold_minutes, distance_threshold_meters, untracked_only: false)
|
def self.get_segments_with_points(user_id, start_timestamp, end_timestamp, time_threshold_minutes, distance_threshold_meters, untracked_only: false)
|
||||||
segments_data = segment_points_in_sql(user_id, start_timestamp, end_timestamp, time_threshold_minutes, distance_threshold_meters, untracked_only: untracked_only)
|
segments_data = segment_points_in_sql(
|
||||||
|
user_id,
|
||||||
# Get all point IDs we need
|
start_timestamp,
|
||||||
all_point_ids = segments_data.flat_map { |seg| seg[:point_ids] }
|
end_timestamp,
|
||||||
|
time_threshold_minutes,
|
||||||
# Single query to get all points
|
distance_threshold_meters,
|
||||||
points_by_id = Point.where(id: all_point_ids).index_by(&:id)
|
untracked_only: untracked_only
|
||||||
|
)
|
||||||
# Build segments with actual Point objects
|
|
||||||
|
point_ids = segments_data.flat_map { |seg| seg[:point_ids] }
|
||||||
|
points_by_id = Point.where(id: point_ids).index_by(&:id)
|
||||||
|
|
||||||
segments_data.map do |seg_data|
|
segments_data.map do |seg_data|
|
||||||
{
|
{
|
||||||
points: seg_data[:point_ids].map { |id| points_by_id[id] }.compact,
|
points: seg_data[:point_ids].map { |id| points_by_id[id] }.compact,
|
||||||
|
|
@ -126,7 +128,7 @@ class Track < ApplicationRecord
|
||||||
# Parse PostgreSQL array format like "{1,2,3}" into Ruby array
|
# Parse PostgreSQL array format like "{1,2,3}" into Ruby array
|
||||||
def self.parse_postgres_array(pg_array_string)
|
def self.parse_postgres_array(pg_array_string)
|
||||||
return [] if pg_array_string.nil? || pg_array_string.empty?
|
return [] if pg_array_string.nil? || pg_array_string.empty?
|
||||||
|
|
||||||
# Remove curly braces and split by comma
|
# Remove curly braces and split by comma
|
||||||
pg_array_string.gsub(/[{}]/, '').split(',').map(&:to_i)
|
pg_array_string.gsub(/[{}]/, '').split(',').map(&:to_i)
|
||||||
end
|
end
|
||||||
|
|
@ -151,23 +153,7 @@ class Track < ApplicationRecord
|
||||||
def broadcast_track_update(action)
|
def broadcast_track_update(action)
|
||||||
TracksChannel.broadcast_to(user, {
|
TracksChannel.broadcast_to(user, {
|
||||||
action: action,
|
action: action,
|
||||||
track: serialize_track_data
|
track: TrackSerializer.new(self).call
|
||||||
})
|
})
|
||||||
end
|
end
|
||||||
|
|
||||||
def serialize_track_data
|
|
||||||
{
|
|
||||||
id: id,
|
|
||||||
start_at: start_at.iso8601,
|
|
||||||
end_at: end_at.iso8601,
|
|
||||||
distance: distance.to_i,
|
|
||||||
avg_speed: avg_speed.to_f,
|
|
||||||
duration: duration,
|
|
||||||
elevation_gain: elevation_gain,
|
|
||||||
elevation_loss: elevation_loss,
|
|
||||||
elevation_max: elevation_max,
|
|
||||||
elevation_min: elevation_min,
|
|
||||||
original_path: original_path.to_s
|
|
||||||
}
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue