gpx_doctor 0.5.0 → 0.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 804ebe2457e24c9688e6e8993d0dc3d4adab7b7fddc27478706eaf72a9d09f59
4
- data.tar.gz: e1bb3e8d17ff968a1cd6414c736d9f444559f447d5e6cc8dd3c79175a394b68b
3
+ metadata.gz: 62f11c008dcbf53c4585d1cc1d4935555ddecbfc98ce4db4f0141476f2847ce3
4
+ data.tar.gz: ebf37bed3a22db9e84a6a0d1b593f2e3f9e9bf0146f114a06d8b900cc2e853f8
5
5
  SHA512:
6
- metadata.gz: f8e5bd2ab41937b0faa004c5d1c1ae3c422ba9d332e3aa1ea353fe7f9c22706c0f53e5b3c8161361e8d38ead9bed4fa516d7415d580cc912bdc3976452107dfb
7
- data.tar.gz: 68e78f3a4f84027272a1a96127b998d2a00c447e1f0b75daff611a8d3123c374df0ab6ebf92563e6ab516a4ff9d2bcaa2540dabdad8358a9d35f87fd4cd23860
6
+ metadata.gz: b096d0f32832c3ecd0147bd5b6066dd18f1fd19233af68101d486b5a4895febb2228da30fd907b849436a1feaca98bc707cfd2a0963b05b0587962d11a803f85
7
+ data.tar.gz: 5cf20d498a2247483904a86ca637bff0cc527bafe74f7c312d869694ef05fa798f675940f536980240eab12a90034bed42e841bcc6056bf43fb5505484baddc8
data/README.md CHANGED
@@ -89,6 +89,7 @@ result = GpxDoctor::Parser.parse("path/to/file.gpx", params: {
89
89
  max_points: 500, # reduce each segment to at most this many points
90
90
  segment_statistics: true, # compute distance_to_next, elevation_change, direction for each point
91
91
  cumulative_distance: true, # add cumulative distance from start of each segment/route
92
+ label_interval: 1.0, # insert an interpolated, labelled point at every interval mark (kilometres or miles based on unit_system)
92
93
  enhance_elevation: true, # fetch missing elevations from the configured elevation server
93
94
  full_poi_data: true # populate result.pois with start/finish boundary data for the whole GPX and each track segment
94
95
  })
@@ -100,13 +101,16 @@ Processing is applied in the following order:
100
101
  2. `max_points` — point reduction
101
102
  3. `segment_statistics` — per-point statistics (distance, bearing, elevation change)
102
103
  4. `cumulative_distance` — cumulative distance from the start of each segment/route
103
- 5. `enhance_elevation` — elevation lookup via the elevation server
104
- 6. `full_poi_data` — POI boundary extraction (start, finish, and per-segment boundaries)
104
+ 5. `label_interval` — labelled point insertion
105
+ 6. `enhance_elevation` — elevation lookup via the elevation server
106
+ 7. `full_poi_data` — POI boundary extraction (start, finish, and per-segment boundaries)
105
107
 
106
108
  `enhance_elevation: true` requires the elevation server to be configured (see **Configuration** above). It only fills in points that have no elevation value; existing elevations are left unchanged.
107
109
 
108
110
  `cumulative_distance: true` adds a `cumulative_distance` field to each point, representing the cumulative distance in kilometers from the start of its segment or route. For tracks with multiple segments, each segment's cumulative distance starts at 0.0 (gaps between segments are not included in the calculation).
109
111
 
112
+ `label_interval: 1.0` walks each route/segment and inserts an interpolated point at every multiple of the given interval (measured as cumulative distance from the start, in kilometres for `:metric` or miles for `:imperial` — see **Unit System** above), setting the new point's `label` field to that distance (e.g. `1.0` for the point at the 1.0 km/mi mark). A mark falling on an existing point (within a small tolerance) is skipped rather than duplicated. `label_interval` runs after `cumulative_distance`, reusing its `cumulative_distance` values instead of recalculating them when `cumulative_distance: true` is also given. **Because `label_interval` is applied after `max_points`, the resulting number of points may exceed `max_points`.**
113
+
110
114
  `full_poi_data: true` populates `result.pois` with the first and last geographic point of the entire GPX (across all routes and track segments), plus optional per-segment boundary data. Distances within each segment start at 0.0 and reflect that segment's length only. The `ele` key is omitted for points that have no elevation value. The global `finish` distance is the sum of all individual collection lengths. See **`result.pois`** below for the output shape.
