mirror of
https://github.com/Freika/dawarich.git
synced 2026-01-10 01:01:39 -05:00
Implement GPX export
This commit is contained in:
parent
80b2f8831d
commit
942c84fb07
10 changed files with 70 additions and 35 deletions
16
CHANGELOG.md
16
CHANGELOG.md
|
|
@ -5,6 +5,22 @@ 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/).
|
||||
|
||||
## [UNRELEASED] — 2024-08-28
|
||||
|
||||
### Added
|
||||
|
||||
- GeoJSON format is now available for exporting data.
|
||||
- GPX format is now available for exporting data.
|
||||
|
||||
### Changed
|
||||
|
||||
- Default exporting format is now GeoJSON instead of Owntracks-like JSON. This will allow you to use the exported data in other applications that support GeoJSON format.
|
||||
|
||||
### TODO
|
||||
|
||||
- [ ] Importing GeoJSON
|
||||
|
||||
|
||||
## [0.12.2] — 2024-08-28
|
||||
|
||||
### Added
|
||||
|
|
|
|||
1
Gemfile
1
Gemfile
|
|
@ -9,6 +9,7 @@ gem 'chartkick'
|
|||
gem 'data_migrate'
|
||||
gem 'devise'
|
||||
gem 'geocoder'
|
||||
gem 'gpx'
|
||||
gem 'httparty'
|
||||
gem 'importmap-rails'
|
||||
gem 'kaminari'
|
||||
|
|
|
|||
|
|
@ -137,6 +137,9 @@ GEM
|
|||
csv (>= 3.0.0)
|
||||
globalid (1.2.1)
|
||||
activesupport (>= 6.1)
|
||||
gpx (1.1.1)
|
||||
nokogiri (~> 1.7)
|
||||
rake
|
||||
hashdiff (1.1.0)
|
||||
httparty (0.22.0)
|
||||
csv
|
||||
|
|
@ -433,6 +436,7 @@ DEPENDENCIES
|
|||
ffaker
|
||||
foreman
|
||||
geocoder
|
||||
gpx
|
||||
httparty
|
||||
importmap-rails
|
||||
kaminari
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
class ExportJob < ApplicationJob
|
||||
queue_as :exports
|
||||
|
||||
def perform(export_id, start_at, end_at, format: :geojson)
|
||||
def perform(export_id, start_at, end_at, format: :json)
|
||||
export = Export.find(export_id)
|
||||
|
||||
Exports::Create.new(export:, start_at:, end_at:, format:).call
|
||||
|
|
|
|||
17
app/serializers/points/gpx_serializer.rb
Normal file
17
app/serializers/points/gpx_serializer.rb
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class Points::GpxSerializer
|
||||
def initialize(points)
|
||||
@points = points
|
||||
end
|
||||
|
||||
def call
|
||||
geojson_data = Points::GeojsonSerializer.new(points).call
|
||||
|
||||
GPX::GeoJSON.convert_to_gpx(geojson_data:)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
attr_reader :points
|
||||
end
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class Exports::Create
|
||||
def initialize(export:, start_at:, end_at:, format: :geojson)
|
||||
def initialize(export:, start_at:, end_at:, format: :json)
|
||||
@export = export
|
||||
@user = export.user
|
||||
@start_at = start_at.to_datetime
|
||||
|
|
@ -12,29 +12,15 @@ class Exports::Create
|
|||
def call
|
||||
export.update!(status: :processing)
|
||||
|
||||
Rails.logger.debug "====Exporting data for #{user.email} from #{start_at} to #{end_at}"
|
||||
|
||||
points = time_framed_points
|
||||
data = points_data(points)
|
||||
|
||||
Rails.logger.debug "====Exporting #{points.size} points"
|
||||
|
||||
data =
|
||||
case format
|
||||
when :geojson then process_geojson_export(points)
|
||||
when :gpx then process_gpx_export(points)
|
||||
else raise ArgumentError, "Unsupported format: #{format}"
|
||||
end
|
||||
|
||||
file_path = Rails.root.join('public', 'exports', "#{export.name}.#{format}")
|
||||
|
||||
File.open(file_path, 'w') { |file| file.write(data) }
|
||||
create_export_file(data)
|
||||
|
||||
export.update!(status: :completed, url: "exports/#{export.name}.#{format}")
|
||||
|
||||
create_export_finished_notification
|
||||
rescue StandardError => e
|
||||
Rails.logger.error("====Export failed to create: #{e.message}")
|
||||
|
||||
create_failed_export_notification(e)
|
||||
|
||||
export.update!(status: :failed)
|
||||
|
|
@ -68,10 +54,25 @@ class Exports::Create
|
|||
).call
|
||||
end
|
||||
|
||||
def points_data(points)
|
||||
case format
|
||||
when :json then process_geojson_export(points)
|
||||
when :gpx then process_gpx_export(points)
|
||||
else raise ArgumentError, "Unsupported format: #{format}"
|
||||
end
|
||||
end
|
||||
|
||||
def process_geojson_export(points)
|
||||
Points::GeojsonSerializer.new(points).call
|
||||
end
|
||||
|
||||
def process_gpx_export(points)
|
||||
Points::GpxSerializer.new(points).call
|
||||
end
|
||||
|
||||
def create_export_file(data)
|
||||
file_path = Rails.root.join('public', 'exports', "#{export.name}.#{format}")
|
||||
|
||||
File.open(file_path, 'w') { |file| file.write(data) }
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@
|
|||
</div>
|
||||
<div class="w-full md:w-2/12">
|
||||
<div class="flex flex-col space-y-2 text-center">
|
||||
<%= link_to 'Export GeoJSON', exports_path(start_at: @start_at, end_at: @end_at, file_format: :geojson), data: { confirm: "Are you sure?", turbo_confirm: "Are you sure? This will start background process of exporting points withing timeframe, selected between #{@start_at} and #{@end_at}", turbo_method: :post }, class: "px-4 py-2 bg-green-500 text-white rounded-md join-item" %>
|
||||
<%= link_to 'Export GeoJSON', exports_path(start_at: @start_at, end_at: @end_at, file_format: :json), data: { confirm: "Are you sure?", turbo_confirm: "Are you sure? This will start background process of exporting points withing timeframe, selected between #{@start_at} and #{@end_at}", turbo_method: :post }, class: "px-4 py-2 bg-green-500 text-white rounded-md join-item" %>
|
||||
</div>
|
||||
</div>
|
||||
<%# <div class="w-full md:w-2/12"> %>
|
||||
|
|
|
|||
1
spec/fixtures/files/geojson/export.json
vendored
Normal file
1
spec/fixtures/files/geojson/export.json
vendored
Normal file
File diff suppressed because one or more lines are too long
|
|
@ -8,7 +8,7 @@ RSpec.describe ExportJob, type: :job do
|
|||
let(:end_at) { Time.zone.now }
|
||||
|
||||
it 'calls the Exports::Create service class' do
|
||||
expect(Exports::Create).to receive(:new).with(export:, start_at:, end_at:).and_call_original
|
||||
expect(Exports::Create).to receive(:new).with(export:, start_at:, end_at:, format: :json).and_call_original
|
||||
|
||||
described_class.perform_now(export.id, start_at, end_at)
|
||||
end
|
||||
|
|
|
|||
|
|
@ -4,20 +4,21 @@ require 'rails_helper'
|
|||
|
||||
RSpec.describe Exports::Create do
|
||||
describe '#call' do
|
||||
subject(:create_export) { described_class.new(export:, start_at:, end_at:).call }
|
||||
subject(:create_export) { described_class.new(export:, start_at:, end_at:, format:).call }
|
||||
|
||||
let(:user) { create(:user) }
|
||||
let(:start_at) { DateTime.new(2021, 1, 1).to_s }
|
||||
let(:end_at) { DateTime.new(2021, 1, 2).to_s }
|
||||
let(:export_name) { "#{start_at.to_date}_#{end_at.to_date}" }
|
||||
let(:export) { create(:export, user:, name: export_name, status: :created) }
|
||||
let(:export_content) { ExportSerializer.new(points, user.email).call }
|
||||
let!(:points) { create_list(:point, 10, user:, timestamp: start_at.to_datetime.to_i) }
|
||||
let(:format) { :json }
|
||||
let(:user) { create(:user) }
|
||||
let(:start_at) { DateTime.new(2021, 1, 1).to_s }
|
||||
let(:end_at) { DateTime.new(2021, 1, 2).to_s }
|
||||
let(:export_name) { "#{start_at.to_date}_#{end_at.to_date}" }
|
||||
let(:export) { create(:export, user:, name: export_name, status: :created) }
|
||||
let(:export_content) { Points::GeojsonSerializer.new(points).call }
|
||||
let!(:points) { create_list(:point, 10, user:, timestamp: start_at.to_datetime.to_i) }
|
||||
|
||||
it 'writes the data to a file' do
|
||||
create_export
|
||||
|
||||
file_path = Rails.root.join('public', 'exports', "#{export_name}.json")
|
||||
file_path = Rails.root.join('spec/fixtures/files/geojson/export.json')
|
||||
|
||||
expect(File.read(file_path)).to eq(export_content)
|
||||
end
|
||||
|
|
@ -49,12 +50,6 @@ RSpec.describe Exports::Create do
|
|||
expect(export.reload.failed?).to be_truthy
|
||||
end
|
||||
|
||||
it 'logs the error' do
|
||||
expect(Rails.logger).to receive(:error).with('====Export failed to create: StandardError')
|
||||
|
||||
create_export
|
||||
end
|
||||
|
||||
it 'creates a notification' do
|
||||
expect { create_export }.to change { Notification.count }.by(1)
|
||||
end
|
||||
|
|
|
|||
Loading…
Reference in a new issue