gpx_doctor 0.2.0 → 0.4.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c1cba3a635e28b6b5731f9a178342323f6068e3954e03439c4c908045346d936
4
- data.tar.gz: 0a9dd34154ee4c75abd3c9556bf69fb80e238ca0a4d940d9371607f299871b73
3
+ metadata.gz: e0ba71f5752f36af3efd9f3b12de47d843a85b3ecd7fbcabd7468a2eef43355e
4
+ data.tar.gz: b58cb9eb1f207c3ec26f127b615ba43f70efe741904c6e73aa2c4ab059a7b710
5
5
  SHA512:
6
- metadata.gz: e9981e204bdf04c176e500920be0ff75b14a011e50d3a149590545adf4351d6e5f00e37ab51bb998f43b97d2619afa0927accb606ab584eab84f0d4e29d2229e
7
- data.tar.gz: 4b226863088aac9b8af344985426bc4beeee6693f1772b710537edb7e6c7059623f0824621f20f40cee55fe13114af7acdd306c524f51cd1fcdec38e50b44fb9
6
+ metadata.gz: 053df672b67de8623f4749e7a5d0e8419667abd7227e31a8937d2434ebad3a0af73d04ae54af95112ecffe1e7824314634316cc1bc1793863d5c9289057cf4bd
7
+ data.tar.gz: 2376c74be6339f8aeeb535e8f79dbd9dd876bca7526713f9be8e09da3793718eaba4e285a455e4a07e1f182768affcf206c6ae7ba81483e37c6468e59bf96e27
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,6 +88,7 @@ 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
91
+ cumulative_distance: true, # add cumulative distance from start of each segment/route
63
92
  enhance_elevation: true # fetch missing elevations from the configured elevation server
64
93
  })
65
94
  ```
@@ -69,10 +98,13 @@ Processing is applied in the following order:
69
98
  1. `max_distance` — segment splitting (interpolates intermediate points)
70
99
  2. `max_points` — point reduction
71
100
  3. `segment_statistics` — per-point statistics (distance, bearing, elevation change)
72
- 4. `enhance_elevation` — elevation lookup via the elevation server
101
+ 4. `cumulative_distance` — cumulative distance from the start of each segment/route
102
+ 5. `enhance_elevation` — elevation lookup via the elevation server
73
103
 
74
104
  `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
105
 
106
+ `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).
107
+
76
108
  ## Accessing data
77
109
 
78
110
  ```ruby
@@ -88,6 +120,121 @@ result.metadata # => #<Metadata …> (or nil)
88
120
  - `<rtept>` elements inside each `<rte>`
89
121
  - `<trkpt>` elements inside each `<trkseg>` inside each `<trk>`
90
122
 
123
+ ## Building GPX files
124
+
125
+ The `GpxDoctor::Builder` class generates GPX 1.1 XML from a `Result` object (the same structure returned by the parser).
126
+
127
+ ### Build to a string
128
+
129
+ ```ruby
130
+ result = GpxDoctor::Parser::Result.new(
131
+ waypoints: [],
132
+ routes: [],
133
+ tracks: [],
134
+ metadata: nil
135
+ )
136
+
137
+ xml_string = GpxDoctor::Builder.build(result)
138
+ # => "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<gpx version=\"1.1\"..."
139
+
140
+ # Optional: specify a custom creator attribute (identifies the software that created the GPX file)
141
+ xml_string = GpxDoctor::Builder.build(result, creator: 'My Application')
142
+ ```
143
+
144
+ ### Build to a file
145
+
146
+ ```ruby
147
+ GpxDoctor::Builder.build_file(result, 'path/to/output.gpx')
148
+ # Writes the GPX XML to the file and returns the XML string
149
+
150
+ # Optional: specify a custom creator attribute
151
+ GpxDoctor::Builder.build_file(result, 'path/to/output.gpx', creator: 'My Application')
152
+ ```
153
+
154
+ ### Input structure
155
+
156
+ The builder expects a `GpxDoctor::Parser::Result` object with the following fields:
157
+
158
+ - **`waypoints`** — Array of `GpxDoctor::Waypoint` objects (top-level waypoints)
159
+ - **`routes`** — Array of `GpxDoctor::Route` objects
160
+ - **`tracks`** — Array of `GpxDoctor::Track` objects
161
+ - **`metadata`** — `GpxDoctor::Metadata` object (optional)
162
+
163
+ All model classes are simple Ruby objects with attributes matching the GPX 1.1 specification (see **Model field reference** below).
164
+
165
+ ### Example: Creating a GPX file from scratch
166
+
167
+ ```ruby
168
+ require 'gpx_doctor'
169
+
170
+ # Create waypoints
171
+ waypoint = GpxDoctor::Waypoint.new(
172
+ lat: 48.2093723,
173
+ lon: 16.356099,
174
+ ele: 160.0,
175
+ name: 'Vienna',
176
+ desc: 'Capital of Austria'
177
+ )
178
+
179
+ # Create a route with points
180
+ route_point_1 = GpxDoctor::Waypoint.new(lat: 48.21, lon: 16.36, ele: 155.0)
181
+ route_point_2 = GpxDoctor::Waypoint.new(lat: 48.22, lon: 16.37, ele: 162.0)
182
+
183
+ route = GpxDoctor::Route.new(
184
+ name: 'City Tour',
185
+ desc: 'A route through the city',
186
+ points: [route_point_1, route_point_2]
187
+ )
188
+
189
+ # Create a track with segments
190
+ track_point_1 = GpxDoctor::Waypoint.new(lat: 48.23, lon: 16.38, ele: 170.0)
191
+ track_point_2 = GpxDoctor::Waypoint.new(lat: 48.24, lon: 16.39, ele: 175.0)
192
+
193
+ segment = GpxDoctor::TrackSegment.new(points: [track_point_1, track_point_2])
194
+ track = GpxDoctor::Track.new(
195
+ name: 'Morning Run',
196
+ desc: 'My morning jog',
197
+ segments: [segment]
198
+ )
199
+
200
+ # Create metadata (optional)
201
+ metadata = GpxDoctor::Metadata.new(
202
+ name: 'My GPX File',
203
+ desc: 'A custom GPX file',
204
+ time: Time.now
205
+ )
206
+
207
+ # Build the result object
208
+ result = GpxDoctor::Parser::Result.new(
209
+ waypoints: [waypoint],
210
+ routes: [route],
211
+ tracks: [track],
212
+ metadata: metadata
213
+ )
214
+
215
+ # Generate GPX XML
216
+ xml_string = GpxDoctor::Builder.build(result, creator: 'My Application')
217
+
218
+ # Or write directly to a file
219
+ GpxDoctor::Builder.build_file(result, 'my_route.gpx', creator: 'My Application')
220
+ ```
221
+
222
+ ### Round-trip workflow
223
+
224
+ You can parse an existing GPX file, modify it, and build it back:
225
+
226
+ ```ruby
227
+ # Parse existing file
228
+ result = GpxDoctor::Parser.parse('input.gpx')
229
+
230
+ # Modify data
231
+ result.routes.first.name = 'Updated Route Name'
232
+ result.waypoints << GpxDoctor::Waypoint.new(lat: 48.5, lon: 16.5, name: 'New Point')
233
+
234
+ # Build back to GPX
235
+ GpxDoctor::Builder.build_file(result, 'output.gpx')
236
+ ```
237
+
91
238
  ## Model field reference
92
239
 
93
240
  ### `Waypoint`
@@ -114,6 +261,10 @@ result.metadata # => #<Metadata …> (or nil)
114
261
  | `pdop` | Float | |
115
262
  | `ageofdgpsdata` | Float | |
116
263
  | `dgpsid` | Integer | 0–1023 |
264
+ | `distance_to_next` | Float | Distance to next point (metres or feet based on `unit_system`) — set by `segment_statistics: true` |
265
+ | `elevation_change` | Float | Elevation change to next point (metres or feet based on `unit_system`) — set by `segment_statistics: true` |
266
+ | `direction` | Float | Bearing to next point (0–360°) — set by `segment_statistics: true` |
267
+ | `cumulative_distance` | Float | Cumulative distance from segment/route start (kilometres or miles based on `unit_system`) — set by `cumulative_distance: true` |
117
268
 
118
269
  `Waypoint#to_h` returns a hash of all non-nil fields.
119
270
 
@@ -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
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GpxDoctor
4
+ class Error < StandardError; end
5
+ class InvalidGpxError < Error; end
6
+ end
@@ -0,0 +1,136 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'json'
4
+
5
+ module GpxDoctor
6
+ # Builds GeoJSON output from parsed GPX data according to RFC 7946.
7
+ #
8
+ # RFC 7946 compliance:
9
+ # - Coordinate order: [longitude, latitude, elevation]
10
+ # - LineString geometries require minimum 2 positions
11
+ # - MultiLineString segments require minimum 2 positions each
12
+ # - Empty or invalid geometries are filtered out
13
+ # - Properties member is null when no properties exist
14
+ class GeoJsonBuilder
15
+ class << self
16
+ def build(result)
17
+ new(result).build
18
+ end
19
+
20
+ def build_file(result, file_path)
21
+ json = build(result)
22
+ File.write(file_path, json)
23
+ json
24
+ end
25
+ end
26
+
27
+ def initialize(result)
28
+ @result = result
29
+ end
30
+
31
+ def build
32
+ features = []
33
+ features.concat(waypoint_features)
34
+ features.concat(route_features)
35
+ features.concat(track_features)
36
+
37
+ JSON.generate(
38
+ type: 'FeatureCollection',
39
+ features: features
40
+ )
41
+ end
42
+
43
+ private
44
+
45
+ def waypoint_features
46
+ @result.waypoints.map { |wpt| point_feature(wpt) }
47
+ end
48
+
49
+ def route_features
50
+ @result.routes.map do |route|
51
+ coords = route.points.map { |pt| coordinate(pt) }
52
+ # RFC 7946: LineString must have 2 or more positions
53
+ next if coords.length < 2
54
+
55
+ props = route_properties(route)
56
+ {
57
+ type: 'Feature',
58
+ properties: props.empty? ? nil : props,
59
+ geometry: {
60
+ type: 'LineString',
61
+ coordinates: coords
62
+ }
63
+ }
64
+ end.compact
65
+ end
66
+
67
+ def track_features
68
+ @result.tracks.map do |track|
69
+ # RFC 7946: Each LineString in MultiLineString must have 2+ positions
70
+ coords = track.segments.map do |seg|
71
+ seg_coords = seg.points.map { |pt| coordinate(pt) }
72
+ seg_coords if seg_coords.length >= 2
73
+ end.compact
74
+
75
+ # Skip tracks with no valid segments
76
+ next if coords.empty?
77
+
78
+ props = track_properties(track)
79
+ {
80
+ type: 'Feature',
81
+ properties: props.empty? ? nil : props,
82
+ geometry: {
83
+ type: 'MultiLineString',
84
+ coordinates: coords
85
+ }
86
+ }
87
+ end.compact
88
+ end
89
+
90
+ def point_feature(wpt)
91
+ props = waypoint_properties(wpt)
92
+ {
93
+ type: 'Feature',
94
+ properties: props.empty? ? nil : props,
95
+ geometry: {
96
+ type: 'Point',
97
+ coordinates: coordinate(wpt)
98
+ }
99
+ }
100
+ end
101
+
102
+ def coordinate(point)
103
+ coord = [point.lon, point.lat]
104
+ coord << point.ele if point.ele
105
+ coord
106
+ end
107
+
108
+ def waypoint_properties(wpt)
109
+ props = {}
110
+ props[:name] = wpt.name if wpt.name
111
+ props[:desc] = wpt.desc if wpt.desc
112
+ props[:sym] = wpt.sym if wpt.sym
113
+ props[:type] = wpt.type if wpt.type
114
+ props[:time] = wpt.time.iso8601 if wpt.time
115
+ props
116
+ end
117
+
118
+ def route_properties(route)
119
+ props = {}
120
+ props[:name] = route.name if route.name
121
+ props[:desc] = route.desc if route.desc
122
+ props[:type] = route.type if route.type
123
+ props[:number] = route.number if route.number
124
+ props
125
+ end
126
+
127
+ def track_properties(track)
128
+ props = {}
129
+ props[:name] = track.name if track.name
130
+ props[:desc] = track.desc if track.desc
131
+ props[:type] = track.type if track.type
132
+ props[:number] = track.number if track.number
133
+ props
134
+ end
135
+ end
136
+ 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
 
@@ -20,7 +20,8 @@ module GpxDoctor
20
20
  end
21
21
 
22
22
  def parse_string(xml_string, params: {})
23
- doc = Nokogiri::XML(xml_string)
23
+ Validator.validate!(xml_string)
24
+ doc = Nokogiri::XML(xml_string) { |config| config.nonet }
24
25
  ns = detect_namespace(doc)
25
26
 
26
27
  new(doc, ns, params: params).parse
@@ -52,6 +53,7 @@ module GpxDoctor
52
53
  split_segments(result, eff_max_dist) if eff_max_dist
53
54
  select_max_points(result) if @params[:max_points]
54
55
  enhance_statistics(result) if @params[:segment_statistics]
56
+ enhance_cumulative_distance(result) if @params[:cumulative_distance]
55
57
  enhance_elevations(result) if @params[:enhance_elevation]
56
58
 
57
59
  result
@@ -266,5 +268,13 @@ module GpxDoctor
266
268
  track.segments.each { |seg| enhancer.enhance(seg.points) }
267
269
  end
268
270
  end
271
+
272
+ def enhance_cumulative_distance(result)
273
+ enhancer = CumulativeDistanceEnhancer.new
274
+ result.routes.each { |route| enhancer.enhance(route.points) }
275
+ result.tracks.each do |track|
276
+ track.segments.each { |seg| enhancer.enhance(seg.points) }
277
+ end
278
+ end
269
279
  end
270
280
  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, flat-earth Pythagorean approximation)