111
115
 
112
116
  ## Accessing data
@@ -294,7 +298,8 @@ GpxDoctor::Builder.build_file(result, 'output.gpx')
294
298
  | `distance_to_next` | Float | Distance to next point (metres or feet based on `unit_system`) — set by `segment_statistics: true` |
295
299
  | `elevation_change` | Float | Elevation change to next point (metres or feet based on `unit_system`) — set by `segment_statistics: true` |
296
300
  | `direction` | Float | Bearing to next point (0–360°) — set by `segment_statistics: true` |
297
- | `cumulative_distance` | Float | Cumulative distance from segment/route start (kilometres or miles based on `unit_system`) — set by `cumulative_distance: true` |
301
+ | `cumulative_distance` | Float | Cumulative distance from segment/route start (kilometres or miles based on `unit_system`) — set by `cumulative_distance: true`; also set (to the same value as `label`) for points inserted by `label_interval` |
302
+ | `label` | Float | Cumulative distance mark for a point inserted by `label_interval` (kilometres or miles based on `unit_system`); `nil` for points not created for labelling — set by `label_interval` |
298
303
 
299
304
  `Waypoint#to_h` returns a hash of all non-nil fields.
300
305
 
@@ -8,7 +8,7 @@ module GpxDoctor
8
8
  sym type fix sat hdop vdop pdop ageofdgpsdata dgpsid
9
9
  ].freeze
10
10
 
11
- STATISTICS_FIELDS = %i[distance_to_next elevation_change direction cumulative_distance].freeze
11
+ STATISTICS_FIELDS = %i[distance_to_next elevation_change direction cumulative_distance label].freeze
12
12
 
13
13
  attr_accessor(*STATISTICS_FIELDS)
14
14
 
@@ -54,6 +54,7 @@ module GpxDoctor
54
54
  select_max_points(result) if @params[:max_points]
55
55
  enhance_statistics(result) if @params[:segment_statistics]
56
56
  enhance_cumulative_distance(result) if @params[:cumulative_distance]
57
+ label_points(result) if @params[:label_interval]
57
58
  enhance_elevations(result) if @params[:enhance_elevation]
58
59
  result.pois = build_pois(result) if @params[:full_poi_data]
59
60
 
@@ -278,6 +279,15 @@ module GpxDoctor
278
279
  end
279
280
  end
280
281
 
282
+ def label_points(result)
283
+ labeler = PointLabeler.new
284
+ interval = @params[:label_interval]
285
+ result.routes.each { |route| route.points = labeler.label(route.points, interval) }
286
+ result.tracks.each do |track|
287
+ track.segments.each { |seg| seg.points = labeler.label(seg.points, interval) }
288
+ end
289
+ end
290
+
281
291
  def point_to_poi(point, distance)
282
292
  poi = { lon: point.lon, lat: point.lat }
283
293
  poi[:ele] = point.ele if point.ele
@@ -0,0 +1,96 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GpxDoctor
4
+ class PointLabeler
5
+ # Tolerance (in the configured distance unit) used to decide whether a
6
+ # label distance coincides with an existing point, to avoid inserting a
7
+ # near-duplicate point right next to it.
8
+ EPSILON = 1e-9
9
+
10
+ # Inserts an interpolated point at every multiple of +label_interval+
11
+ # measured as cumulative distance from the start of +points+ (kilometres
12
+ # for :metric, miles for :imperial — see GpxDoctor.configuration.unit_system).
13
+ #
14
+ # When +points+ already carry a +cumulative_distance+ (set by
15
+ # GpxDoctor::CumulativeDistanceEnhancer), those values are reused instead
16
+ # of being recalculated. Otherwise cumulative distance is computed from
17
+ # scratch.
18
+ #
19
+ # Existing points are left untouched. A new point is inserted between the
20
+ # two existing points that bracket each label distance, with lat/lon
21
+ # always interpolated, and ele/time interpolated only when both endpoints
22
+ # have values. The new point's +label+ field is set to the target
23
+ # distance (e.g. 1.0 for the point at the 1.0 km/mi mark).
24
+ #
25
+ # A label distance that falls (within a small tolerance) on an existing
26
+ # point is skipped — no duplicate point is inserted.
27
+ #
28
+ # Returns a new array; the original is not mutated. When +points+ has
29
+ # fewer than 2 elements, or +label_interval+ is nil, zero or negative,
30
+ # the original array is returned unchanged.
31
+ def label(points, label_interval)
32
+ return points if points.nil? || points.size < 2 || label_interval.nil? || label_interval <= 0
33
+
34
+ cumulative = cumulative_distances(points)
35
+
36
+ result = [points.first]
37
+ label_interval = label_interval.to_f
38
+ target = label_interval
39
+
40
+ points.each_cons(2).with_index do |(a, b), i|
41
+ start_dist = cumulative[i]
42
+ end_dist = cumulative[i + 1]
43
+
44
+ while target <= end_dist + EPSILON
45
+ if target > start_dist + EPSILON && target < end_dist - EPSILON
46
+ result << interpolate(a, b, start_dist, end_dist, target)
47
+ end
48
+ target += label_interval
49
+ end
50
+
51
+ result << b
52
+ end
53
+
54
+ result
55
+ end
56
+
57
+ private
58
+
59
+ # Returns the cumulative distance of every point, reusing each point's
60
+ # already-computed +cumulative_distance+ (see CumulativeDistanceEnhancer)
61
+ # when available, to avoid walking the points and recalculating distances
62
+ # a second time.
63
+ def cumulative_distances(points)
64
+ return points.map(&:cumulative_distance) if precomputed?(points)
65
+
66
+ unit_system = GpxDoctor.configuration.unit_system
67
+
68
+ cumulative = [0.0]
69
+ points.each_cons(2) do |a, b|
70
+ distance_km = DistanceCalculator.distance(a, b) / 1000.0
71
+ distance_converted = UnitConverter.convert_cumulative_distance(distance_km, unit_system)
72
+ cumulative << cumulative.last + distance_converted
73
+ end
74
+ cumulative
75
+ end
76
+
77
+ def precomputed?(points)
78
+ points.all? { |p| p.respond_to?(:cumulative_distance) && !p.cumulative_distance.nil? }
79
+ end
80
+
81
+ def interpolate(a, b, start_dist, end_dist, target)
82
+ fraction = (target - start_dist) / (end_dist - start_dist)
83
+ ele = a.ele && b.ele ? a.ele + fraction * (b.ele - a.ele) : nil
84
+ time = a.time && b.time ? a.time + fraction * (b.time - a.time) : nil
85
+
86
+ Models::Waypoint.new(
87
+ lat: a.lat + fraction * (b.lat - a.lat),
88
+ lon: a.lon + fraction * (b.lon - a.lon),
89
+ ele: ele,
90
+ time: time,
91
+ label: target,
92
+ cumulative_distance: target
93
+ )
94
+ end
95
+ end
96
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module GpxDoctor
4
- VERSION = '0.5.0'
4
+ VERSION = '0.6.1'
5
5
  end
data/lib/gpx_doctor.rb CHANGED
@@ -21,6 +21,7 @@ require 'gpx_doctor/statistics_enhancer'
21
21
  require 'gpx_doctor/cumulative_distance_enhancer'
22
22
  require 'gpx_doctor/segment_splitter'
23
23
  require 'gpx_doctor/point_selector'
24
+ require 'gpx_doctor/point_labeler'
24
25
  require 'gpx_doctor/parser'
25
26
  require 'gpx_doctor/builder'
26
27
  require 'gpx_doctor/geojson_builder'
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: gpx_doctor
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.0
4
+ version: 0.6.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Poltrax
@@ -63,6 +63,7 @@ files:
63
63
  - lib/gpx_doctor/models/track_segment.rb
64
64
  - lib/gpx_doctor/models/waypoint.rb
65
65
  - lib/gpx_doctor/parser.rb
66
+ - lib/gpx_doctor/point_labeler.rb
66
67
  - lib/gpx_doctor/point_selector.rb
67
68
  - lib/gpx_doctor/segment_splitter.rb
68
69
  - lib/gpx_doctor/statistics_enhancer.rb