Extract latitude and longitude from raw_data

This commit is contained in:
Eugene Burmakin 2025-04-13 23:25:26 +02:00
parent b0b0a11c30
commit 5fe503f745
8 changed files with 85 additions and 7 deletions

2
.gitignore vendored
View file

@ -68,3 +68,5 @@
/config/credentials/production.key /config/credentials/production.key
/config/credentials/production.yml.enc /config/credentials/production.yml.enc
Makefile

View file

@ -5,13 +5,17 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/)
and this project adheres to [Semantic Versioning](http://semver.org/). and this project adheres to [Semantic Versioning](http://semver.org/).
# 0.25.5 - UNRELEASED # 0.25.5 - 2025-04-13
## Removed ## Removed
- Optional telemetry was removed from the app. - Optional telemetry was removed from the app.
- Sidekiq Web UI is now protected by basic auth in non-self-hosted mode. (Needs to be tested) - Sidekiq Web UI is now protected by basic auth in non-self-hosted mode. (Needs to be tested)
## Changed
- `rake points:migrate_to_lonlat` task now also tries to extract latitude and longitude from `raw_data` column before using `longitude` and `latitude` columns to fill `lonlat` column.
# 0.25.4 - 2025-04-02 # 0.25.4 - 2025-04-02

View file

View file

@ -3,17 +3,20 @@
class Photos::ImportParser class Photos::ImportParser
include Imports::Broadcaster include Imports::Broadcaster
include PointValidation include PointValidation
attr_reader :import, :json, :user_id attr_reader :import, :user_id
def initialize(import, user_id) def initialize(import, user_id)
@import = import @import = import
@json = import.raw_data
@user_id = user_id @user_id = user_id
end end
def call def call
import.file.download do |file|
json = Oj.load(file)
json.each.with_index(1) { |point, index| create_point(point, index) } json.each.with_index(1) { |point, index| create_point(point, index) }
end end
end
def create_point(point, index) def create_point(point, index)
return 0 if point['latitude'].blank? || point['longitude'].blank? || point['timestamp'].blank? return 0 if point['latitude'].blank? || point['longitude'].blank? || point['timestamp'].blank?

View file

@ -0,0 +1,56 @@
# frozen_string_literal: true
class Points::RawDataLonlatExtractor
def initialize(point)
@point = point
end
def call
lonlat = extract_lonlat(@point)
@point.update(
longitude: lonlat[0],
latitude: lonlat[1]
)
end
private
# rubocop:disable Metrics/MethodLength
def extract_lonlat(point)
if point.raw_data['activitySegment']['waypointPath']['waypoints'][0]
# google_semantic_history_parser
[
point.raw_data['activitySegment']['waypointPath']['waypoints'][0]['lngE7'].to_f / 10**7,
point.raw_data['activitySegment']['waypointPath']['waypoints'][0]['latE7'].to_f / 10**7
]
elsif point.raw_data['longitudeE7'] && point.raw_data['latitudeE7']
# google records
[
point.raw_data['longitudeE7'].to_f / 10**7,
point.raw_data['latitudeE7'].to_f / 10**7
]
elsif point.raw_data['position']['LatLng']
# google phone export
raw_coordinates = point.raw_data['position']['LatLng']
if raw_coordinates.include?('°')
raw_coordinates.split(', ').map { _1.chomp('°') }
else
raw_coordinates.delete('geo:').split(',')
end
elsif point.raw_data['lon'] && point.raw_data['lat']
# gpx_track_importer, owntracks
[point.raw_data['lon'], point.raw_data['lat']]
elsif point.raw_data['geometry']['coordinates'][0] && point.raw_data['geometry']['coordinates'][1]
# geojson
[
point.raw_data['geometry']['coordinates'][0],
point.raw_data['geometry']['coordinates'][1]
]
elsif point.raw_data['longitude'] && point.raw_data['latitude']
# immich_api, photoprism_api
[point.raw_data['longitude'], point.raw_data['latitude']]
end
end
# rubocop:enable Metrics/MethodLength
end

View file

@ -98,8 +98,8 @@
</div> </div>
<ul tabindex="0" class="dropdown-content z-[5000] menu p-2 shadow-lg bg-base-100 rounded-box min-w-52" data-notifications-target="list"> <ul tabindex="0" class="dropdown-content z-[5000] menu p-2 shadow-lg bg-base-100 rounded-box min-w-52" data-notifications-target="list">
<li><%= link_to 'See all', notifications_path %></li> <li><%= link_to 'See all', notifications_path %></li>
<div class="divider p-0 m-0"></div>
<% @unread_notifications.first(10).each do |notification| %> <% @unread_notifications.first(10).each do |notification| %>
<div class="divider p-0 m-0"></div>
<li class='notification-item'> <li class='notification-item'>
<%= link_to notification do %> <%= link_to notification do %>
<%= notification.title %> <%= notification.title %>
@ -126,7 +126,7 @@
</li> </li>
<% else %> <% else %>
<li><%= link_to 'Login', new_user_session_path %></li> <li><%= link_to 'Login', new_user_session_path %></li>
<% if !SELF_HOSTED && devise_mapping.registerable? && controller_name != 'registrations' %> <% if !SELF_HOSTED && devise_mapping&.registerable? %>
<li><%= link_to 'Register', new_user_registration_path %></li> <li><%= link_to 'Register', new_user_registration_path %></li>
<% end %> <% end %>
<% end %> <% end %>

View file

@ -5,6 +5,12 @@ namespace :points do
task migrate_to_lonlat: :environment do task migrate_to_lonlat: :environment do
puts 'Updating points to use lonlat...' puts 'Updating points to use lonlat...'
points = Point.where(longitude: nil, latitude: nil).select(:id, :longitude, :latitude, :raw_data)
points.find_each do |point|
Points::RawDataLonlatExtractor.new(point).call
end
ActiveRecord::Base.connection.execute('REINDEX TABLE points;') ActiveRecord::Base.connection.execute('REINDEX TABLE points;')
ActiveRecord::Base.transaction do ActiveRecord::Base.transaction do

View file

@ -13,7 +13,14 @@ RSpec.describe Photos::ImportParser do
let(:immich_data) do let(:immich_data) do
JSON.parse(File.read(Rails.root.join('spec/fixtures/files/immich/geodata.json'))) JSON.parse(File.read(Rails.root.join('spec/fixtures/files/immich/geodata.json')))
end end
let(:import) { create(:import, user:, raw_data: immich_data) } let(:import) { create(:import, user:) }
let(:file_path) { Rails.root.join('spec/fixtures/files/immich/geodata.json') }
let(:file) { Rack::Test::UploadedFile.new(file_path, 'text/plain') }
before do
import.file.attach(io: File.open(file_path), filename: 'immich_geodata.json', content_type: 'application/json')
end
context 'when there are no points' do context 'when there are no points' do
it 'creates new points' do it 'creates new points' do