mirror of
https://github.com/Freika/dawarich.git
synced 2026-01-10 17:21:38 -05:00
143 lines
4 KiB
Ruby
143 lines
4 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
class GoogleMaps::SemanticHistoryParser
|
|
include Imports::Broadcaster
|
|
|
|
BATCH_SIZE = 1000
|
|
attr_reader :import, :user_id
|
|
|
|
def initialize(import, user_id)
|
|
@import = import
|
|
@user_id = user_id
|
|
@current_index = 0
|
|
end
|
|
|
|
def call
|
|
parsed_json.each_slice(BATCH_SIZE) do |batch|
|
|
@current_index += batch.size
|
|
process_batch(batch)
|
|
broadcast_import_progress(import, @current_index)
|
|
end
|
|
end
|
|
|
|
private
|
|
|
|
def process_batch(batch)
|
|
records = batch.map { |point_data| prepare_point_data(point_data) }
|
|
|
|
# rubocop:disable Rails/SkipsModelValidations
|
|
Point.upsert_all(
|
|
records,
|
|
unique_by: %i[lonlat timestamp user_id],
|
|
returning: false,
|
|
on_duplicate: :skip
|
|
)
|
|
# rubocop:enable Rails/SkipsModelValidations
|
|
rescue StandardError => e
|
|
create_notification("Failed to process location batch: #{e.message}")
|
|
end
|
|
|
|
def prepare_point_data(point_data)
|
|
{
|
|
lonlat: point_data[:lonlat],
|
|
timestamp: point_data[:timestamp],
|
|
raw_data: point_data[:raw_data],
|
|
topic: 'Google Maps Timeline Export',
|
|
tracker_id: 'google-maps-timeline-export',
|
|
import_id: import.id,
|
|
user_id: user_id,
|
|
created_at: Time.current,
|
|
updated_at: Time.current
|
|
}
|
|
end
|
|
|
|
def create_notification(message)
|
|
Notification.create!(
|
|
user_id: user_id,
|
|
title: 'Google Maps Timeline Import Error',
|
|
content: message,
|
|
kind: :error
|
|
)
|
|
end
|
|
|
|
def parsed_json
|
|
data = nil
|
|
|
|
import.file.download do |f|
|
|
json = Oj.load(f)
|
|
|
|
data = json['timelineObjects'].flat_map do |timeline_object|
|
|
parse_timeline_object(timeline_object)
|
|
end.compact
|
|
end
|
|
|
|
data
|
|
end
|
|
|
|
def parse_timeline_object(timeline_object)
|
|
if timeline_object['activitySegment'].present?
|
|
parse_activity_segment(timeline_object['activitySegment'])
|
|
elsif timeline_object['placeVisit'].present?
|
|
parse_place_visit(timeline_object['placeVisit'])
|
|
end
|
|
end
|
|
|
|
def parse_activity_segment(activity)
|
|
if activity['startLocation'].blank?
|
|
parse_waypoints(activity)
|
|
else
|
|
build_point_from_location(
|
|
longitude: activity['startLocation']['longitudeE7'],
|
|
latitude: activity['startLocation']['latitudeE7'],
|
|
timestamp: activity['duration']['startTimestamp'] || activity['duration']['startTimestampMs'],
|
|
raw_data: activity
|
|
)
|
|
end
|
|
end
|
|
|
|
def parse_waypoints(activity)
|
|
return if activity['waypointPath'].blank?
|
|
|
|
activity['waypointPath']['waypoints'].map do |waypoint|
|
|
build_point_from_location(
|
|
longitude: waypoint['lngE7'],
|
|
latitude: waypoint['latE7'],
|
|
timestamp: activity['duration']['startTimestamp'] || activity['duration']['startTimestampMs'],
|
|
raw_data: activity
|
|
)
|
|
end
|
|
end
|
|
|
|
def parse_place_visit(place_visit)
|
|
if place_visit.dig('location', 'latitudeE7').present? &&
|
|
place_visit.dig('location', 'longitudeE7').present?
|
|
build_point_from_location(
|
|
longitude: place_visit['location']['longitudeE7'],
|
|
latitude: place_visit['location']['latitudeE7'],
|
|
timestamp: place_visit['duration']['startTimestamp'] || place_visit['duration']['startTimestampMs'],
|
|
raw_data: place_visit
|
|
)
|
|
elsif (candidate = place_visit.dig('otherCandidateLocations', 0))
|
|
parse_candidate_location(candidate, place_visit)
|
|
end
|
|
end
|
|
|
|
def parse_candidate_location(candidate, place_visit)
|
|
return unless candidate['latitudeE7'].present? && candidate['longitudeE7'].present?
|
|
|
|
build_point_from_location(
|
|
longitude: candidate['longitudeE7'],
|
|
latitude: candidate['latitudeE7'],
|
|
timestamp: place_visit['duration']['startTimestamp'] || place_visit['duration']['startTimestampMs'],
|
|
raw_data: place_visit
|
|
)
|
|
end
|
|
|
|
def build_point_from_location(longitude:, latitude:, timestamp:, raw_data:)
|
|
{
|
|
lonlat: "POINT(#{longitude.to_f / 10**7} #{latitude.to_f / 10**7})",
|
|
timestamp: Timestamps.parse_timestamp(timestamp),
|
|
raw_data: raw_data
|
|
}
|
|
end
|
|
end
|