mirror of
https://github.com/Freika/dawarich.git
synced 2026-01-10 17:21:38 -05:00
Add Google Maps Phone Takeout parser
This commit is contained in:
parent
5adc96bdb9
commit
96a5240729
8 changed files with 193 additions and 3 deletions
|
|
@ -1 +1 @@
|
|||
0.5.1
|
||||
0.5.2
|
||||
|
|
|
|||
|
|
@ -5,6 +5,14 @@ All notable changes to this project will be documented in this file.
|
|||
The format is based on [Keep a Changelog](http://keepachangelog.com/)
|
||||
and this project adheres to [Semantic Versioning](http://semver.org/).
|
||||
|
||||
## [0.5.2] — 2024-06-08
|
||||
|
||||
### Added
|
||||
|
||||
- Test version of google takeout importing service for exports from users' phones
|
||||
|
||||
---
|
||||
|
||||
## [0.5.1] — 2024-06-07
|
||||
|
||||
### Added
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ class ImportJob < ApplicationJob
|
|||
case source
|
||||
when 'google_semantic_history' then GoogleMaps::SemanticHistoryParser
|
||||
when 'google_records' then GoogleMaps::RecordsParser
|
||||
when 'google_phone_takeout' then GoogleMaps::PhoneTakeoutParser
|
||||
when 'owntracks' then OwnTracks::ExportParser
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -8,5 +8,5 @@ class Import < ApplicationRecord
|
|||
|
||||
include ImportUploader::Attachment(:raw)
|
||||
|
||||
enum source: { google_semantic_history: 0, owntracks: 1, google_records: 2 }
|
||||
enum source: { google_semantic_history: 0, owntracks: 1, google_records: 2, google_phone_takeout: 3 }
|
||||
end
|
||||
|
|
|
|||
71
app/services/google_maps/phone_takeout_parser.rb
Normal file
71
app/services/google_maps/phone_takeout_parser.rb
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class GoogleMaps::PhoneTakeoutParser
|
||||
attr_reader :import, :user_id
|
||||
|
||||
def initialize(import, user_id)
|
||||
@import = import
|
||||
@user_id = user_id
|
||||
end
|
||||
|
||||
def call
|
||||
points_data = parse_json
|
||||
|
||||
points = 0
|
||||
|
||||
points_data.each do |point_data|
|
||||
next if Point.exists?(timestamp: point_data[:timestamp])
|
||||
|
||||
Point.create(
|
||||
latitude: point_data[:latitude],
|
||||
longitude: point_data[:longitude],
|
||||
timestamp: point_data[:timestamp],
|
||||
raw_data: point_data[:raw_data],
|
||||
topic: 'Google Maps Phone Timeline Export',
|
||||
tracker_id: 'google-maps-phone-timeline-export',
|
||||
import_id: import.id,
|
||||
user_id:
|
||||
)
|
||||
|
||||
points += 1
|
||||
end
|
||||
|
||||
doubles = points_data.size - points
|
||||
processed = points + doubles
|
||||
|
||||
{ raw_points: points_data.size, points:, doubles:, processed: }
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def parse_json
|
||||
import.raw_data['semanticSegments'].flat_map do |segment|
|
||||
if segment.key?('timelinePath')
|
||||
segment['timelinePath'].map do |point|
|
||||
lat, lon = parse_coordinates(point['point'])
|
||||
timestamp = DateTime.parse(point['time']).to_i
|
||||
|
||||
point_hash(lat, lon, timestamp, segment)
|
||||
end
|
||||
elsif segment.key?('visit')
|
||||
lat, lon = parse_coordinates(segment['visit']['topCandidate']['placeLocation']['latLng'])
|
||||
timestamp = DateTime.parse(segment['startTime']).to_i
|
||||
|
||||
point_hash(lat, lon, timestamp, segment)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def parse_coordinates(coordinates)
|
||||
coordinates.split(', ').map { _1.chomp('°') }
|
||||
end
|
||||
|
||||
def point_hash(lat, lon, timestamp, raw_data)
|
||||
{
|
||||
latitude: lat.to_f,
|
||||
longitude: lon.to_f,
|
||||
timestamp:,
|
||||
raw_data:
|
||||
}
|
||||
end
|
||||
end
|
||||
106
spec/fixtures/files/google/phone-takeout.json
vendored
Normal file
106
spec/fixtures/files/google/phone-takeout.json
vendored
Normal file
|
|
@ -0,0 +1,106 @@
|
|||
{
|
||||
"semanticSegments": [
|
||||
{
|
||||
"startTime": "2019-04-03T08:00:00.000+02:00",
|
||||
"endTime": "2019-04-03T10:00:00.000+02:00",
|
||||
"timelinePath": [
|
||||
{
|
||||
"point": "50.0506312°, 14.3439906°",
|
||||
"time": "2019-04-03T08:14:00.000+02:00"
|
||||
},
|
||||
{
|
||||
"point": "50.0506312°, 14.3439906°",
|
||||
"time": "2019-04-03T08:46:00.000+02:00"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"startTime": "2019-04-03T08:13:57.000+02:00",
|
||||
"endTime": "2019-04-03T20:10:18.000+02:00",
|
||||
"startTimeTimezoneUtcOffsetMinutes": 120,
|
||||
"endTimeTimezoneUtcOffsetMinutes": 120,
|
||||
"visit": {
|
||||
"hierarchyLevel": 0,
|
||||
"probability": 0.8500000238418579,
|
||||
"topCandidate": {
|
||||
"placeId": "some random id",
|
||||
"semanticType": "UNKNOWN",
|
||||
"probability": 0.44970497488975525,
|
||||
"placeLocation": {
|
||||
"latLng": "50.0506312°, 14.3439906°"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"rawSignals": [
|
||||
{
|
||||
"activityRecord": {
|
||||
"probableActivities": [
|
||||
{
|
||||
"type": "STILL",
|
||||
"confidence": 0.9599999785423279
|
||||
},
|
||||
{
|
||||
"type": "IN_VEHICLE",
|
||||
"confidence": 0.009999999776482582
|
||||
},
|
||||
{
|
||||
"type": "ON_FOOT",
|
||||
"confidence": 0.009999999776482582
|
||||
},
|
||||
{
|
||||
"type": "WALKING",
|
||||
"confidence": 0.009999999776482582
|
||||
},
|
||||
{
|
||||
"type": "UNKNOWN",
|
||||
"confidence": 0.009999999776482582
|
||||
},
|
||||
{
|
||||
"type": "IN_ROAD_VEHICLE",
|
||||
"confidence": 0.009999999776482582
|
||||
},
|
||||
{
|
||||
"type": "IN_RAIL_VEHICLE",
|
||||
"confidence": 0.009999999776482582
|
||||
},
|
||||
{
|
||||
"type": "IN_ROAD_VEHICLE",
|
||||
"confidence": 0.009999999776482582
|
||||
}
|
||||
],
|
||||
"timestamp": "2024-04-26T20:54:38.000+02:00"
|
||||
}
|
||||
},
|
||||
{
|
||||
"activityRecord": {
|
||||
"probableActivities": [
|
||||
{
|
||||
"type": "STILL",
|
||||
"confidence": 0.9900000095367432
|
||||
},
|
||||
{
|
||||
"type": "UNKNOWN",
|
||||
"confidence": 0.009999999776482582
|
||||
}
|
||||
],
|
||||
"timestamp": "2024-04-26T20:55:45.000+02:00"
|
||||
}
|
||||
}
|
||||
],
|
||||
"userLocationProfile": {
|
||||
"frequentPlaces": [
|
||||
{
|
||||
"placeId": "some random id",
|
||||
"placeLocation": "50.0506312°, 14.3439906°",
|
||||
"label": "WORK"
|
||||
},
|
||||
{
|
||||
"placeId": "some random id",
|
||||
"placeLocation": "50.0506312°, 14.3439906°",
|
||||
"label": "HOME"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
@ -5,4 +5,8 @@ RSpec.describe Import, type: :model do
|
|||
it { is_expected.to have_many(:points).dependent(:destroy) }
|
||||
it { is_expected.to belong_to(:user) }
|
||||
end
|
||||
|
||||
describe 'enums' do
|
||||
it { is_expected.to define_enum_for(:source).with_values(google_semantic_history: 0, owntracks: 1, google_records: 2, google_phone_takeout: 3) }
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -180,7 +180,7 @@ paths:
|
|||
lat: 52.502397
|
||||
lon: 13.356718
|
||||
tid: Swagger
|
||||
tst: 1717786543
|
||||
tst: 1717877268
|
||||
servers:
|
||||
- url: http://{defaultHost}
|
||||
variables:
|
||||
|
|
|
|||
Loading…
Reference in a new issue