mirror of
https://github.com/Freika/dawarich.git
synced 2026-01-11 09:41:40 -05:00
Unify timestamps
This commit is contained in:
parent
a66f41d9fb
commit
e64e706b0f
13 changed files with 39 additions and 44 deletions
|
|
@ -1913,6 +1913,7 @@ export default class extends BaseController {
|
|||
headers: {
|
||||
'Accept': 'application/json',
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': `Bearer ${this.apiKey}`
|
||||
},
|
||||
body: JSON.stringify({
|
||||
api_key: this.apiKey
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ class Point < ApplicationRecord
|
|||
after_create :async_reverse_geocode, if: -> { DawarichSettings.store_geodata? && !reverse_geocoded? }
|
||||
after_create :set_country
|
||||
after_create_commit :broadcast_coordinates
|
||||
after_create_commit :trigger_incremental_track_generation, if: -> { import_id.nil? } # Only for real-time points
|
||||
after_create_commit :trigger_incremental_track_generation, if: -> { import_id.nil? }
|
||||
after_commit :recalculate_track, on: :update
|
||||
|
||||
def self.without_raw_data
|
||||
|
|
@ -66,6 +66,20 @@ class Point < ApplicationRecord
|
|||
Country.containing_point(lon, lat)
|
||||
end
|
||||
|
||||
def self.normalize_timestamp(timestamp)
|
||||
case timestamp
|
||||
when Integer
|
||||
timestamp
|
||||
when String, Numeric, DateTime, Time
|
||||
timestamp.to_i
|
||||
when nil
|
||||
raise ArgumentError, 'Timestamp cannot be nil'
|
||||
else
|
||||
raise ArgumentError, "Cannot convert timestamp to integer: #{timestamp.class}"
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
private
|
||||
|
||||
# rubocop:disable Metrics/MethodLength Metrics/AbcSize
|
||||
|
|
|
|||
|
|
@ -42,7 +42,7 @@ class Gpx::TrackImporter
|
|||
{
|
||||
lonlat: "POINT(#{point['lon'].to_d} #{point['lat'].to_d})",
|
||||
altitude: point['ele'].to_i,
|
||||
timestamp: Time.parse(point['time']).to_i,
|
||||
timestamp: Point.normalize_timestamp(point['time']),
|
||||
import_id: import.id,
|
||||
velocity: speed(point),
|
||||
raw_data: point,
|
||||
|
|
|
|||
|
|
@ -56,7 +56,7 @@ class Immich::ImportGeodata
|
|||
latitude: asset['exifInfo']['latitude'],
|
||||
longitude: asset['exifInfo']['longitude'],
|
||||
lonlat: "SRID=4326;POINT(#{asset['exifInfo']['longitude']} #{asset['exifInfo']['latitude']})",
|
||||
timestamp: Time.zone.parse(asset['exifInfo']['dateTimeOriginal']).to_i
|
||||
timestamp: Point.normalize_timestamp(asset['exifInfo']['dateTimeOriginal'])
|
||||
}
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ class Overland::Params
|
|||
lonlat: "POINT(#{point[:geometry][:coordinates][0]} #{point[:geometry][:coordinates][1]})",
|
||||
battery_status: point[:properties][:battery_state],
|
||||
battery: battery_level(point[:properties][:battery_level]),
|
||||
timestamp: DateTime.parse(point[:properties][:timestamp]),
|
||||
timestamp: Point.normalize_timestamp(point[:properties][:timestamp]),
|
||||
altitude: point[:properties][:altitude],
|
||||
velocity: point[:properties][:speed],
|
||||
tracker_id: point[:properties][:device_id],
|
||||
|
|
|
|||
|
|
@ -66,7 +66,7 @@ class Photoprism::ImportGeodata
|
|||
latitude: asset['Lat'],
|
||||
longitude: asset['Lng'],
|
||||
lonlat: "SRID=4326;POINT(#{asset['Lng']} #{asset['Lat']})",
|
||||
timestamp: Time.zone.parse(asset['TakenAt']).to_i
|
||||
timestamp: Point.normalize_timestamp(asset['TakenAt'])
|
||||
}
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ class Points::Params
|
|||
lonlat: lonlat(point),
|
||||
battery_status: point[:properties][:battery_state],
|
||||
battery: battery_level(point[:properties][:battery_level]),
|
||||
timestamp: DateTime.parse(point[:properties][:timestamp]),
|
||||
timestamp: normalize_timestamp(point[:properties][:timestamp]),
|
||||
altitude: point[:properties][:altitude],
|
||||
tracker_id: point[:properties][:device_id],
|
||||
velocity: point[:properties][:speed],
|
||||
|
|
@ -48,4 +48,8 @@ class Points::Params
|
|||
def lonlat(point)
|
||||
"POINT(#{point[:geometry][:coordinates][0]} #{point[:geometry][:coordinates][1]})"
|
||||
end
|
||||
|
||||
def normalize_timestamp(timestamp)
|
||||
Point.normalize_timestamp(DateTime.parse(timestamp))
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -51,7 +51,7 @@ module Tracks
|
|||
|
||||
Point.transaction do
|
||||
# Clean up existing tracks if needed
|
||||
track_cleaner.cleanup_if_needed
|
||||
track_cleaner.cleanup
|
||||
|
||||
# Load points using the configured strategy
|
||||
points = point_loader.load_points
|
||||
|
|
|
|||
|
|
@ -11,8 +11,6 @@ class Tracks::RedisBuffer
|
|||
@day = day.is_a?(Date) ? day : Date.parse(day.to_s)
|
||||
end
|
||||
|
||||
# Store buffered points for an incomplete track segment
|
||||
# @param points [Array<Point>] array of Point objects to buffer
|
||||
def store(points)
|
||||
return if points.empty?
|
||||
|
||||
|
|
@ -23,8 +21,6 @@ class Tracks::RedisBuffer
|
|||
Rails.logger.debug "Stored #{points.size} points in buffer for user #{user_id}, day #{day}"
|
||||
end
|
||||
|
||||
# Retrieve buffered points for the user/day combination
|
||||
# @return [Array<Hash>] array of point hashes or empty array if no buffer exists
|
||||
def retrieve
|
||||
redis_key = buffer_key
|
||||
cached_data = Rails.cache.read(redis_key)
|
||||
|
|
@ -44,8 +40,6 @@ class Tracks::RedisBuffer
|
|||
Rails.logger.debug "Cleared buffer for user #{user_id}, day #{day}"
|
||||
end
|
||||
|
||||
# Check if a buffer exists for the user/day combination
|
||||
# @return [Boolean] true if buffer exists, false otherwise
|
||||
def exists?
|
||||
Rails.cache.exist?(buffer_key)
|
||||
end
|
||||
|
|
|
|||
|
|
@ -42,9 +42,6 @@ module Tracks::Segmentation
|
|||
|
||||
private
|
||||
|
||||
# Split an array of points into track segments based on time and distance thresholds
|
||||
# @param points [Array] array of Point objects or point hashes
|
||||
# @return [Array<Array>] array of point segments
|
||||
def split_points_into_segments(points)
|
||||
return [] if points.empty?
|
||||
|
||||
|
|
@ -67,10 +64,6 @@ module Tracks::Segmentation
|
|||
segments
|
||||
end
|
||||
|
||||
# Check if a new segment should start based on time and distance thresholds
|
||||
# @param current_point [Point, Hash] current point (Point object or hash)
|
||||
# @param previous_point [Point, Hash, nil] previous point or nil
|
||||
# @return [Boolean] true if new segment should start
|
||||
def should_start_new_segment?(current_point, previous_point)
|
||||
return false if previous_point.nil?
|
||||
|
||||
|
|
@ -91,10 +84,6 @@ module Tracks::Segmentation
|
|||
false
|
||||
end
|
||||
|
||||
# Calculate distance between two points in kilometers
|
||||
# @param point1 [Point, Hash] first point
|
||||
# @param point2 [Point, Hash] second point
|
||||
# @return [Float] distance in kilometers
|
||||
def calculate_distance_kilometers_between_points(point1, point2)
|
||||
lat1, lon1 = point_coordinates(point1)
|
||||
lat2, lon2 = point_coordinates(point2)
|
||||
|
|
@ -103,10 +92,6 @@ module Tracks::Segmentation
|
|||
Geocoder::Calculations.distance_between([lat1, lon1], [lat2, lon2], units: :km)
|
||||
end
|
||||
|
||||
# Check if a segment should be finalized (has a large enough gap at the end)
|
||||
# @param segment_points [Array] array of points in the segment
|
||||
# @param grace_period_minutes [Integer] grace period in minutes (default 5)
|
||||
# @return [Boolean] true if segment should be finalized
|
||||
def should_finalize_segment?(segment_points, grace_period_minutes = 5)
|
||||
return false if segment_points.size < 2
|
||||
|
||||
|
|
@ -121,22 +106,19 @@ module Tracks::Segmentation
|
|||
time_since_last_point > grace_period_seconds
|
||||
end
|
||||
|
||||
# Extract timestamp from point (handles both Point objects and hashes)
|
||||
# @param point [Point, Hash] point object or hash
|
||||
# @return [Integer] timestamp as integer
|
||||
def point_timestamp(point)
|
||||
if point.respond_to?(:timestamp)
|
||||
# Point objects from database always have integer timestamps
|
||||
point.timestamp
|
||||
elsif point.is_a?(Hash)
|
||||
point[:timestamp] || point['timestamp']
|
||||
# Hash might come from Redis buffer or test data
|
||||
timestamp = point[:timestamp] || point['timestamp']
|
||||
timestamp.to_i
|
||||
else
|
||||
raise ArgumentError, "Invalid point type: #{point.class}"
|
||||
end
|
||||
end
|
||||
|
||||
# Extract coordinates from point (handles both Point objects and hashes)
|
||||
# @param point [Point, Hash] point object or hash
|
||||
# @return [Array<Float>] [lat, lon] coordinates
|
||||
def point_coordinates(point)
|
||||
if point.respond_to?(:lat) && point.respond_to?(:lon)
|
||||
[point.lat, point.lon]
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ module Tracks
|
|||
@user = user
|
||||
end
|
||||
|
||||
def cleanup_if_needed
|
||||
def cleanup
|
||||
# No cleanup needed for incremental processing
|
||||
# We only append new tracks, don't remove existing ones
|
||||
end
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@
|
|||
#
|
||||
# Example usage:
|
||||
# cleaner = Tracks::TrackCleaners::ReplaceCleaner.new(user, start_at: 1.week.ago, end_at: Time.current)
|
||||
# cleaner.cleanup_if_needed
|
||||
# cleaner.cleanup
|
||||
#
|
||||
module Tracks
|
||||
module TrackCleaners
|
||||
|
|
@ -37,7 +37,7 @@ module Tracks
|
|||
@end_at = end_at
|
||||
end
|
||||
|
||||
def cleanup_if_needed
|
||||
def cleanup
|
||||
tracks_to_remove = find_tracks_to_remove
|
||||
|
||||
if tracks_to_remove.any?
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ RSpec.describe Tracks::Generator do
|
|||
describe '#call' do
|
||||
context 'with no points to process' do
|
||||
before do
|
||||
allow(track_cleaner).to receive(:cleanup_if_needed)
|
||||
allow(track_cleaner).to receive(:cleanup)
|
||||
allow(point_loader).to receive(:load_points).and_return([])
|
||||
end
|
||||
|
||||
|
|
@ -54,7 +54,7 @@ RSpec.describe Tracks::Generator do
|
|||
end
|
||||
|
||||
before do
|
||||
allow(track_cleaner).to receive(:cleanup_if_needed)
|
||||
allow(track_cleaner).to receive(:cleanup)
|
||||
allow(point_loader).to receive(:load_points).and_return(points)
|
||||
allow(incomplete_segment_handler).to receive(:should_finalize_segment?).and_return(true)
|
||||
allow(incomplete_segment_handler).to receive(:cleanup_processed_data)
|
||||
|
|
@ -91,7 +91,7 @@ RSpec.describe Tracks::Generator do
|
|||
end
|
||||
|
||||
before do
|
||||
allow(track_cleaner).to receive(:cleanup_if_needed)
|
||||
allow(track_cleaner).to receive(:cleanup)
|
||||
allow(point_loader).to receive(:load_points).and_return(points)
|
||||
allow(incomplete_segment_handler).to receive(:should_finalize_segment?).and_return(false)
|
||||
allow(incomplete_segment_handler).to receive(:handle_incomplete_segment)
|
||||
|
|
@ -129,7 +129,7 @@ RSpec.describe Tracks::Generator do
|
|||
end
|
||||
|
||||
before do
|
||||
allow(track_cleaner).to receive(:cleanup_if_needed)
|
||||
allow(track_cleaner).to receive(:cleanup)
|
||||
allow(point_loader).to receive(:load_points).and_return(old_points + recent_points)
|
||||
|
||||
# First segment (old points) should be finalized
|
||||
|
|
@ -168,7 +168,7 @@ RSpec.describe Tracks::Generator do
|
|||
end
|
||||
|
||||
before do
|
||||
allow(track_cleaner).to receive(:cleanup_if_needed)
|
||||
allow(track_cleaner).to receive(:cleanup)
|
||||
allow(point_loader).to receive(:load_points).and_return(single_point)
|
||||
allow(incomplete_segment_handler).to receive(:should_finalize_segment?).and_return(true)
|
||||
allow(incomplete_segment_handler).to receive(:cleanup_processed_data)
|
||||
|
|
@ -186,7 +186,7 @@ RSpec.describe Tracks::Generator do
|
|||
|
||||
context 'error handling' do
|
||||
before do
|
||||
allow(track_cleaner).to receive(:cleanup_if_needed)
|
||||
allow(track_cleaner).to receive(:cleanup)
|
||||
allow(point_loader).to receive(:load_points).and_raise(StandardError, 'Point loading failed')
|
||||
end
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue