gpx_doctor 0.3.0 → 0.5.0
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 +4 -4
- data/README.md +183 -2
- data/lib/gpx_doctor/configuration.rb +10 -1
- data/lib/gpx_doctor/cumulative_distance_enhancer.rb +28 -0
- data/lib/gpx_doctor/models/waypoint.rb +1 -1
- data/lib/gpx_doctor/parser.rb +57 -1
- data/lib/gpx_doctor/statistics_enhancer.rb +9 -5
- data/lib/gpx_doctor/unit_converter.rb +32 -0
- data/lib/gpx_doctor/version.rb +1 -1
- data/lib/gpx_doctor.rb +2 -0
- metadata +3 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 804ebe2457e24c9688e6e8993d0dc3d4adab7b7fddc27478706eaf72a9d09f59
|
|
4
|
+
data.tar.gz: e1bb3e8d17ff968a1cd6414c736d9f444559f447d5e6cc8dd3c79175a394b68b
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: f8e5bd2ab41937b0faa004c5d1c1ae3c422ba9d332e3aa1ea353fe7f9c22706c0f53e5b3c8161361e8d38ead9bed4fa516d7415d580cc912bdc3976452107dfb
|
|
7
|
+
data.tar.gz: 68e78f3a4f84027272a1a96127b998d2a00c447e1f0b75daff611a8d3123c374df0ab6ebf92563e6ab516a4ff9d2bcaa2540dabdad8358a9d35f87fd4cd23860
|
data/README.md
CHANGED
|
@@ -24,6 +24,7 @@ GpxDoctor.configure do |config|
|
|
|
24
24
|
config.elevation_server_url = "https://elevation.example.com"
|
|
25
25
|
config.elevation_server_user = "user"
|
|
26
26
|
config.elevation_server_password = "secret"
|
|
27
|
+
config.unit_system = :imperial # or :metric (default)
|
|
27
28
|
end
|
|
28
29
|
|
|
29
30
|
GpxDoctor.configuration.elevation_server_url # => "https://elevation.example.com"
|
|
@@ -36,6 +37,33 @@ GpxDoctor.reset_configuration! # resets to defaults
|
|
|
36
37
|
| `elevation_server_url` | String | `nil` | URL of the elevation server |
|
|
37
38
|
| `elevation_server_user` | String | `nil` | Username for the elevation server |
|
|
38
39
|
| `elevation_server_password` | String | `nil` | Password for the elevation server |
|
|
40
|
+
| `unit_system` | Symbol | `:metric` | Unit system (`:metric` or `:imperial`) for distance and elevation values |
|
|
41
|
+
|
|
42
|
+
### Unit System
|
|
43
|
+
|
|
44
|
+
The `unit_system` configuration affects the following fields:
|
|
45
|
+
|
|
46
|
+
- **`:metric` (default)**:
|
|
47
|
+
- `distance_to_next` and `elevation_change` are in **meters**
|
|
48
|
+
- `cumulative_distance` is in **kilometers**
|
|
49
|
+
- **`:imperial`**:
|
|
50
|
+
- `distance_to_next` and `elevation_change` are in **feet**
|
|
51
|
+
- `cumulative_distance` is in **miles**
|
|
52
|
+
|
|
53
|
+
```ruby
|
|
54
|
+
# Use imperial units
|
|
55
|
+
GpxDoctor.configure do |config|
|
|
56
|
+
config.unit_system = :imperial
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
result = GpxDoctor::Parser.parse("path/to/file.gpx", params: {
|
|
60
|
+
segment_statistics: true,
|
|
61
|
+
cumulative_distance: true
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
# distance_to_next and elevation_change will be in feet
|
|
65
|
+
# cumulative_distance will be in miles
|
|
66
|
+
```
|
|
39
67
|
|
|
40
68
|
## Parsing
|
|
41
69
|
|
|
@@ -60,7 +88,9 @@ result = GpxDoctor::Parser.parse("path/to/file.gpx", params: {
|
|
|
60
88
|
max_distance: 200, # insert interpolated points so no two consecutive points exceed this distance (metres)
|
|
61
89
|
max_points: 500, # reduce each segment to at most this many points
|
|
62
90
|
segment_statistics: true, # compute distance_to_next, elevation_change, direction for each point
|
|
63
|
-
|
|
91
|
+
cumulative_distance: true, # add cumulative distance from start of each segment/route
|
|
92
|
+
enhance_elevation: true, # fetch missing elevations from the configured elevation server
|
|
93
|
+
full_poi_data: true # populate result.pois with start/finish boundary data for the whole GPX and each track segment
|
|
64
94
|
})
|
|
65
95
|
```
|
|
66
96
|
|
|
@@ -69,10 +99,16 @@ Processing is applied in the following order:
|
|
|
69
99
|
1. `max_distance` — segment splitting (interpolates intermediate points)
|
|
70
100
|
2. `max_points` — point reduction
|
|
71
101
|
3. `segment_statistics` — per-point statistics (distance, bearing, elevation change)
|
|
72
|
-
4. `
|
|
102
|
+
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)
|
|
73
105
|
|
|
74
106
|
`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.
|
|
75
107
|
|
|
108
|
+
`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
|
+
|
|
110
|
+
`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
|
+
|
|
76
112
|
## Accessing data
|
|
77
113
|
|
|
78
114
|
```ruby
|
|
@@ -81,6 +117,7 @@ result.waypoints # => [#<Waypoint …>] (top-level <wpt> elements only)
|
|
|
81
117
|
result.routes # => [#<Route …>]
|
|
82
118
|
result.tracks # => [#<Track …>]
|
|
83
119
|
result.metadata # => #<Metadata …> (or nil)
|
|
120
|
+
result.pois # => Hash (only when parsed with full_poi_data: true, otherwise nil)
|
|
84
121
|
```
|
|
85
122
|
|
|
86
123
|
`result.points` is a flat array containing **all** geographic points from:
|
|
@@ -88,6 +125,146 @@ result.metadata # => #<Metadata …> (or nil)
|
|
|
88
125
|
- `<rtept>` elements inside each `<rte>`
|
|
89
126
|
- `<trkpt>` elements inside each `<trkseg>` inside each `<trk>`
|
|
90
127
|
|
|
128
|
+
### `result.pois`
|
|
129
|
+
|
|
130
|
+
When parsed with `full_poi_data: true`, `result.pois` contains boundary data for the entire GPX and each track segment:
|
|
131
|
+
|
|
132
|
+
```ruby
|
|
133
|
+
result = GpxDoctor::Parser.parse("route.gpx", params: { full_poi_data: true })
|
|
134
|
+
result.pois
|
|
135
|
+
# =>
|
|
136
|
+
# {
|
|
137
|
+
# start: { lon: 16.38, lat: 48.23, ele: 170.0, distance: 0.0 },
|
|
138
|
+
# finish: { lon: 16.39, lat: 48.24, ele: 175.0, distance: 1.42 },
|
|
139
|
+
# segments: [
|
|
140
|
+
# {
|
|
141
|
+
# start: { lon: 16.38, lat: 48.23, ele: 170.0, distance: 0.0 },
|
|
142
|
+
# finish: { lon: 16.39, lat: 48.24, ele: 175.0, distance: 1.42 }
|
|
143
|
+
# }
|
|
144
|
+
# ]
|
|
145
|
+
# }
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
- **`start`** / **`finish`** — first and last geographic point across all routes and track segments, each with `lon`, `lat`, `distance` (and `ele` when present). `start` always has `distance: 0.0`; `finish` distance is the sum of all individual route/segment lengths.
|
|
149
|
+
- **`segments`** — present only when the GPX contains track segments. Each entry is `{start:, finish:}` for one `<trkseg>`, with distances measured from the beginning of that segment (`start` is always `distance: 0.0`).
|
|
150
|
+
- The `ele` key is omitted for points that have no elevation value.
|
|
151
|
+
- Distance values respect the configured `unit_system` (kilometres for `:metric`, miles for `:imperial`).
|
|
152
|
+
|
|
153
|
+
## Building GPX files
|
|
154
|
+
|
|
155
|
+
The `GpxDoctor::Builder` class generates GPX 1.1 XML from a `Result` object (the same structure returned by the parser).
|
|
156
|
+
|
|
157
|
+
### Build to a string
|
|
158
|
+
|
|
159
|
+
```ruby
|
|
160
|
+
result = GpxDoctor::Parser::Result.new(
|
|
161
|
+
waypoints: [],
|
|
162
|
+
routes: [],
|
|
163
|
+
tracks: [],
|
|
164
|
+
metadata: nil
|
|
165
|
+
)
|
|
166
|
+
|
|
167
|
+
xml_string = GpxDoctor::Builder.build(result)
|
|
168
|
+
# => "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<gpx version=\"1.1\"..."
|
|
169
|
+
|
|
170
|
+
# Optional: specify a custom creator attribute (identifies the software that created the GPX file)
|
|
171
|
+
xml_string = GpxDoctor::Builder.build(result, creator: 'My Application')
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
### Build to a file
|
|
175
|
+
|
|
176
|
+
```ruby
|
|
177
|
+
GpxDoctor::Builder.build_file(result, 'path/to/output.gpx')
|
|
178
|
+
# Writes the GPX XML to the file and returns the XML string
|
|
179
|
+
|
|
180
|
+
# Optional: specify a custom creator attribute
|
|
181
|
+
GpxDoctor::Builder.build_file(result, 'path/to/output.gpx', creator: 'My Application')
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
### Input structure
|
|
185
|
+
|
|
186
|
+
The builder expects a `GpxDoctor::Parser::Result` object with the following fields:
|
|
187
|
+
|
|
188
|
+
- **`waypoints`** — Array of `GpxDoctor::Waypoint` objects (top-level waypoints)
|
|
189
|
+
- **`routes`** — Array of `GpxDoctor::Route` objects
|
|
190
|
+
- **`tracks`** — Array of `GpxDoctor::Track` objects
|
|
191
|
+
- **`metadata`** — `GpxDoctor::Metadata` object (optional)
|
|
192
|
+
|
|
193
|
+
All model classes are simple Ruby objects with attributes matching the GPX 1.1 specification (see **Model field reference** below).
|
|
194
|
+
|
|
195
|
+
### Example: Creating a GPX file from scratch
|
|
196
|
+
|
|
197
|
+
```ruby
|
|
198
|
+
require 'gpx_doctor'
|
|
199
|
+
|
|
200
|
+
# Create waypoints
|
|
201
|
+
waypoint = GpxDoctor::Waypoint.new(
|
|
202
|
+
lat: 48.2093723,
|
|
203
|
+
lon: 16.356099,
|
|
204
|
+
ele: 160.0,
|
|
205
|
+
name: 'Vienna',
|
|
206
|
+
desc: 'Capital of Austria'
|
|
207
|
+
)
|
|
208
|
+
|
|
209
|
+
# Create a route with points
|
|
210
|
+
route_point_1 = GpxDoctor::Waypoint.new(lat: 48.21, lon: 16.36, ele: 155.0)
|
|
211
|
+
route_point_2 = GpxDoctor::Waypoint.new(lat: 48.22, lon: 16.37, ele: 162.0)
|
|
212
|
+
|
|
213
|
+
route = GpxDoctor::Route.new(
|
|
214
|
+
name: 'City Tour',
|
|
215
|
+
desc: 'A route through the city',
|
|
216
|
+
points: [route_point_1, route_point_2]
|
|
217
|
+
)
|
|
218
|
+
|
|
219
|
+
# Create a track with segments
|
|
220
|
+
track_point_1 = GpxDoctor::Waypoint.new(lat: 48.23, lon: 16.38, ele: 170.0)
|
|
221
|
+
track_point_2 = GpxDoctor::Waypoint.new(lat: 48.24, lon: 16.39, ele: 175.0)
|
|
222
|
+
|
|
223
|
+
segment = GpxDoctor::TrackSegment.new(points: [track_point_1, track_point_2])
|
|
224
|
+
track = GpxDoctor::Track.new(
|
|
225
|
+
name: 'Morning Run',
|
|
226
|
+
desc: 'My morning jog',
|
|
227
|
+
segments: [segment]
|
|
228
|
+
)
|
|
229
|
+
|
|
230
|
+
# Create metadata (optional)
|
|
231
|
+
metadata = GpxDoctor::Metadata.new(
|
|
232
|
+
name: 'My GPX File',
|
|
233
|
+
desc: 'A custom GPX file',
|
|
234
|
+
time: Time.now
|
|
235
|
+
)
|
|
236
|
+
|
|
237
|
+
# Build the result object
|
|
238
|
+
result = GpxDoctor::Parser::Result.new(
|
|
239
|
+
waypoints: [waypoint],
|
|
240
|
+
routes: [route],
|
|
241
|
+
tracks: [track],
|
|
242
|
+
metadata: metadata
|
|
243
|
+
)
|
|
244
|
+
|
|
245
|
+
# Generate GPX XML
|
|
246
|
+
xml_string = GpxDoctor::Builder.build(result, creator: 'My Application')
|
|
247
|
+
|
|
248
|
+
# Or write directly to a file
|
|
249
|
+
GpxDoctor::Builder.build_file(result, 'my_route.gpx', creator: 'My Application')
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
### Round-trip workflow
|
|
253
|
+
|
|
254
|
+
You can parse an existing GPX file, modify it, and build it back:
|
|
255
|
+
|
|
256
|
+
```ruby
|
|
257
|
+
# Parse existing file
|
|
258
|
+
result = GpxDoctor::Parser.parse('input.gpx')
|
|
259
|
+
|
|
260
|
+
# Modify data
|
|
261
|
+
result.routes.first.name = 'Updated Route Name'
|
|
262
|
+
result.waypoints << GpxDoctor::Waypoint.new(lat: 48.5, lon: 16.5, name: 'New Point')
|
|
263
|
+
|
|
264
|
+
# Build back to GPX
|
|
265
|
+
GpxDoctor::Builder.build_file(result, 'output.gpx')
|
|
266
|
+
```
|
|
267
|
+
|
|
91
268
|
## Model field reference
|
|
92
269
|
|
|
93
270
|
### `Waypoint`
|
|
@@ -114,6 +291,10 @@ result.metadata # => #<Metadata …> (or nil)
|
|
|
114
291
|
| `pdop` | Float | |
|
|
115
292
|
| `ageofdgpsdata` | Float | |
|
|
116
293
|
| `dgpsid` | Integer | 0–1023 |
|
|
294
|
+
| `distance_to_next` | Float | Distance to next point (metres or feet based on `unit_system`) — set by `segment_statistics: true` |
|
|
295
|
+
| `elevation_change` | Float | Elevation change to next point (metres or feet based on `unit_system`) — set by `segment_statistics: true` |
|
|
296
|
+
| `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` |
|
|
117
298
|
|
|
118
299
|
`Waypoint#to_h` returns a hash of all non-nil fields.
|
|
119
300
|
|
|
@@ -5,13 +5,22 @@ module GpxDoctor
|
|
|
5
5
|
attr_accessor :elevation_server,
|
|
6
6
|
:elevation_server_url,
|
|
7
7
|
:elevation_server_user,
|
|
8
|
-
:elevation_server_password
|
|
8
|
+
:elevation_server_password,
|
|
9
|
+
:unit_system
|
|
9
10
|
|
|
10
11
|
def initialize
|
|
11
12
|
@elevation_server = false
|
|
12
13
|
@elevation_server_url = nil
|
|
13
14
|
@elevation_server_user = nil
|
|
14
15
|
@elevation_server_password = nil
|
|
16
|
+
@unit_system = :metric
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def unit_system=(value)
|
|
20
|
+
unless [:metric, :imperial].include?(value)
|
|
21
|
+
raise ArgumentError, "unit_system must be :metric or :imperial"
|
|
22
|
+
end
|
|
23
|
+
@unit_system = value
|
|
15
24
|
end
|
|
16
25
|
end
|
|
17
26
|
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module GpxDoctor
|
|
4
|
+
class CumulativeDistanceEnhancer
|
|
5
|
+
# Enhances each waypoint with cumulative distance from the start of the segment/route.
|
|
6
|
+
# The first point receives cumulative_distance = 0.0
|
|
7
|
+
# Each subsequent point receives cumulative_distance = previous.cumulative_distance + distance from previous
|
|
8
|
+
# Distance is stored in kilometers for metric and miles for imperial.
|
|
9
|
+
#
|
|
10
|
+
# Mutates waypoints in place.
|
|
11
|
+
def enhance(waypoints)
|
|
12
|
+
return if waypoints.nil? || waypoints.empty?
|
|
13
|
+
|
|
14
|
+
unit_system = GpxDoctor.configuration.unit_system
|
|
15
|
+
|
|
16
|
+
cumulative = 0.0
|
|
17
|
+
waypoints.first.cumulative_distance = cumulative
|
|
18
|
+
|
|
19
|
+
waypoints.each_cons(2) do |current, nxt|
|
|
20
|
+
distance_meters = DistanceCalculator.distance(current, nxt)
|
|
21
|
+
distance_km = distance_meters / 1000.0
|
|
22
|
+
distance_converted = UnitConverter.convert_cumulative_distance(distance_km, unit_system)
|
|
23
|
+
cumulative += distance_converted
|
|
24
|
+
nxt.cumulative_distance = cumulative
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -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].freeze
|
|
11
|
+
STATISTICS_FIELDS = %i[distance_to_next elevation_change direction cumulative_distance].freeze
|
|
12
12
|
|
|
13
13
|
attr_accessor(*STATISTICS_FIELDS)
|
|
14
14
|
|
data/lib/gpx_doctor/parser.rb
CHANGED
|
@@ -7,7 +7,7 @@ module GpxDoctor
|
|
|
7
7
|
class Parser
|
|
8
8
|
GPX_NS = 'http://www.topografix.com/GPX/1/1'
|
|
9
9
|
|
|
10
|
-
Result = Struct.new(:waypoints, :routes, :tracks, :metadata, keyword_init: true) do
|
|
10
|
+
Result = Struct.new(:waypoints, :routes, :tracks, :metadata, :pois, keyword_init: true) do
|
|
11
11
|
def points
|
|
12
12
|
waypoints + routes.flat_map(&:points) + tracks.flat_map(&:points)
|
|
13
13
|
end
|
|
@@ -53,7 +53,9 @@ module GpxDoctor
|
|
|
53
53
|
split_segments(result, eff_max_dist) if eff_max_dist
|
|
54
54
|
select_max_points(result) if @params[:max_points]
|
|
55
55
|
enhance_statistics(result) if @params[:segment_statistics]
|
|
56
|
+
enhance_cumulative_distance(result) if @params[:cumulative_distance]
|
|
56
57
|
enhance_elevations(result) if @params[:enhance_elevation]
|
|
58
|
+
result.pois = build_pois(result) if @params[:full_poi_data]
|
|
57
59
|
|
|
58
60
|
result
|
|
59
61
|
end
|
|
@@ -267,5 +269,59 @@ module GpxDoctor
|
|
|
267
269
|
track.segments.each { |seg| enhancer.enhance(seg.points) }
|
|
268
270
|
end
|
|
269
271
|
end
|
|
272
|
+
|
|
273
|
+
def enhance_cumulative_distance(result)
|
|
274
|
+
enhancer = CumulativeDistanceEnhancer.new
|
|
275
|
+
result.routes.each { |route| enhancer.enhance(route.points) }
|
|
276
|
+
result.tracks.each do |track|
|
|
277
|
+
track.segments.each { |seg| enhancer.enhance(seg.points) }
|
|
278
|
+
end
|
|
279
|
+
end
|
|
280
|
+
|
|
281
|
+
def point_to_poi(point, distance)
|
|
282
|
+
poi = { lon: point.lon, lat: point.lat }
|
|
283
|
+
poi[:ele] = point.ele if point.ele
|
|
284
|
+
poi[:distance] = distance
|
|
285
|
+
poi
|
|
286
|
+
end
|
|
287
|
+
|
|
288
|
+
def build_pois(result)
|
|
289
|
+
collections = result.routes.map(&:points) +
|
|
290
|
+
result.tracks.flat_map { |t| t.segments.map(&:points) }
|
|
291
|
+
non_empty = collections.reject(&:empty?)
|
|
292
|
+
|
|
293
|
+
return {} if non_empty.empty?
|
|
294
|
+
|
|
295
|
+
total_distance = non_empty.sum { |pts| collection_distance(pts) }
|
|
296
|
+
|
|
297
|
+
pois = {
|
|
298
|
+
start: point_to_poi(non_empty.first.first, 0.0),
|
|
299
|
+
finish: point_to_poi(non_empty.last.last, total_distance)
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
segments_pois = result.tracks.flat_map do |track|
|
|
303
|
+
track.segments.filter_map do |seg|
|
|
304
|
+
next if seg.points.empty?
|
|
305
|
+
|
|
306
|
+
{
|
|
307
|
+
start: point_to_poi(seg.points.first, 0.0),
|
|
308
|
+
finish: point_to_poi(seg.points.last, collection_distance(seg.points))
|
|
309
|
+
}
|
|
310
|
+
end
|
|
311
|
+
end
|
|
312
|
+
|
|
313
|
+
pois[:segments] = segments_pois unless segments_pois.empty?
|
|
314
|
+
pois
|
|
315
|
+
end
|
|
316
|
+
|
|
317
|
+
def collection_distance(points)
|
|
318
|
+
return 0.0 if points.length < 2
|
|
319
|
+
|
|
320
|
+
unit_system = GpxDoctor.configuration.unit_system
|
|
321
|
+
points.each_cons(2).sum do |a, b|
|
|
322
|
+
dist_km = DistanceCalculator.distance(a, b) / 1000.0
|
|
323
|
+
UnitConverter.convert_cumulative_distance(dist_km, unit_system)
|
|
324
|
+
end
|
|
325
|
+
end
|
|
270
326
|
end
|
|
271
327
|
end
|
|
@@ -3,20 +3,24 @@
|
|
|
3
3
|
module GpxDoctor
|
|
4
4
|
class StatisticsEnhancer
|
|
5
5
|
# Enhances each consecutive pair of waypoints with statistics:
|
|
6
|
-
# - distance_to_next (meters
|
|
7
|
-
# - elevation_change
|
|
8
|
-
# - direction
|
|
6
|
+
# - distance_to_next (meters or feet depending on configuration)
|
|
7
|
+
# - elevation_change (meters or feet depending on configuration)
|
|
8
|
+
# - direction (degrees 0-360, geographic bearing to next point)
|
|
9
9
|
#
|
|
10
10
|
# The last point in the list receives nil for all three fields.
|
|
11
11
|
# Mutates waypoints in place.
|
|
12
12
|
def enhance(waypoints)
|
|
13
13
|
return if waypoints.nil? || waypoints.size < 2
|
|
14
14
|
|
|
15
|
+
unit_system = GpxDoctor.configuration.unit_system
|
|
16
|
+
|
|
15
17
|
waypoints.each_cons(2) do |current, nxt|
|
|
16
|
-
|
|
18
|
+
distance_meters = DistanceCalculator.distance(current, nxt)
|
|
19
|
+
current.distance_to_next = UnitConverter.convert_distance(distance_meters, unit_system)
|
|
17
20
|
|
|
18
21
|
current.elevation_change = if current.ele && nxt.ele
|
|
19
|
-
nxt.ele - current.ele
|
|
22
|
+
elevation_change_meters = nxt.ele - current.ele
|
|
23
|
+
UnitConverter.convert_elevation(elevation_change_meters, unit_system)
|
|
20
24
|
end
|
|
21
25
|
|
|
22
26
|
current.direction = DistanceCalculator.bearing(current, nxt)
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module GpxDoctor
|
|
4
|
+
module UnitConverter
|
|
5
|
+
METERS_TO_FEET = 3.28084
|
|
6
|
+
METERS_TO_MILES = 0.000621371
|
|
7
|
+
KILOMETERS_TO_MILES = 0.621371
|
|
8
|
+
|
|
9
|
+
module_function
|
|
10
|
+
|
|
11
|
+
# Convert meters to the configured unit system
|
|
12
|
+
# Returns feet for imperial, meters for metric
|
|
13
|
+
def convert_distance(meters, unit_system = :metric)
|
|
14
|
+
return meters if unit_system == :metric
|
|
15
|
+
meters * METERS_TO_FEET
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# Convert meters to the configured unit system
|
|
19
|
+
# Returns feet for imperial, meters for metric
|
|
20
|
+
def convert_elevation(meters, unit_system = :metric)
|
|
21
|
+
return meters if unit_system == :metric
|
|
22
|
+
meters * METERS_TO_FEET
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# Convert kilometers to the configured unit system
|
|
26
|
+
# Returns miles for imperial, kilometers for metric
|
|
27
|
+
def convert_cumulative_distance(kilometers, unit_system = :metric)
|
|
28
|
+
return kilometers if unit_system == :metric
|
|
29
|
+
kilometers * KILOMETERS_TO_MILES
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
data/lib/gpx_doctor/version.rb
CHANGED
data/lib/gpx_doctor.rb
CHANGED
|
@@ -15,8 +15,10 @@ require 'gpx_doctor/models/track_segment'
|
|
|
15
15
|
require 'gpx_doctor/models/route'
|
|
16
16
|
require 'gpx_doctor/models/track'
|
|
17
17
|
require 'gpx_doctor/distance_calculator'
|
|
18
|
+
require 'gpx_doctor/unit_converter'
|
|
18
19
|
require 'gpx_doctor/elevation_client'
|
|
19
20
|
require 'gpx_doctor/statistics_enhancer'
|
|
21
|
+
require 'gpx_doctor/cumulative_distance_enhancer'
|
|
20
22
|
require 'gpx_doctor/segment_splitter'
|
|
21
23
|
require 'gpx_doctor/point_selector'
|
|
22
24
|
require 'gpx_doctor/parser'
|
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.
|
|
4
|
+
version: 0.5.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Poltrax
|
|
@@ -47,6 +47,7 @@ files:
|
|
|
47
47
|
- lib/gpx_doctor.rb
|
|
48
48
|
- lib/gpx_doctor/builder.rb
|
|
49
49
|
- lib/gpx_doctor/configuration.rb
|
|
50
|
+
- lib/gpx_doctor/cumulative_distance_enhancer.rb
|
|
50
51
|
- lib/gpx_doctor/distance_calculator.rb
|
|
51
52
|
- lib/gpx_doctor/elevation_client.rb
|
|
52
53
|
- lib/gpx_doctor/errors.rb
|
|
@@ -65,6 +66,7 @@ files:
|
|
|
65
66
|
- lib/gpx_doctor/point_selector.rb
|
|
66
67
|
- lib/gpx_doctor/segment_splitter.rb
|
|
67
68
|
- lib/gpx_doctor/statistics_enhancer.rb
|
|
69
|
+
- lib/gpx_doctor/unit_converter.rb
|
|
68
70
|
- lib/gpx_doctor/validator.rb
|
|
69
71
|
- lib/gpx_doctor/version.rb
|
|
70
72
|
homepage: https://github.com/Poltrax-live/gpx-doctor
|