mirror of
https://github.com/Freika/dawarich.git
synced 2026-01-10 01:01:39 -05:00
Implement dawarich points parsing
This commit is contained in:
parent
46a30dc6a2
commit
6c0a954e8e
8 changed files with 298 additions and 1 deletions
26
app/jobs/points/create_job.rb
Normal file
26
app/jobs/points/create_job.rb
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class Points::CreateJob < ApplicationJob
|
||||
queue_as :default
|
||||
|
||||
def perform(params, user_id)
|
||||
data = Overland::Params.new(params).call
|
||||
|
||||
data.each do |location|
|
||||
next if point_exists?(location, user_id)
|
||||
|
||||
Point.create!(location.merge(user_id:))
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def point_exists?(params, user_id)
|
||||
Point.exists?(
|
||||
latitude: params[:latitude],
|
||||
longitude: params[:longitude],
|
||||
timestamp: params[:timestamp],
|
||||
user_id:
|
||||
)
|
||||
end
|
||||
end
|
||||
41
app/services/points/params.rb
Normal file
41
app/services/points/params.rb
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class Points::Params
|
||||
attr_reader :data, :points
|
||||
|
||||
def initialize(json)
|
||||
@data = json.with_indifferent_access
|
||||
@points = @data[:locations]
|
||||
end
|
||||
|
||||
def call
|
||||
points.map do |point|
|
||||
next if point[:geometry].nil? || point.dig(:properties, :timestamp).nil?
|
||||
|
||||
{
|
||||
latitude: point[:geometry][:coordinates][1],
|
||||
longitude: point[:geometry][:coordinates][0],
|
||||
battery_status: point[:properties][:battery_state],
|
||||
battery: battery_level(point[:properties][:battery_level]),
|
||||
timestamp: DateTime.parse(point[:properties][:timestamp]),
|
||||
altitude: point[:properties][:altitude],
|
||||
tracker_id: point[:properties][:device_id],
|
||||
velocity: point[:properties][:speed],
|
||||
ssid: point[:properties][:wifi],
|
||||
accuracy: point[:properties][:horizontal_accuracy],
|
||||
vertical_accuracy: point[:properties][:vertical_accuracy],
|
||||
course_accuracy: point[:properties][:course_accuracy],
|
||||
course: point[:properties][:course],
|
||||
raw_data: point
|
||||
}
|
||||
end.compact
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def battery_level(level)
|
||||
value = (level.to_f * 100).to_i
|
||||
|
||||
value.positive? ? value : nil
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class AddCourseAndCourseAccuracyToPoints < ActiveRecord::Migration[8.0]
|
||||
def change
|
||||
add_column :points, :course, :decimal, precision: 8, scale: 5
|
||||
add_column :points, :course_accuracy, :decimal, precision: 8, scale: 5
|
||||
end
|
||||
end
|
||||
11
db/migrate/20250120152540_add_external_track_id_to_points.rb
Normal file
11
db/migrate/20250120152540_add_external_track_id_to_points.rb
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class AddExternalTrackIdToPoints < ActiveRecord::Migration[8.0]
|
||||
disable_ddl_transaction!
|
||||
|
||||
def change
|
||||
add_column :points, :external_track_id, :string
|
||||
|
||||
add_index :points, :external_track_id, algorithm: :concurrently
|
||||
end
|
||||
end
|
||||
6
db/schema.rb
generated
6
db/schema.rb
generated
|
|
@ -10,7 +10,7 @@
|
|||
#
|
||||
# It's strongly recommended that you check this file into your version control system.
|
||||
|
||||
ActiveRecord::Schema[8.0].define(version: 2024_12_11_113119) do
|
||||
ActiveRecord::Schema[8.0].define(version: 2025_01_20_152540) do
|
||||
# These are extensions that must be enabled in order to support this database
|
||||
enable_extension "pg_catalog.plpgsql"
|
||||
|
||||
|
|
@ -156,12 +156,16 @@ ActiveRecord::Schema[8.0].define(version: 2024_12_11_113119) do
|
|||
t.jsonb "geodata", default: {}, null: false
|
||||
t.bigint "visit_id"
|
||||
t.datetime "reverse_geocoded_at"
|
||||
t.decimal "course", precision: 8, scale: 5
|
||||
t.decimal "course_accuracy", precision: 8, scale: 5
|
||||
t.string "external_track_id"
|
||||
t.index ["altitude"], name: "index_points_on_altitude"
|
||||
t.index ["battery"], name: "index_points_on_battery"
|
||||
t.index ["battery_status"], name: "index_points_on_battery_status"
|
||||
t.index ["city"], name: "index_points_on_city"
|
||||
t.index ["connection"], name: "index_points_on_connection"
|
||||
t.index ["country"], name: "index_points_on_country"
|
||||
t.index ["external_track_id"], name: "index_points_on_external_track_id"
|
||||
t.index ["geodata"], name: "index_points_on_geodata", using: :gin
|
||||
t.index ["import_id"], name: "index_points_on_import_id"
|
||||
t.index ["latitude", "longitude"], name: "index_points_on_latitude_and_longitude"
|
||||
|
|
|
|||
136
spec/fixtures/files/points/geojson_example.json
vendored
Normal file
136
spec/fixtures/files/points/geojson_example.json
vendored
Normal file
|
|
@ -0,0 +1,136 @@
|
|||
{
|
||||
"locations" : [
|
||||
{
|
||||
"type" : "Feature",
|
||||
"geometry" : {
|
||||
"type" : "Point",
|
||||
"coordinates" : [
|
||||
-122.40530871,
|
||||
37.744304130000003
|
||||
]
|
||||
},
|
||||
"properties" : {
|
||||
"horizontal_accuracy" : 5,
|
||||
"track_id" : "799F32F5-89BB-45FB-A639-098B1B95B09F",
|
||||
"speed_accuracy" : 0,
|
||||
"vertical_accuracy" : -1,
|
||||
"course_accuracy" : 0,
|
||||
"altitude" : 0,
|
||||
"speed" : 92.087999999999994,
|
||||
"course" : 27.07,
|
||||
"timestamp" : "2025-01-17T21:03:01Z",
|
||||
"device_id" : "8D5D4197-245B-4619-A88B-2049100ADE46"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type" : "Feature",
|
||||
"properties" : {
|
||||
"timestamp" : "2025-01-17T21:03:02Z",
|
||||
"horizontal_accuracy" : 5,
|
||||
"course" : 24.260000000000002,
|
||||
"speed_accuracy" : 0,
|
||||
"device_id" : "8D5D4197-245B-4619-A88B-2049100ADE46",
|
||||
"vertical_accuracy" : -1,
|
||||
"altitude" : 0,
|
||||
"track_id" : "799F32F5-89BB-45FB-A639-098B1B95B09F",
|
||||
"speed" : 92.448000000000008,
|
||||
"course_accuracy" : 0
|
||||
},
|
||||
"geometry" : {
|
||||
"type" : "Point",
|
||||
"coordinates" : [
|
||||
-122.40518926999999,
|
||||
37.744513759999997
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type" : "Feature",
|
||||
"properties" : {
|
||||
"altitude" : 0,
|
||||
"horizontal_accuracy" : 5,
|
||||
"speed" : 123.76800000000001,
|
||||
"course_accuracy" : 0,
|
||||
"speed_accuracy" : 0,
|
||||
"course" : 309.73000000000002,
|
||||
"track_id" : "F63A3CF9-2FF8-4076-8F59-5BB1EDC23888",
|
||||
"device_id" : "8D5D4197-245B-4619-A88B-2049100ADE46",
|
||||
"timestamp" : "2025-01-17T21:18:38Z",
|
||||
"vertical_accuracy" : -1
|
||||
},
|
||||
"geometry" : {
|
||||
"type" : "Point",
|
||||
"coordinates" : [
|
||||
-122.28487643,
|
||||
37.454486080000002
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type" : "Feature",
|
||||
"properties" : {
|
||||
"track_id" : "F63A3CF9-2FF8-4076-8F59-5BB1EDC23888",
|
||||
"device_id" : "8D5D4197-245B-4619-A88B-2049100ADE46",
|
||||
"speed_accuracy" : 0,
|
||||
"course_accuracy" : 0,
|
||||
"speed" : 123.3,
|
||||
"horizontal_accuracy" : 5,
|
||||
"course" : 309.38,
|
||||
"altitude" : 0,
|
||||
"timestamp" : "2025-01-17T21:18:39Z",
|
||||
"vertical_accuracy" : -1
|
||||
},
|
||||
"geometry" : {
|
||||
"coordinates" : [
|
||||
-122.28517332,
|
||||
37.454684899999997
|
||||
],
|
||||
"type" : "Point"
|
||||
}
|
||||
},
|
||||
{
|
||||
"geometry" : {
|
||||
"coordinates" : [
|
||||
-122.28547306,
|
||||
37.454883219999999
|
||||
],
|
||||
"type" : "Point"
|
||||
},
|
||||
"properties" : {
|
||||
"course_accuracy" : 0,
|
||||
"device_id" : "8D5D4197-245B-4619-A88B-2049100ADE46",
|
||||
"vertical_accuracy" : -1,
|
||||
"course" : 309.73000000000002,
|
||||
"speed_accuracy" : 0,
|
||||
"timestamp" : "2025-01-17T21:18:40Z",
|
||||
"horizontal_accuracy" : 5,
|
||||
"speed" : 125.06400000000001,
|
||||
"track_id" : "F63A3CF9-2FF8-4076-8F59-5BB1EDC23888",
|
||||
"altitude" : 0
|
||||
},
|
||||
"type" : "Feature"
|
||||
},
|
||||
{
|
||||
"geometry" : {
|
||||
"type" : "Point",
|
||||
"coordinates" : [
|
||||
-122.28577665,
|
||||
37.455080109999997
|
||||
]
|
||||
},
|
||||
"properties" : {
|
||||
"course_accuracy" : 0,
|
||||
"speed_accuracy" : 0,
|
||||
"speed" : 124.05600000000001,
|
||||
"track_id" : "F63A3CF9-2FF8-4076-8F59-5BB1EDC23888",
|
||||
"course" : 309.73000000000002,
|
||||
"device_id" : "8D5D4197-245B-4619-A88B-2049100ADE46",
|
||||
"altitude" : 0,
|
||||
"horizontal_accuracy" : 5,
|
||||
"vertical_accuracy" : -1,
|
||||
"timestamp" : "2025-01-17T21:18:41Z"
|
||||
},
|
||||
"type" : "Feature"
|
||||
}
|
||||
]
|
||||
}
|
||||
5
spec/jobs/points/create_job_spec.rb
Normal file
5
spec/jobs/points/create_job_spec.rb
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
require 'rails_helper'
|
||||
|
||||
RSpec.describe Points::CreateJob, type: :job do
|
||||
pending "add some examples to (or delete) #{__FILE__}"
|
||||
end
|
||||
66
spec/services/points/params_spec.rb
Normal file
66
spec/services/points/params_spec.rb
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe Points::Params do
|
||||
describe '#call' do
|
||||
let(:file_path) { 'spec/fixtures/files/points/geojson_example.json' }
|
||||
let(:file) { File.open(file_path) }
|
||||
let(:json) { JSON.parse(file.read) }
|
||||
let(:expected_json) do
|
||||
{
|
||||
latitude: 37.74430413,
|
||||
longitude: -122.40530871,
|
||||
battery_status: nil,
|
||||
battery: nil,
|
||||
timestamp: DateTime.parse('2025-01-17T21:03:01Z'),
|
||||
altitude: 0,
|
||||
tracker_id: '8D5D4197-245B-4619-A88B-2049100ADE46',
|
||||
velocity: 92.088,
|
||||
ssid: nil,
|
||||
accuracy: 5,
|
||||
vertical_accuracy: -1,
|
||||
course_accuracy: 0,
|
||||
course: 27.07,
|
||||
raw_data: {
|
||||
type: 'Feature',
|
||||
geometry: {
|
||||
type: 'Point',
|
||||
coordinates: [-122.40530871, 37.74430413]
|
||||
},
|
||||
properties: {
|
||||
horizontal_accuracy: 5,
|
||||
track_id: '799F32F5-89BB-45FB-A639-098B1B95B09F',
|
||||
speed_accuracy: 0,
|
||||
vertical_accuracy: -1,
|
||||
course_accuracy: 0,
|
||||
altitude: 0,
|
||||
speed: 92.088,
|
||||
course: 27.07,
|
||||
timestamp: '2025-01-17T21:03:01Z',
|
||||
device_id: '8D5D4197-245B-4619-A88B-2049100ADE46'
|
||||
}
|
||||
}.with_indifferent_access
|
||||
}
|
||||
end
|
||||
|
||||
subject(:params) { described_class.new(json).call }
|
||||
|
||||
it 'returns an array of points' do
|
||||
expect(params).to be_an(Array)
|
||||
expect(params.first).to eq(expected_json)
|
||||
end
|
||||
|
||||
it 'returns the correct number of points' do
|
||||
expect(params.size).to eq(6)
|
||||
end
|
||||
|
||||
it 'returns correct keys' do
|
||||
expect(params.first.keys).to eq(expected_json.keys)
|
||||
end
|
||||
|
||||
it 'returns the correct values' do
|
||||
expect(params.first).to eq(expected_json)
|
||||
end
|
||||
end
|
||||
end
|
||||
Loading…
Reference in a new issue