7
- # - elevation_change (meters, next.ele - current.ele; nil when elevation missing)
8
- # - direction (degrees 0-360, geographic bearing to next point)
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
- current.distance_to_next = DistanceCalculator.distance(current, nxt)
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
@@ -0,0 +1,63 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'nokogiri'
4
+
5
+ module GpxDoctor
6
+ class Validator
7
+ ALLOWED_VERSIONS = %w[1.0 1.1].freeze
8
+
9
+ # Matches a DOCTYPE declaration anywhere in the string (case-insensitive).
10
+ # DOCTYPE is rejected entirely because it can carry external-entity
11
+ # references (XXE) or internal entity bombs (billion-laughs).
12
+ DOCTYPE_PATTERN = /<!DOCTYPE/i.freeze
13
+
14
+ class << self
15
+ def validate!(xml_string)
16
+ new(xml_string).validate!
17
+ end
18
+ end
19
+
20
+ def initialize(xml_string)
21
+ @xml_string = xml_string
22
+ end
23
+
24
+ # Raises GpxDoctor::InvalidGpxError if the input is not a valid, safe GPX document.
25
+ def validate!
26
+ reject_doctype!
27
+ doc = parse_xml!
28
+ validate_root!(doc)
29
+ validate_version!(doc)
30
+ true
31
+ end
32
+
33
+ private
34
+
35
+ def reject_doctype!
36
+ return unless @xml_string.match?(DOCTYPE_PATTERN)
37
+
38
+ raise InvalidGpxError, 'DOCTYPE declarations are not allowed'
39
+ end
40
+
41
+ def parse_xml!
42
+ doc = Nokogiri::XML(@xml_string) { |config| config.nonet }
43
+ return doc if doc.errors.empty?
44
+
45
+ raise InvalidGpxError, "Invalid XML: #{doc.errors.map(&:message).join('; ')}"
46
+ end
47
+
48
+ def validate_root!(doc)
49
+ root = doc.root
50
+ return if root&.name == 'gpx'
51
+
52
+ raise InvalidGpxError, 'Root element must be <gpx>'
53
+ end
54
+
55
+ def validate_version!(doc)
56
+ version = doc.root['version']
57
+ return if ALLOWED_VERSIONS.include?(version)
58
+
59
+ raise InvalidGpxError,
60
+ "Unsupported GPX version: #{version.inspect}. Supported versions: #{ALLOWED_VERSIONS.join(', ')}"
61
+ end
62
+ end
63
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module GpxDoctor
4
- VERSION = '0.2.0'
4
+ VERSION = '0.4.0'
5
5
  end
data/lib/gpx_doctor.rb CHANGED
@@ -1,7 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'gpx_doctor/version'
4
+ require 'gpx_doctor/errors'
4
5
  require 'gpx_doctor/configuration'
6
+ require 'gpx_doctor/validator'
5
7
  require 'gpx_doctor/models/email'
6
8
  require 'gpx_doctor/models/link'
7
9
  require 'gpx_doctor/models/copyright'
@@ -13,12 +15,15 @@ require 'gpx_doctor/models/track_segment'
13
15
  require 'gpx_doctor/models/route'
14
16
  require 'gpx_doctor/models/track'
15
17
  require 'gpx_doctor/distance_calculator'
18
+ require 'gpx_doctor/unit_converter'
16
19
  require 'gpx_doctor/elevation_client'
17
20
  require 'gpx_doctor/statistics_enhancer'
21
+ require 'gpx_doctor/cumulative_distance_enhancer'
18
22
  require 'gpx_doctor/segment_splitter'
19
23
  require 'gpx_doctor/point_selector'
20
24
  require 'gpx_doctor/parser'
21
25
  require 'gpx_doctor/builder'
26
+ require 'gpx_doctor/geojson_builder'
22
27
 
23
28
  module GpxDoctor
24
29
  class << self
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.2.0
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Poltrax
@@ -47,8 +47,11 @@ 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
53
+ - lib/gpx_doctor/errors.rb
54
+ - lib/gpx_doctor/geojson_builder.rb
52
55
  - lib/gpx_doctor/models/bounds.rb
53
56
  - lib/gpx_doctor/models/copyright.rb
54
57
  - lib/gpx_doctor/models/email.rb
@@ -63,6 +66,8 @@ files:
63
66
  - lib/gpx_doctor/point_selector.rb
64
67
  - lib/gpx_doctor/segment_splitter.rb
65
68
  - lib/gpx_doctor/statistics_enhancer.rb
69
+ - lib/gpx_doctor/unit_converter.rb
70
+ - lib/gpx_doctor/validator.rb
66
71
  - lib/gpx_doctor/version.rb
67
72
  homepage: https://github.com/Poltrax-live/gpx-doctor
68
73
  licenses: