mirror of
https://github.com/Freika/dawarich.git
synced 2026-01-10 17:21:38 -05:00
111 lines
3.5 KiB
Ruby
111 lines
3.5 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
module LocationSearch
|
|
class ResultAggregator
|
|
include ActionView::Helpers::TextHelper
|
|
|
|
# Time threshold for grouping consecutive points into visits (minutes)
|
|
VISIT_TIME_THRESHOLD = 30
|
|
|
|
def group_points_into_visits(points)
|
|
return [] if points.empty?
|
|
|
|
# Sort points by timestamp to handle unordered input
|
|
sorted_points = points.sort_by { |p| p[:timestamp] }
|
|
|
|
visits = []
|
|
current_visit_points = []
|
|
|
|
sorted_points.each do |point|
|
|
if current_visit_points.empty? || within_visit_threshold?(current_visit_points.last, point)
|
|
current_visit_points << point
|
|
else
|
|
# Finalize current visit and start a new one
|
|
visits << create_visit_from_points(current_visit_points) if current_visit_points.any?
|
|
current_visit_points = [point]
|
|
end
|
|
end
|
|
|
|
# Don't forget the last visit
|
|
visits << create_visit_from_points(current_visit_points) if current_visit_points.any?
|
|
|
|
visits.sort_by { |visit| -visit[:timestamp] } # Most recent first
|
|
end
|
|
|
|
private
|
|
|
|
def within_visit_threshold?(previous_point, current_point)
|
|
time_diff = (current_point[:timestamp] - previous_point[:timestamp]).abs / 60.0 # minutes
|
|
time_diff <= VISIT_TIME_THRESHOLD
|
|
end
|
|
|
|
def create_visit_from_points(points)
|
|
return nil if points.empty?
|
|
|
|
# Sort points by timestamp to get chronological order
|
|
sorted_points = points.sort_by { |p| p[:timestamp] }
|
|
first_point = sorted_points.first
|
|
last_point = sorted_points.last
|
|
|
|
# Calculate visit duration
|
|
duration_minutes =
|
|
if sorted_points.length > 1
|
|
((last_point[:timestamp] - first_point[:timestamp]) / 60.0).round
|
|
else
|
|
# Single point visit - estimate based on typical stay time
|
|
15 # minutes
|
|
end
|
|
|
|
# Find the most accurate point (lowest accuracy value means higher precision)
|
|
most_accurate_point = points.min_by { |p| p[:accuracy] || 999_999 }
|
|
|
|
# Calculate average distance from search center
|
|
average_distance = (points.sum { |p| p[:distance_meters] } / points.length).round(2)
|
|
|
|
{
|
|
timestamp: first_point[:timestamp],
|
|
date: first_point[:date],
|
|
coordinates: most_accurate_point[:coordinates],
|
|
distance_meters: average_distance,
|
|
duration_estimate: format_duration(duration_minutes),
|
|
points_count: points.length,
|
|
accuracy_meters: most_accurate_point[:accuracy],
|
|
visit_details: {
|
|
start_time: first_point[:date],
|
|
end_time: last_point[:date],
|
|
duration_minutes: duration_minutes,
|
|
city: most_accurate_point[:city],
|
|
country: most_accurate_point[:country],
|
|
altitude_range: calculate_altitude_range(points)
|
|
}
|
|
}
|
|
end
|
|
|
|
def format_duration(minutes)
|
|
return "~#{pluralize(minutes, 'minute')}" if minutes < 60
|
|
|
|
hours = minutes / 60
|
|
remaining_minutes = minutes % 60
|
|
|
|
if remaining_minutes.zero?
|
|
"~#{pluralize(hours, 'hour')}"
|
|
else
|
|
"~#{pluralize(hours, 'hour')} #{pluralize(remaining_minutes, 'minute')}"
|
|
end
|
|
end
|
|
|
|
def calculate_altitude_range(points)
|
|
altitudes = points.map { |p| p[:altitude] }.compact
|
|
return nil if altitudes.empty?
|
|
|
|
min_altitude = altitudes.min
|
|
max_altitude = altitudes.max
|
|
|
|
if min_altitude == max_altitude
|
|
"#{min_altitude}m"
|
|
else
|
|
"#{min_altitude}m - #{max_altitude}m"
|
|
end
|
|
end
|
|
end
|
|
end
|