From 4a704ed6087260279e87cef000a3f4a6df5afa9d Mon Sep 17 00:00:00 2001 From: Eugene Burmakin Date: Sat, 23 Aug 2025 18:37:51 +0200 Subject: [PATCH] Update gpx serializer --- CHANGELOG.md | 1 + app/serializers/points/gpx_serializer.rb | 114 +++++++++++++++--- .../serializers/points/gpx_serializer_spec.rb | 56 +++++++-- 3 files changed, 142 insertions(+), 29 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 64d88779..4e0e5e2d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ## Changed - If user already have import with the same name, it will be appended with timestamp during the import process. +- Export to GPX now adds adds speed and course to each point if they are available. ## Fixed diff --git a/app/serializers/points/gpx_serializer.rb b/app/serializers/points/gpx_serializer.rb index fa088ecd..82eb156f 100644 --- a/app/serializers/points/gpx_serializer.rb +++ b/app/serializers/points/gpx_serializer.rb @@ -1,5 +1,17 @@ # frozen_string_literal: true +# Simple wrapper class that acts like GPX::GPXFile but preserves enhanced XML +class EnhancedGpxFile < GPX::GPXFile + def initialize(name, xml_string) + super(name: name) + @enhanced_xml = xml_string + end + + def to_s + @enhanced_xml + end +end + class Points::GpxSerializer def initialize(points, name) @points = points @@ -7,30 +19,92 @@ class Points::GpxSerializer end def call - gpx_file = GPX::GPXFile.new(name: "dawarich_#{name}") - track = GPX::Track.new(name: "dawarich_#{name}") + gpx_file = create_base_gpx_file + add_track_points_to_gpx(gpx_file) + xml_string = enhance_gpx_with_speed_and_course(gpx_file.to_s) - gpx_file.tracks << track - - track_segment = GPX::Segment.new - track.segments << track_segment - - points.each do |point| - track_segment.points << GPX::TrackPoint.new( - lat: point.lat, - lon: point.lon, - elevation: point.altitude.to_f, - time: point.recorded_at - ) - end - - GPX::GPXFile.new( - name: "dawarich_#{name}", - gpx_data: gpx_file.to_s.sub(']*>.*?<\/trkpt>)/m) do |trkpt_xml| + point = points[trkpt_count] + trkpt_count += 1 + enhance_single_trackpoint(trkpt_xml, point) + end + end + + def enhance_single_trackpoint(trkpt_xml, point) + enhanced_trkpt = add_speed_to_trackpoint(trkpt_xml, point) + add_course_to_trackpoint(enhanced_trkpt, point) + end + + def add_speed_to_trackpoint(trkpt_xml, point) + return trkpt_xml unless should_include_speed?(point) + + trkpt_xml.sub(/([^<]*<\/ele>)/, "\\1\n #{point.velocity.to_f}") + end + + def add_course_to_trackpoint(trkpt_xml, point) + return trkpt_xml unless should_include_course?(point) + + extensions_xml = "\n \n #{point.course.to_f}\n " + trkpt_xml.sub(/\n <\/trkpt>/, "#{extensions_xml}\n ") + end + + def should_include_speed?(point) + point.velocity.present? && point.velocity.to_f > 0 + end + + def should_include_course?(point) + point.course.present? + end end diff --git a/spec/serializers/points/gpx_serializer_spec.rb b/spec/serializers/points/gpx_serializer_spec.rb index 7445862d..b931121a 100644 --- a/spec/serializers/points/gpx_serializer_spec.rb +++ b/spec/serializers/points/gpx_serializer_spec.rb @@ -8,7 +8,7 @@ RSpec.describe Points::GpxSerializer do let(:points) do (1..3).map do |i| - create(:point, timestamp: 1.day.ago + i.minutes) + create(:point, timestamp: 1.day.ago + i.minutes, velocity: i * 10.5, course: i * 45.2) end end @@ -16,17 +16,55 @@ RSpec.describe Points::GpxSerializer do expect(serializer).to be_a(GPX::GPXFile) end - it 'includes waypoints' do - expect(serializer.tracks[0].points.size).to eq(3) + it 'includes waypoints in XML output' do + gpx_xml = serializer.to_s + + # Check that all 3 points are included in XML + expect(gpx_xml.scan(/#{point.altitude.to_f}") + end + end + + it 'includes speed and course data in the GPX XML output' do + gpx_xml = serializer.to_s + + # Check that speed is included in XML for points with velocity + expect(gpx_xml).to include('10.5') + expect(gpx_xml).to include('21.0') + expect(gpx_xml).to include('31.5') + + # Check that course is included in extensions for points with course data + expect(gpx_xml).to include('45.2') + expect(gpx_xml).to include('90.4') + expect(gpx_xml).to include('135.6') end - it 'includes waypoints with correct attributes' do - serializer.tracks[0].points.each_with_index do |track_point, index| - point = points[index] + context 'when points have nil velocity or course' do + let(:points) do + [ + create(:point, timestamp: 1.day.ago, velocity: nil, course: nil), + create(:point, timestamp: 1.day.ago + 1.minute, velocity: 15.5, course: nil), + create(:point, timestamp: 1.day.ago + 2.minutes, velocity: nil, course: 90.0) + ] + end - expect(track_point.lat.to_s).to eq(point.lat.to_s) - expect(track_point.lon.to_s).to eq(point.lon.to_s) - expect(track_point.time).to eq(point.recorded_at) + it 'handles nil values gracefully in XML output' do + gpx_xml = serializer.to_s + + # Should only include speed for the point with velocity + expect(gpx_xml).to include('15.5') + expect(gpx_xml).not_to include('0') # Should not include zero/nil speeds + + # Should only include course for the point with course data + expect(gpx_xml).to include('90.0') + + # Should have 3 track points total + expect(gpx_xml.scan(/